FieldHelp's portalled panel puts a docsHref link at the end of the tab order #840

Open
opened 2026-08-26 19:24:08 +02:00 by timothy · 0 comments
Owner

Split out of #734 (raised by its third cold review, finding N2).

FieldHelp's panel is portalled to document.body so that .ctv-card's overflow: hidden cannot clip it. The cost, accepted knowingly and recorded in docs/spa-conventions.md §15 and spa.field-progressive-disclosure: a focusable element inside the panel lands at the end of the document's tab order, not next to its trigger.

Measured on the FFmpeg profile editor with a pinned panel: document.body.lastElementChild === panel, trigger is focusable #34 of 66 — so a link in the panel is roughly 32 tab stops past its own trigger, after every remaining control on the page. Before the portal it was the immediate next stop.

Why it is not urgent

Latent. The only panel-internal focusable is the level-3 docsHref link, and no screen passes docsHref — the external docs do not exist yet. #734 built and typed the affordance without a live target.

Why it was not fixed in #734

It was found at the third review round of a five-commit branch. The fix is custom Tab-forwarding between a trigger and its portalled panel — new interaction code, on the round where new interaction code had already produced one withdrawn mechanism (the hover bridge, added and removed within the same branch after it turned out to work only on a vertical descent). Adding a second bespoke mechanism at that point would have repeated the mistake.

The trigger

The first screen that passes docsHref. Until then there is nothing to tab to. §15's adopting checklist points here, so an adopter meets this issue at the moment it starts to matter.

Sketch

Likely a keydown on the trigger that forwards Tab into the panel when it is pinned and holds a focusable element, plus Shift+Tab back — or a focus trap while pinned, if the panel ever grows past one link. Prefer whichever is testable in jsdom without asserting its own arrangement; note that React propagates portal events through the React tree, which has already caused one test here to pass for the wrong reason.

Done-when

  • A focusable element inside a pinned panel is reachable from its trigger in document order
  • Shift-Tab returns to the trigger
  • The test fails against the current (portal-only) behaviour — mutation proof, executed
  • The known-limitation notes in docs/spa-conventions.md §15 and docs/decisions/records/spa/field-progressive-disclosure.md are replaced by the real behaviour
  • Adversarial review passed
Split out of #734 (raised by its third cold review, finding N2). `FieldHelp`'s panel is portalled to `document.body` so that `.ctv-card`'s `overflow: hidden` cannot clip it. The cost, accepted knowingly and recorded in `docs/spa-conventions.md` §15 and `spa.field-progressive-disclosure`: **a focusable element inside the panel lands at the end of the document's tab order**, not next to its trigger. Measured on the FFmpeg profile editor with a pinned panel: `document.body.lastElementChild === panel`, trigger is focusable #34 of 66 — so a link in the panel is roughly 32 tab stops past its own trigger, after every remaining control on the page. Before the portal it was the immediate next stop. ## Why it is not urgent **Latent.** The only panel-internal focusable is the level-3 `docsHref` link, and **no screen passes `docsHref`** — the external docs do not exist yet. #734 built and typed the affordance without a live target. ## Why it was not fixed in #734 It was found at the third review round of a five-commit branch. The fix is custom Tab-forwarding between a trigger and its portalled panel — new interaction code, on the round where new interaction code had already produced one withdrawn mechanism (the hover bridge, added and removed within the same branch after it turned out to work only on a vertical descent). Adding a second bespoke mechanism at that point would have repeated the mistake. ## The trigger **The first screen that passes `docsHref`.** Until then there is nothing to tab to. §15's adopting checklist points here, so an adopter meets this issue at the moment it starts to matter. ## Sketch Likely a `keydown` on the trigger that forwards Tab into the panel when it is pinned and holds a focusable element, plus Shift+Tab back — or a focus trap while pinned, if the panel ever grows past one link. Prefer whichever is testable in jsdom without asserting its own arrangement; note that React propagates portal events through the React tree, which has already caused one test here to pass for the wrong reason. ## Done-when - [ ] A focusable element inside a pinned panel is reachable from its trigger in document order - [ ] Shift-Tab returns to the trigger - [ ] The test fails against the current (portal-only) behaviour — mutation proof, executed - [ ] The known-limitation notes in `docs/spa-conventions.md` §15 and `docs/decisions/records/spa/field-progressive-disclosure.md` are replaced by the real behaviour - [ ] Adversarial review passed
timothy added the frontendpriority: low labels 2026-08-26 19:24:34 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: timothy/ersatztv#840