mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-11 22:54:00 +01:00
tokens.md 260 -> 219 + operability.md 69
nielsen-audit.md 258 -> 154 + shipped-record.md 122
README.md 257 -> 172 + operability-patterns.md 95
Worked the requested order rather than cutting straight to a split.
(a) Automate: the categorical ramp's four constraints were restated in prose
with the measured margins hand-copied (ΔE 34.6/26.0/25.6/27.4, etc).
ramp_check.py prints every one of those numbers and fails if any
regresses, so the prose now points at the command instead of duplicating
its output. The two consequences the numbers do not state — why the ramp
is mid-toned, and why lightness falls monotonically — stay in prose.
(b) Dedup: measured, not assumed. Zero paragraphs are shared between the
three files; README already delegates the rules to tokens.md and shows
only per-stack code. Nothing to remove.
(c/d) Split on meaning: tokens.md is "frozen values", so the behavioural
requirements that *use* those tokens moved to operability.md, with the
per-stack code for them in operability-patterns.md. nielsen-audit.md is a
live grid, so completed phases moved to shipped-record.md — following the
existing phase3-record.md convention, not a new one.
The focus-ring token row had to stay in tokens.md: palette_check.py parses
that file, and moving the row made the check fail with "MISSING md:focus-ring"
— a token it cannot see is a token nothing compares across the four stacks.
The rationale lives in operability.md and the row links to it.
palette_check and ramp_check both at 0 failing checks; every relative link
between the docs resolves.
`bash scripts/check_file_length.sh --all` now exits 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CFSGeWxHPY5rHUGrPqhcmG
70 lines
3.6 KiB
Markdown
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
|
|
[`README.md`](README.md#pointer-free--small-screen-per-stack-patterns).
|
|
|
|
## 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.
|