utils/unified-design-system/operability.md
Krzysztof kuhy Rudnicki 44bc12b61a
Some checks failed
crdt-sync pre-commit / pre-commit (push) Waiting to run
crdt-sync tests / test (3.10) (push) Waiting to run
crdt-sync tests / test (3.11) (push) Waiting to run
crdt-sync tests / test (3.12) (push) Waiting to run
design_system tests / test (push) Waiting to run
file length / file-length (push) Waiting to run
palette drift / palette (push) Waiting to run
sync_settings_ui tests / test (push) Waiting to run
web_ui tests / test (push) Waiting to run
gatelock pre-commit / pre-commit (push) Has been cancelled
gatelock tests / test (3.10) (push) Has been cancelled
gatelock tests / test (3.11) (push) Has been cancelled
gatelock tests / test (3.12) (push) Has been cancelled
Fix a dangling anchor left by the doc split
operability.md pointed at README.md#pointer-free--small-screen-per-stack-patterns,
written before that section was moved out of README into
operability-patterns.md. The link now targets the file that holds the content.

My earlier link check only validated the *file* part of each link and silently
ignored the fragment, which is why this survived. Re-checked with the anchor
compared against the target file's actual headings; all links in the directory
now resolve, fragment included.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CFSGeWxHPY5rHUGrPqhcmG
2026-08-22 16:03:20 +02:00

70 lines
3.6 KiB
Markdown

# Pointer-free operability and screen size (required)
Split out of [`tokens.md`](tokens.md), which freezes the palette and the
scales. These are the *behavioural* requirements that use those tokens — a
token file should be readable as a table of values, and these are rules with
reasons attached.
Per-stack implementations of everything here are in
[`operability-patterns.md`](operability-patterns.md).
## Pointer-free operability (required)
**Every action must be reachable and activatable with the keyboard alone.** No
mouse, no touchpad, no touch. This is not an accessibility nicety here: several
of these apps are *lockers* that hold a global input grab with VT switching
disabled, so a control only a pointer can reach is not an inconvenience — it is
a bricked machine.
Six things must hold on every surface:
1. **No pointer-only handler.** Any `onTap`/`onLongPress`/`<Button-1>`/click
handler needs a keyboard-reachable equivalent on the same action. A bare
`GestureDetector` and an `onLongPress`-only destructive action are both
defects. Long-press in particular has *no* keyboard analogue — never make it
the only path to an action.
2. **Everything interactive is a focus stop.** Widgets that opt out of the focus
ring (`takefocus=0`, which Tk's `Menubutton`/`OptionMenu` default to) are
unreachable. Verify by walking the real ring, not by reading the code.
3. **Focus is visible.** See the focus-ring token below — the platform default
is a black ring, which vanishes on `ink`.
4. **Focus never lands somewhere invisible.** If a container scrolls, focus
entering a clipped child must scroll it into view. Clipping does not remove a
widget from the focus ring, so this is the default failure, not an edge case.
5. **Scrolling is keyboard-driven.** A scroll container needs `PageUp`/`PageDown`
and arrow keys, not just a draggable thumb and a wheel.
6. **Confirm/submit and cancel/back have accelerators.** `Enter` submits, `Escape`
cancels. On Tk, note `Enter` does **not** activate a `tk.Button` on X11 (only
`Space` does) and is bound to nothing on `Entry`/`Spinbox` — you must bind it.
### The focus-ring token
`focus-ring` is `#B8862E` (= `accent`), 2px at 1px offset. It is defined in
[`tokens.md`](tokens.md#focus-ring) with the rest of the palette, because
`scripts/palette_check.py` parses that file — a token it cannot see is a token
nothing compares across the four stacks.
Platform defaults are wrong on this palette: Tk ships
`highlightcolor="#000000"` with `highlightthickness=1`, i.e. a black ring on
`ink` (`#211D1B`) — invisible. Set `focus-ring` explicitly. Never
`highlightthickness=0` on something focusable.
⚠️ On Tk, `highlightbackground` is the **unfocused** ring and `highlightcolor`
is the **focused** one. Setting only `highlightbackground` inverts the
affordance — the widget outlines when it is *not* focused and goes black when
it *is*. Set `highlightcolor`.
### Escape hatches vs. deliberate gating
Some apps intentionally resist dismissal (an alarm you must solve a challenge to
silence, a meal gate you must log a meal to clear). That gating is a feature and
this rule does not override it. What the rule requires is that the *sanctioned*
paths — solve the challenge, use the budgeted escape hatch, submit the form — are
fully keyboard-operable. Making a gate keyboard-reachable is not weakening it.
## Screen size (required)
Moved to `screen-size.md` (250-line cap): the 1366x768 / 1024x600 targets, the
height-budgeting rules, and why centring unscrollable content clips
symmetrically. Still part of this spec — read it for any layout work.