fix(auth): a tab acts only as the account it was built for, and the review's other findings

A tab names the account it was claimed for on every request, beacon and upload
(X-Siftlode-Expect-Account); current_user and /auth/logout refuse a mismatch with
401 account_changed and keep the session, so a tab left showing the account
signed in before reloads instead of writing to the new one.

/auth/login clears a connect marker left by an abandoned connect, and a link
completes only on a session that still holds. The prefs merge reads the row
under a lock. A tab's sessionStorage records its own owner. A page the account
loses is given up, address bar and remembered page included. The panel layouts
are preferences; the storage contract also refuses a key handed to the Storage
API or to usePref outside its kind. A refused sign-in as someone else and a
failed sign-out say so. The chromium-only e2e specs are in the gate register,
the member walk waits for its requests, and devices.spec checks the save itself.
This commit is contained in:
2026-10-21 01:05:12 +02:00
parent 28b77fed1e
commit 94418a087e
39 changed files with 576 additions and 78 deletions
+2 -2
View File
@@ -28,7 +28,7 @@ import {
// stable useCallback setters, one-line hooks; composed in main.tsx around <App/>).
//
// scope / show / sort / filters / q are per-account persisted (localStorage) and seeded from it at mount
// (BootGate pins the tab's account first; an account switch reloads). `playlistOpen` is an ephemeral
// (BootGate claims the local state for the account first; a different account reloads the tab). `playlistOpen` is an ephemeral
// hand-off (the rail sets it, PlexBrowse consumes it into a history subview then clears it). The Plex
// search term is deliberately its OWN state, NOT the feed's `filters.q`, so a Plex search never leaks
// into (and is never greeted by a stale value from) the feed.
@@ -84,7 +84,7 @@ function readPlexFilters(): PlexFilters {
return EMPTY_PLEX_FILTERS;
}
}
// The base is this account's stored view (BootGate pins the account before this provider mounts), so a
// The base is this account's stored view (BootGate claims the local state before this provider mounts), so a
// PARTIAL link — one carrying only the scope, say — keeps the stored sort, show and filters for the rest.
// `present` records which dimensions the link actually carried: only those are persisted (below).
type PlexSeedPresent = { scope: boolean; sort: boolean; show: boolean; filters: boolean };