- The saved views, the view switcher, the year filter and the table's single-choice filter mark the
chosen row with MenuCheck, as DESIGN asks, not a trailing tick, a dot or a filled row; a contract
test holds every checkable menu row to it. keepSelectedPaint had no caller left and is gone.
- A combobox's panel carries its listbox role on an inner PopoverList (listInside), so the failed
load, Loading, No matches and the 'n more' line sit beside the options, not inside the listbox. The
Plex search box's Loading line likewise.
- The Plex sidebar's playlist picker offers Create only once the lists have loaded: over a failed load
it pushed the error and its Retry out at the first keystroke.
The 43 react-refresh/only-export-components warnings the gate let through meant an edit to a
provider (or AnchoredPopover, Toaster, plexDetailUi, …) could not be hot-swapped: Fast Refresh
reloads a module that also exports hooks or helpers.
- Each provider's contexts, the types its hooks return and the hooks themselves live in a sibling
context module (navigationContext.ts, channelsContext.ts, …); the provider file keeps the
component. loadAccountFilters moved next to the feed filters' defaults (feedFiltersInit.ts),
following navigationInit/playlistsInit.
- Helpers that sat in component files have their own modules: welcomeMode, downloadTitle,
plexPlayable, channelSync, ui/anchoredPopoverHooks, ui/pageToolbarSlot, ui/pagerContent,
ui/toastLevels, ui/overlayOrder, ui/tableAlign, lib/plexDetail; the cells of the channel columns
and the registry's page wrappers are components files of their own (channelCells, modulePages).
- usePersistedTab, an alias of useAccountPersistedState, is gone; its callers use the original.
npm run lint is now eslint --max-warnings 0, so a warning fails the gate like an error does.
A feed card cost ~10 Tab stops, so the tenth card sat ~135 Tabs from the top of the page, and the
only keyboard focus ring (a 60% accent outline on the thumbnail) all but vanished on the dark glass.
First slice of the E11 keyboard layer (S4a); Home, Plex and the tables follow in S4b/S4c.
- `lib/listNav`: one window listener over every registered list, walked in document order. `j`/`k`
step item to item (on into the next list — owner decision D4), the arrows move around a grid, Enter
clicks the item's own `data-nav-open` element, so it opens exactly like a click. An item root is
never a Tab stop: it gets `tabindex=-1` only while the keyboard is on it, and keeps it until the next
item takes it, so the player's focus restore lands back on the card. The first `j` takes the first
card you can see, not the top of the list.
- `VirtualGrid` takes part with `keyboardNav` (column count + scrolling an unmounted row in);
`VirtualFeed` turns it on, so the feed, the library and the channel page all have it.
- Keymap: two-step sequences (`g h`) whose pending step belongs to the scope that started it; the
page-level scopes (`page`, `search`, `list`) are quiet while a layer that owns the keyboard is open,
enforced in `dispatch`. A test pins the owner's rule that none of them needs a modifier on the
Hungarian layout.
- `g h/f/c/p/x/n/m/d/s/,` jump to a module this account can reach; `s` focuses the header search box
and selects the old query; Escape in it hands the keyboard back to the page (Plex's typeahead spends
its first Escape on its panel).
- Hover popovers are passive layers: an open tooltip still takes Escape first but no longer silences
the page keys.
- Focus ring (owner decision D2 = B): solid 2px accent around the whole item; the app-wide button/
link/row ring goes from 60% to solid too. The item keeps a transparent resting outline because the
video card transitions `all` — without it every `j` flashed a 3px white ring (measured in Chrome).
- Help sheet: the new keys, in a fixed order after the surface's own; a `g` chord only for a reachable
module; opened over a player or dialog it leaves out the page-level keys and the context-menu key,
which nothing can reach there (the latter pre-existing since E10).
- CLAUDE.md: a canonical-patterns row for list/grid keys (`useListNav` + `navItemAttrs` +
`data-nav-open`, not a private arrow-key handler).
Ships the E5 sprint stack (S1–S7): field-scoped Plex search + typeahead, cast &
crew pages, the HU-overlay Hungarian metadata + language-aware serving, the
IMDb/audience rating split, the toolbar relocation, and the narrow/mobile header
fix — plus the underlying Plex-module refactors (feedquery/plex.cards/plex.query
extraction, db.query→2.0 select).
- VERSION 0.65.2 → 0.66.0
- releaseNotes.ts: 0.66.0 user-facing entry
- prettier-format the E5-touched frontend files (release hygiene)
Merged main (0.65.2 download-gc hotfix) in first; no code conflicts (E5 is
Plex-only, the hotfix was notifications/downloads).
- Reset `hi` on Escape and gate `activeOptionId` on `open`, so a dismissed
panel never exposes an aria-activedescendant pointing at an unmounted option,
and no stale highlight flashes on reopen (live-verified: aria-activedescendant
is null when nothing is highlighted / the panel is closed).
- Strengthen the reopen test: setQ now writes back to the mock provider's q, so
the test asserts a FRESH suggest query fires on retype (q="matrixx"), not just
that a cached listbox re-appears.
- Add a combobox-ARIA test: role=combobox, aria-expanded toggles with the panel,
and aria-controls associates the input with the listbox it opens.
Suite 171 green; typecheck + eslint clean.
- Decouple the typeahead's dismiss from real focus: a separate `dismissed`
flag (set on Escape/pick, cleared on typing/refocus) replaces manually
clearing `focused`, so closing the panel no longer wedges it shut while the
input keeps DOM focus — typing again re-opens it (verified in-browser).
- Scroll the highlighted option into view during arrow-key nav (listRef +
data-idx + scrollIntoView), instead of leaving listRef unused.
- Wire the input as a combobox for screen readers: role=combobox,
aria-autocomplete/expanded/controls/activedescendant + per-option ids.
- Drop the redundant client-side .slice (the request already caps at limit=10).
- SearchBar gains an `inputProps` passthrough for the combobox ARIA.
- Add PlexSearchBox.dom.test.tsx (4 tests): per-scope pill options, suggestion
render + keyboard pick→jumpTo, and the Escape→retype reopen regression.
Reviewer's scrollbar-blur concern was a false positive — the listbox container
is not focusable, so a mousedown on it never blurs the input.