Writing the Phase 6 end-to-end journey suite turned up seven accessibility gaps in the client. They surfaced because that suite forbids data-testid and addresses the UI the way a screen reader does, so a control a locator cannot name is a control a screen reader cannot name either.
None was patched, because Phase 6 is a pure test addition and changes no production file. Every journey works around its gap with a legitimate text or role locator, so fixing these will not break the suite. Several locators get simpler.
Ordered by severity.
-
A book card cannot be reached by keyboard at all. client/features/library/components/BookCard.tsx renders the card as a div with onClick and onContextMenu. No role, no accessible name, no tab stop, so a keyboard user cannot open a book. Wants a button, or role="button" with tabIndex={0} and a key handler, named from the book title. Journeys reach it with getByText(title, { exact: true }).first().
-
Two controls share the accessible name "Next section". client/features/reader/components/ChapterRail.tsx and client/features/reader/components/SectionTapZones.tsx both render a button with that name, wired to the same goNext callback. Assistive technology announces the same control twice, and Playwright's strict mode rejects it outright until a journey disambiguates. One of the two wants a distinct name or none at all.
-
The book context menu has no menu semantics. client/features/library/dialogs/BookContextMenu.tsx is a plain div of button elements. No menu role, no menuitem roles, no arrow-key navigation, so assistive technology sees a pile of buttons rather than a menu.
-
The feedback form's textareas are not label-associated. FeedbackForm.tsx puts each label as a plain sibling of its textarea, with no for and id pair and no aria-label, so neither field has an accessible name. Journeys locate them by placeholder.
-
The Rename dialog's labels are not wired to their inputs. Title and Subtitle both lack htmlFor with a matching id, so getByLabel finds neither and a screen reader announces two unlabelled text fields. The journey selects them positionally inside the dialog instead.
-
The Delete dialog's confirmation input has no label. The field where a reader types the confirmation word has no label element and no aria-label.
-
The library toolbar's icon-only controls have no accessible name. The search, grid, and list toggles in LibraryToolbar carry a title attribute, which is a tooltip rather than a name. They want aria-label. The search journey uses the keyboard shortcut instead of the button because of this.
Separately, and not an accessibility issue: the packaged Electron app logs two Content Security Policy violations on boot, an inline script blocked by script-src 'self' and a data-URI font blocked by font-src. The app still renders. Worth its own look.
Writing the Phase 6 end-to-end journey suite turned up seven accessibility gaps in the client. They surfaced because that suite forbids
data-testidand addresses the UI the way a screen reader does, so a control a locator cannot name is a control a screen reader cannot name either.None was patched, because Phase 6 is a pure test addition and changes no production file. Every journey works around its gap with a legitimate text or role locator, so fixing these will not break the suite. Several locators get simpler.
Ordered by severity.
A book card cannot be reached by keyboard at all.
client/features/library/components/BookCard.tsxrenders the card as adivwithonClickandonContextMenu. No role, no accessible name, no tab stop, so a keyboard user cannot open a book. Wants abutton, orrole="button"withtabIndex={0}and a key handler, named from the book title. Journeys reach it withgetByText(title, { exact: true }).first().Two controls share the accessible name "Next section".
client/features/reader/components/ChapterRail.tsxandclient/features/reader/components/SectionTapZones.tsxboth render a button with that name, wired to the samegoNextcallback. Assistive technology announces the same control twice, and Playwright's strict mode rejects it outright until a journey disambiguates. One of the two wants a distinct name or none at all.The book context menu has no menu semantics.
client/features/library/dialogs/BookContextMenu.tsxis a plaindivofbuttonelements. Nomenurole, nomenuitemroles, no arrow-key navigation, so assistive technology sees a pile of buttons rather than a menu.The feedback form's textareas are not label-associated.
FeedbackForm.tsxputs eachlabelas a plain sibling of itstextarea, with noforandidpair and noaria-label, so neither field has an accessible name. Journeys locate them by placeholder.The Rename dialog's labels are not wired to their inputs. Title and Subtitle both lack
htmlForwith a matchingid, sogetByLabelfinds neither and a screen reader announces two unlabelled text fields. The journey selects them positionally inside the dialog instead.The Delete dialog's confirmation input has no label. The field where a reader types the confirmation word has no label element and no
aria-label.The library toolbar's icon-only controls have no accessible name. The search, grid, and list toggles in
LibraryToolbarcarry atitleattribute, which is a tooltip rather than a name. They wantaria-label. The search journey uses the keyboard shortcut instead of the button because of this.Separately, and not an accessibility issue: the packaged Electron app logs two Content Security Policy violations on boot, an inline script blocked by
script-src 'self'and a data-URI font blocked byfont-src. The app still renders. Worth its own look.