Plex player: a finger's tap on the picture only shows or hides the controls,
the hide timer starts with playback, a double-tap on a side third skips 10 s,
the seek bar is a 2.75rem touch-none hit area with its head shown on touch.
The row is play/pause, Back by position, Next only with a next item, mute,
time, tracks, a fixed ⋯ (settings, media info, stats, shortcuts, download)
and fullscreen; Stop, Restart and the ±10 s buttons are gone.
A started card shows the video card's Continue + Restart pair (one shared
MediaResumeButtons, beside the picture's button), and its menu lists the
restart; the player opens that item at 0 (fromStart). YouTube modal: the
description opens from an ⓘ, the queue arrows sit inside the screen.
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.
C1 moved the optimistic flip onto the key the page reads — and so made visible that nothing corrects
it: the resync only invalidated `plex-library`, so a failed toggle stayed "watched" beside its own
error toast, and the collection's other cached sort orders stayed stale after a good one. The
resync now takes this page's `detail` prefix too, as the Discover and person pages do for theirs.
The C1 test holds the request open, so it still pins the optimistic write on its own.
Deep-review R3. When 889d2acf put the sort into the collection detail's query key, two things on
the page did not follow it:
- The watched toggle's optimistic write kept the key's old spelling. setQueryData matches exactly,
so it landed on an entry nothing reads, and its resync only invalidates the library root — the
click succeeded server-side and changed nothing on screen (badge, icon, aria-label) until a
reload; a second click toggled it back. One `detailKey` now serves the read and the write.
- Every sort change was a key with no data yet, so the whole page — hero, grid, and the Sort control
with its open panel — was swapped for a spinner, and focus fell to <body>: key and direction could
not be set in one visit. The previous page now stays on screen while the new order loads, for the
SAME collection only (stepping to another must not show this one's hero meanwhile).
Verified on localdev (Rocky Collection): the toggle flips the poster; IMDb rating then Descending set
in one open panel, the hero never blanked, order reversed. 3 mutations, each red on its test.
The S4 Play-all wrapper had no test — a regression inverting the empty-queue
guard or dropping the notice would re-introduce the silent no-op it was added
to fix, and stay green. Adds a PlexCollectionView DOM test (spied api +
notify) asserting: a leaf queues as itself, a show expands to its episodes in
season->episode order, and an empty expansion surfaces plex.collection.playEmpty
instead of no-op'ing.