Captures in this browser: every storage change rebuilds the list and drops keyboard focus #17

Closed
opened 2026-10-01 10:47:16 +00:00 by tiagoagueda · 0 comments
Owner

Found on 2026-09-29 in a read-only review of postulo-chromium at 7998de372, reading how src/captures.js redraws its list.

Any change to a capture key replaces every item of the list on Captures in this browser. The button that had focus is destroyed with its item, so focus falls back to the document body and a keyboard or screen-reader user is sent back to the top of the page with nothing said about where they are.

  • Where: src/captures.js:171-182, draw(): $("list").replaceChildren(...records.map((record) => item(record, when)));, with no focus handling; src/captures.js:206-219, the storage.onChanged listener that calls draw() for any capture key. Only an open editor defers the redraw (:172-175), and closing the editor removes its panel, and the focused Save or Cancel, the same way.
  • What happens: on the tenth item, pressing Delete (and confirming), Try now, Send for review, Hold back or Save writes to storage; the listener redraws and focus lands on the body. The same happens without any action of the person's while the background delivers waiting captures, each delivery updating a record.
  • Why it matters: a keyboard user must tab through the page again to reach the item they were working on, after every action; a screen-reader user loses their place. This fails WCAG 2.2 success criterion 2.4.3 (Focus Order), which the core holds itself to and the extension's pages should too.
  • Fix: give each action button a data-action and each item a data-id. Before replacing the list, note the focused element's id and action; afterwards focus the same control in the new item, or, when the item is gone (deleted, cleared), the next item's first button, or the list's heading. Alternatively, update changed items in place and remove deleted ones, so untouched items keep their focus. Announce the outcome through the existing #status live region, as send() already does.

Done when

  • A jsdom test (a new test/captures.test.mjs) draws a list of three captures, focuses Delete on the second, removes it through the store, and asserts that focus is on a control of the third item rather than on document.body.
Found on 2026-09-29 in a read-only review of postulo-chromium at 7998de372, reading how `src/captures.js` redraws its list. Any change to a capture key replaces every item of the list on *Captures in this browser*. The button that had focus is destroyed with its item, so focus falls back to the document body and a keyboard or screen-reader user is sent back to the top of the page with nothing said about where they are. - **Where:** `src/captures.js:171-182`, `draw()`: `$("list").replaceChildren(...records.map((record) => item(record, when)));`, with no focus handling; `src/captures.js:206-219`, the `storage.onChanged` listener that calls `draw()` for any capture key. Only an open editor defers the redraw (`:172-175`), and closing the editor removes its panel, and the focused *Save* or *Cancel*, the same way. - **What happens:** on the tenth item, pressing *Delete* (and confirming), *Try now*, *Send for review*, *Hold back* or *Save* writes to storage; the listener redraws and focus lands on the body. The same happens without any action of the person's while the background delivers waiting captures, each delivery updating a record. - **Why it matters:** a keyboard user must tab through the page again to reach the item they were working on, after every action; a screen-reader user loses their place. This fails WCAG 2.2 success criterion 2.4.3 (Focus Order), which the core holds itself to and the extension's pages should too. - **Fix:** give each action button a `data-action` and each item a `data-id`. Before replacing the list, note the focused element's id and action; afterwards focus the same control in the new item, or, when the item is gone (deleted, cleared), the next item's first button, or the list's heading. Alternatively, update changed items in place and remove deleted ones, so untouched items keep their focus. Announce the outcome through the existing `#status` live region, as `send()` already does. ## Done when - A jsdom test (a new `test/captures.test.mjs`) draws a list of three captures, focuses *Delete* on the second, removes it through the store, and asserts that focus is on a control of the third item rather than on `document.body`.
tiagoagueda added this to the 0.6.0 milestone 2026-10-01 10:47:16 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo-chromium#17
No description provided.