oficina / docs Public

The formatting bar is one tab stop, not eleven

role="toolbar" is a promise: a single tab stop, with the arrow keys
moving between the controls inside it. This branch made that promise in
the markup and did not keep it — eleven tab stops between the page
header and the first word of a document, on every page load, for someone
who only wanted to type. Measured, not estimated: the drive counted nine
presses from sign-out to the editor before this commit.

toolbar.mjs now implements the WAI-ARIA pattern. Every control is
tabindex="-1" in the server-rendered markup and the module promotes one;
Left/Right (and Home/End) move between them, with preventDefault on the
keys it handles because a closed native <select> changes its own value
on Left/Right. Up/Down are left alone, which is how the three selects
stay operable from the keyboard.

Not by reordering the DOM, which was the other available fix and is
worse: the bar sits above the paper visually, so DOM order already
matches reading order, and pulling focus order away from visual order to
save a few tab presses trades a small annoyance for a WCAG 2.4.3 Focus
Order violation. (Raised, and argued correctly, by the caret-placement
session.)

Nothing here touches focus VISIBILITY. The controls keep rastrillo's own
ring — a roving tabindex decides which control Tab reaches, not whether
you can see which one you are on — and show.html's ring removal stays
scoped to [docs-editor] alone.

One trap this pattern sets, handled: undo and redo start disabled, a
disabled control cannot take focus at all, and a tab stop parked on one
makes the whole bar unreachable rather than merely awkward. ensureReachable
runs after every sync, because sync is what enables and disables things.

Three checks in the browser drive, all confirmed red before being kept:

- A focus trap on the bar (preventDefault on Tab) — the reachability
  check reports twelve presses without arriving.
- The pre-fix behaviour (every control its own tab stop) — nine presses,
  which is what the single-tab-stop check now refuses.
- Arrow navigation removed — focus goes "from undo to undo".

The reachability check exists because of a gap in the previous commit's
keyboard assertion, spotted by the caret session: focusing the editor
programmatically succeeds whether or not a person could ever have got
there with Tab, so a bar that trapped focus would have left the document
unreachable to anyone without a mouse while every check still passed. It
tabs in from the sign-out button instead, bounded at twelve presses —
bounded rather than exact, so it does not become an assertion about how
many controls the bar has this week.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YYB9fUAfxXNzkeC3fsFYzR
Paul Campbell pushed by paul@keymail.dev beb6bbce2a05a4803488fe50f3744420b379da4e parent 8aa8701
4 files changed, +167 −17
  • fixtures/browser-drive.mjs +64 −0
  • fixtures/toolbar.mjs +87 −1
  • internal/docs/static/{editor.b09632ce9455588a.js => editor.3dbb8fa17262528b.js} +4 −4
  • internal/docs/views/show.html +12 −12

CI passed — run details

Intent

Human

<task-notification> <task-id>b35fljxli</task-id> <tool-use-id>toolu_015YpDqw1S2D5iDujzG1qhMV</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-oficina-docs/8c293727-d922-4f5a-bfde-9457b649670f/tasks/b35fljxli.output</output-file> <status>completed</status> <summary>Background command "Wait for CI to settle" completed (exit code 0)</summary> </task-notification>

Attributed to this commit: 125 g CO₂e (16239871 tokens). This is an attribution, not a measurement — usage counted from this commit's own marker forward to the next one on formatting-and-export, never backward from it. A turn that produced more than one commit books all of its tokens to the last one, and a concurrent session on this branch can add to the same figure. Some of it was priced using the shared default rate, because no model-specific factor row exists yet for at least one model involved. The Carbon view has the repo total, which is never derived from this attribution.

fixtures/browser-drive.mjs

Loading diff…

fixtures/toolbar.mjs

Loading diff…

internal/docs/static/{editor.b09632ce9455588a.js => editor.3dbb8fa17262528b.js}

Loading diff…

internal/docs/views/show.html

Loading diff…