docs: record the second Phase 5 pass and what it corrected
Build & publish Arch package / arch-package (push) Successful in 1m57s
CI / check (push) Successful in 2m19s
Search index maintenance / maintain-index (push) Successful in 6s
CI / e2e (push) Failing after 3m2s

Three of a11y.md's findings describe a build that no longer exists —
one fixed by a phase that was not about it, one whose stated mechanism
stopped being true when Phase 4 bundled the icons, and one that
reproduces as a different shape. The generalisation is that a finding
has a date as well as a magnitude and a mechanism.

Also records the two bad versions of the duplicate-shelf rule that the
*existing* tests caught, the eleven e2e specs that landing on Home
broke and the one of them that was a real bug, and the second CI e2e
failure on a commit that changed no application code.
This commit is contained in:
2026-08-12 11:44:58 -04:00
parent 862e8a0468
commit 63d11c3f9c
3 changed files with 314 additions and 1 deletions
@@ -1687,6 +1687,136 @@ And two things about CI, neither mine and both pre-existing on
---
### Phase 5 — the second pass: the dialogs, the menu, the ARIA tail, and Home
Item 3 of the four as one landing, plus the first of the smaller ones.
Every finding was reproduced in the running app before it was fixed,
and two of them changed shape when it was.
- **`a11y.4` / `a11y.16` — five hand-rolled dialogs are `wa-dialog`s.**
Split by shape rather than by owner: the three that only ask a
question (the autotag warning, the leave-as-is confirmation, the
remove-library confirmation) are `confirmAction()` calls, and the two
carrying input (paste URL, MusicBrainz search) are `<wa-dialog>`s in
place. Verified in the app: the native dialog reports `:modal`, focus
lands in the first field, Escape closes it and the view state follows
through `@wa-hide`. **`autotag-view`'s last document keydown listener
died with them** — it existed only for Escape, because its dialogs
could not close themselves.
- **`a11y.3` — the context menu has a keyboard model.** `MenuKeyboard`
in `context-menu-controller.ts`: focus the first item, Arrow/Home/End
(wrapping), Enter/Space, Escape/Tab, focus restored to the row.
Shift+F10 and the ContextMenu key open it from a focused row in all
six hosts. It is standalone rather than part of the controller
because `playlist-view` renders a menu without the controller.
- **Three lists had no focused row to open it from** — the queue panel
and both playlist detail views — and gained a roving tab stop
(`utils/roving-rows.ts`). `track-list` keeps its own.
- **The ARIA tail**: `aria-sort` on the column headers (`a11y.9`),
`role=listbox`/`option` on the four selectable grids with the invalid
`aria-selected`-on-`button` dropped (`a11y.13`), live regions on the
four silent async surfaces (`a11y.12`), the `rem` type scale
(`a11y.19`), `job-indicator`'s unmanaged `role="dialog"` (`a11y.17`)
and its colour-only failure dot (`a11y.23`).
- **`H-8` / `H-9` — the app lands on Home, and Home is worth landing
on.** The missing-art placeholder draws the letter tile the other two
grids draw, and a shelf that repeats the one above it is suppressed
in `backend/home` — the same rule as omitting an empty one.
#### Where the plan was wrong — the second pass
Eight things, and three of them are the audit describing a build that
had already moved:
- **`a11y.12`'s first bullet was fixed two phases ago, twice.** It names
`config-page`'s private toast as having no `role="status"`. Phase 3
deleted that toast, and the surface that replaced it —
`notification-host` and `inline-notice` — has had `role="status"
aria-live="polite"` from the day it was written. Two of the finding's
five bullets were closed by a phase that was not about accessibility.
- **`H-9`'s stated mechanism is not why the card is invisible.** The
audit says the placeholder "has no background". It has one:
`--yj-bg-surface`, which is *almost exactly the page colour*, holding
a `wa-icon` at `--yj-text-tertiary`. And the icon has rendered at all
only since Phase 4 bundled the icon set — before that the fallback
was a CDN fetch, so "renders as nothing" was literally true offline
and is now merely nearly true. The fix is the same; the reason it was
worth checking is that "no background" would have been fixed by one
line that changed nothing visible.
- **`H-9`'s duplicate-shelf half does not reproduce as stated, and the
obvious rule breaks a small library.** The audit says "all three
shelves show the same seven albums". On the fixture library there are
five shelves and the duplication is one *pair* — "On repeat" is
"Pick up where you left off" reordered. The first rule I wrote
(suppress at two-thirds overlap with the shelf above) **collapsed a
four-album library to a single shelf**, and the second (guarded by a
fixed shelf size) let an 11-album library keep three identical
shelves while a 13-album one lost them. Both were caught by the
*existing* Go tests, not by the one written for the change. The rule
that survives is "a repeat is a fault only if a different row was
possible" — the shelf must not be showing the whole library.
- **Landing on Home broke eleven e2e specs, and one of them was an app
bug.** Nine assumed the track list is the first thing on screen. One
failed because Home's shelves name the same artists as
`artists-view`, and every primary view stays in the DOM — so an
unscoped `getByText().first()` matched a card on a `.view-hidden`
page. And one was real: **`getByRole('button', {name: 'Shuffle'})`
resolved to two elements**, because Home's page-header action and the
transport's shuffle mode had the same accessible name. They were
never on screen together before; a cached Home is in the
accessibility tree from the first paint. It is "Shuffle suggestions"
now.
- **`a11y.19` and `a11y.20` are one finding, and the second one wins.**
Converting the type scale to `rem` works — verified at a 24px root, a
track cell goes 12px to 18px — and does *not* reach the four
virtualized lists, whose rows are a hardcoded px height duplicated as
the layout's `_itemSize` and carry `contain: strict`. Measured: the
row stays 33px while its text grows to 18px, so larger text crops it.
Left unfixed and documented in the token file, which is what a11y.20
itself asks for: deriving `_itemSize` from a measured row is a change
to the scroll maths of four lists, not to a type scale.
- **Two of the three things that made the menu work are invisible to a
component test.** `wa-dropdown-item` sets its `role` in its own first
update, so querying by role at the host's `updateComplete` finds no
items; and `focus()` on a `wa-popup` that has not positioned itself
is a silent no-op. Both produced a menu that opened and refused to
take focus, and both were found by driving the real app — a component
test against hand-built markup passes either way, which is why the
e2e spec exists.
- **A backtick in a comment inside a `css` template literal ends the
literal.** The skill warns about this. I did it twice in one session
anyway, and the second time every test file in the suite failed to
import, which reads like anything except a stray backtick.
- **The `wa-dialog` migration removed state that had a use.**
`config-page`'s `isRemoving` had no reader once the dialog owned the
spinner. Rather than delete it, `removingLibraryId` now means "which
row is busy" and the row says "Removing…" — the removal is a backend
call of unknown length, and moving the confirmation out of the page
had quietly removed the only feedback that it had started.
#### Not done, and still worth doing (after the second pass)
Item 4's remaining smaller items, each independently landable: the
album page's primary action and its unexplained ✓ badges (`H-13`), the
`?` shortcuts overlay (and with it keyboard seeking from a focused
row), Settings reordered with a Playback section (`H-22`) together with
`a11y.1` (`config-section`'s disclosure header is a bare `<div @click>`,
so every setting is behind a control that cannot be tabbed to) and
`a11y.2` (the Downloads tabs), an Album column in the track list
(`H-15`), and `cover-grid`'s dead album dropdown (`perf.p2`).
Three inherited items are unchanged: `tracklist.delete` (below), the
header search box on `smart-playlist-details`, and keyboard seeking
from a focused row.
**`tracklist.delete` was deliberately not built.** It needs a "remove
from library" that does not exist *and* a decision about what it
removes. Neither answer is currently right: removing the row is a lie
unless it also excludes the path, since the next scan brings it back,
and removing the file is a delete-your-music button one keystroke from
a focused row. The honest interim is to stop advertising the binding in
Settings; that is not done yet either.
## Phase 6 — Explore starts the conversation
The only phase that adds rather than repairs.