fix(views): seed the default view only after the tab-account pins

The S2 seed effect ran on the saved-views query's first resolve, but that
query is ungated and — since SavedViewsProvider mounts above <App>, which pins
the tab-account only after /api/me — resolves before the account is known on a
fresh tab. It then evaluated canSeedDefault() against a null account (which
reports 'stored, do not seed') and burned its one-shot guard, so the starred
default was never applied until a reload: exactly the fresh-tab miss S2 set out
to fix. Gate the effect on the reactive account id so it defers instead of
consuming the guard, and re-fires when the account pins. Covers feed + plex.

Adds a dom test for the defer-then-seed-on-pin path the old test masked by
pre-pinning the account.
This commit is contained in:
2026-08-24 22:09:39 +02:00
parent 06b85586e6
commit 852b227ea2
2 changed files with 28 additions and 4 deletions
+11 -2
View File
@@ -24,6 +24,7 @@ import { filtersToParams, hasFilterParams } from "../lib/urlState";
import { pickDefaultToSeed, preferSticky } from "../lib/savedViews";
import { notifyActionError } from "../lib/actionErrors";
import { accountKey, LS } from "../lib/storage";
import { useActiveAccountId } from "../lib/activeAccount";
import { useConfirm } from "./ConfirmProvider";
import { useNavigation } from "./NavigationProvider";
import { useFeedFilters, useFeedFiltersActions } from "./FeedFiltersProvider";
@@ -172,10 +173,18 @@ export function SavedViewsProvider({ children }: { children: ReactNode }) {
// login effect (on a fresh browser the feed could seed DEFAULT_FILTERS and never re-apply until a
// reload) — and it gives Plex the default it previously never applied at all. Per-source guard so a
// 60s background refetch never re-seeds over the filters you're editing.
//
// WAIT FOR THE ACCOUNT PIN. This provider mounts above <App>, which pins the tab-account only after
// /api/me — but viewsQuery is ungated, so on a fresh tab it resolves FIRST, while the account is null.
// canSeedDefault() reads the per-account stored-filters key, which is indeterminate (→ "assume stored")
// with no account, so evaluating it too early would decline to seed AND burn the one-shot guard, never
// re-running once the account pins. Gating on the reactive account id makes the effect re-fire on the
// pin (useActiveAccountId subscribes), and defers consuming the guard until canSeedDefault is real.
const accountId = useActiveAccountId();
const seededRef = useRef<Set<ViewSource>>(new Set());
useEffect(() => {
const data = viewsQuery.data;
if (!data || seededRef.current.has(source)) return;
if (!data || accountId == null || seededRef.current.has(source)) return;
seededRef.current.add(source);
const def = pickDefaultToSeed(data, cfg.canSeedDefault());
if (!def) return;
@@ -184,7 +193,7 @@ export function SavedViewsProvider({ children }: { children: ReactNode }) {
// cfg is read fresh on this resolve and the per-source guard makes the body run once, so cfg
// (which re-memoizes on every filter edit) is deliberately kept out of the deps.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [viewsQuery.data, source]);
}, [viewsQuery.data, source, accountId]);
const currentKey = cfg.keyOf(cfg.filters);
const activeView = useMemo(