Tools on a phone
See also: Tools · Creating new tools · Tools JS lifecycle
Every tool must display and work on a 360 CSS-pixel phone. The bar is usable, not comfortable: nothing clipped, no sideways page scroll, every action reachable, every input typeable. This page is the contract a tool author works to, and how it is checked.
The contract
At the phone tier (@width_break_point_phone, 600px) and down to the floor @min_target_viewport (360px):
- The page never scrolls sideways. A wide child reflows, or scrolls inside its own box (
overflow-x: auto) — a translation matrix or an A4 print preview legitimately pans; the page does not. - Every visible control is on screen and at least 44px on its shortest side (WCAG 2.5.5). This includes disabled controls: a disabled control is the same target the moment it is enabled.
- Text stays readable. A column so narrow that a word stands one letter per line fails, even when nothing overflows.
- A tool that opens in a dialog is a full-screen sheet.
- A drag has a touch alternative. HTML5 drag-and-drop does not fire from a finger, so a drag-only action is impossible on a phone (see Gestures).
What you get for free
client/dedalo/core/tools_common/css/tool_responsive.less (imported by tool_common.less) applies to every tool:
| Surface | Phone behaviour |
|---|---|
.tool_header |
Title above description, description capped at two lines, sticky. The dialog title bar grows with it; the body starts below. |
.tool_header > .tool_buttons_container |
Controls wrap and become 44px tall. |
.wrapper_tool inputs, selects, buttons |
44px targets; inputs at 16px text (iOS zooms the page on focus of anything smaller). |
| images, video, canvas | max-width: 100%. |
table |
Scrolls inside its own box. |
.phone_stack |
Opt-in: add the class to a grid and it becomes one column. |
Dialogs (dd-modal) |
Full-viewport sheet. |
Most tools pass on this alone. Write tool-specific rules only for what it does not cover.
Writing phone rules for your tool
Put them in your tool's own sheet, nested in the rule they change, against the token — a literal width fails tool_phone_tripwire:
.wrapper_tool.tool_my_tool {
>.content_data {
grid-template-columns: 240px 1fr 1fr;
@media screen and (max-width: @width_break_point_phone) {
grid-template-columns: minmax(0, 1fr);
}
}
}
Your sheet must import the variables (@import (once) '../../../client/dedalo/core/page/css/layout/vars';), then bun run css:build.
The mistakes the phone check found most often:
| Symptom | Cause | Fix |
|---|---|---|
| Content 500px wide in a 360px sheet | A desktop min-width (often 34rem) |
Lift it at the phone tier. |
| Buttons spill off both edges | display: flex; justify-content: center without wrapping |
flex-wrap: wrap. |
| One letter per line | Many 1fr columns in a narrow box |
Stack the columns, or give them a minimum width and let the box scroll. |
| A 44px rule has no effect | A more specific original selector wins | Nest the phone rule inside the original rule. |
| An item shrinks to 10px | It is a flex item beside a long label | flex: 0 0 44px. |
Gestures
Three shared helpers cover what CSS cannot:
- Tap to pick, tap to place —
client/dedalo/core/common/js/touch_pick.js. The touch twin of a drag onto a target (a record onto a thesaurus term, a coin into a slot). The drag source callstouch_pick.pick(payload, label)with the same payload itsdragstartputs indataTransfer; the drop target, on a tap whiletouch_pick.active(), calls its existing drop handler withtouch_pick.as_drop_event(). One drop implementation, two gestures. Offer the tap only whentouch_pick.is_touch()(a coarse pointer, not a narrow window). Used by tool_cataloging and tool_numisdata_order_coins. - Up/down buttons —
client/dedalo/core/tools_common/js/phone_reorder.js. The touch twin of a drag that sorts a list.render_phone_reorder({on_move, labels, is_first, is_last});on_move(-1|1)must run the same reorder the drop does. - Pane switch —
client/dedalo/core/tools_common/js/pane_switch.js. For a two-pane tool that is unusable stacked:render_pane_switch(host, [{key, label}, …])shows onedata-paneat a time on a phone and nothing changes on a desktop.
Checking your tool
bun run test:tools:phone --tool tool_my_tool --shots /tmp/shots
It starts its own server on the suite database, logs in, opens the tool the way a user does (the caller's page, then open_tool on the live instance) at 360×740 with touch emulation, and reports every failure of the contract above, plus the tool rendering its error panel. --shots saves a full-page screenshot: read it — the check measures geometry, and a screenshot is the only review of whether the tool is actually usable.
To add a tool, give it a probe in test/helpers/tool_phone_ratchet.ts. For a tool on a component of the suite's test3 playground section (a component_input_text is test52):
tool_my_tool: test3('test52', 'component_input_text', 'lg-spa'),
Other probe kinds cover a list button, a section_tool, a real tap, and a launcher's own open function; a probe can also build a scratch record, which is swept afterwards. To find which live elements offer your tool: bun run test:tools:phone --discover test3/1.
The ratchet only shrinks: a tool in NOT_YET_PHONE whose probe starts passing turns the run red until it moves to PHONE_CASES. test/unit/tool_phone_tripwire.test.ts keeps the list complete — a new tool directory must appear in one of the two lists.