fix(e4): address deep-review findings — bookmark popstate, focus trap, touch a11y
Deep-review (6 dimensions, 3-verifier adversarial pass) surfaced 7 findings, all verified; fixed here. - NavigationProvider + history.ts (medium, correctness): the popstate handlers restored the open channel / Plex sub-view from history.state ONLY, but a fresh ?chan / ?v bookmark lives in the URL with no marker in state — so any overlay's programmatic history.back() (closing a modal/player) silently closed the bookmarked channel or collapsed the sub-view to the grid. Give onPop the same URL fallback the initializers already use (history.ts extracts a shared readView so seed and popstate can't diverge again). - plexCards SeasonCard (medium, a11y): the "In progress" season badge was a reveal-idle swap-badge → display:none on touch, and a season has no progress bar, so an in-progress season was indistinguishable from unwatched on touch. Moved it to the top-left (the toggle owns top-right) and made it always visible. - plexCards watched toggle (low, a11y / WCAG 1.4.1): watched state was color-only on touch (green vs white CheckCircle2). New WatchedIcon distinguishes by SHAPE — CheckCircle2 (watched) vs an empty Circle (not) — used by all three card toggles. - Modal.tsx (low, a11y): the focus trap leaked on Shift+Tab when focus rested on the panel container itself (Node.contains(self) is true, so the pull-back branch never ran). Guard now also checks `active === panel`. - index.css (low, a11y): .reveal-idle faded only on .group:hover though .reveal is revealed on hover AND focus-within → the badge overlapped the control on keyboard focus. Added the focus-within fade. - PlexBrowse.tsx (low, correctness): the ?v=playlist decoder accepted a non-integer id (Number.isFinite); tightened to Number.isInteger(n) && n > 0, matching the ?pl guard. Gate green (tsc, knip, lint 0-err, 247 vitest). Self-E2E (localdev): ?v=show and ?chan bookmarks survive a modal open+close (were collapsing/closing before); Modal Shift+Tab from the panel stays inside; in-progress season badge visible on touch; watched toggle shows an empty-circle-vs-check shape difference; 0 console errors.
This commit is contained in:
@@ -32,8 +32,12 @@ export function useHistorySubview<T>(
|
||||
open: (next: T) => void;
|
||||
back: () => void;
|
||||
} {
|
||||
const [view, setView] = useState<T>(() => {
|
||||
// Reload of an in-app entry: history.state._sub. Fresh bookmark: decode the ?<key> param.
|
||||
// Resolve the current entry's sub-view: history.state._sub for an in-app entry, else decode the
|
||||
// ?<key> URL param for a fresh bookmark (whose entry carries the param but no _sub), else root.
|
||||
// ⚠️ The seed AND the popstate handler must use THIS same fallback: a popstate can land on a
|
||||
// bookmark entry (URL-only, no _sub) — e.g. after an overlay's programmatic history.back() — and
|
||||
// reading history.state alone would wrongly collapse the bookmarked sub-view to root.
|
||||
const readView = (): T => {
|
||||
const fromState = window.history.state?._sub as T | undefined;
|
||||
if (fromState != null) return fromState;
|
||||
if (url) {
|
||||
@@ -42,13 +46,15 @@ export function useHistorySubview<T>(
|
||||
if (decoded != null) return decoded;
|
||||
}
|
||||
return root;
|
||||
});
|
||||
};
|
||||
const readViewRef = useRef(readView);
|
||||
readViewRef.current = readView;
|
||||
const [view, setView] = useState<T>(readView);
|
||||
useEffect(() => {
|
||||
// Back/Forward restores the sub-view from history.state (the browser restores each entry's URL too).
|
||||
const onPop = () => setView((window.history.state?._sub as T) ?? root);
|
||||
// Back/Forward restores the sub-view from the landed entry (state OR its restored ?<key> URL).
|
||||
const onPop = () => setView(readViewRef.current());
|
||||
window.addEventListener("popstate", onPop);
|
||||
return () => window.removeEventListener("popstate", onPop);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
return {
|
||||
view,
|
||||
|
||||
Reference in New Issue
Block a user