fix(feed): scope saved filters + default view per account

Feed filters and the default-saved-view mirror lived under shared localStorage keys, so one
account's view (incl. its starred default) leaked into another account signed into the same
browser — visible with per-tab accounts as a fresh account showing the previous account's
'N active' filters instead of a clean default.

Key both by the tab's active account (siftlode.filters.<id> / siftlode.defaultViewFilters.<id>);
load a tab's filters from its own account on login/switch/add, falling back to defaults when the
account isn't known yet. A share link's filters still win and are persisted to the account. The
old shared keys are simply no longer read (a one-time reset to defaults on the first load after
this change; migrating them would risk re-leaking across accounts).
This commit is contained in:
npeter83 2026-07-02 00:37:23 +02:00
parent f32c3f08dc
commit 59de0ffdfd
3 changed files with 63 additions and 22 deletions

View file

@ -17,10 +17,10 @@ import {
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { api, type FeedFilters, type SavedView } from "../lib/api";
import { api, getActiveAccount, type FeedFilters, type SavedView } from "../lib/api";
import { filtersToParams, shareUrl } from "../lib/urlState";
import { notify } from "../lib/notifications";
import { LS, readJSON, writeJSON } from "../lib/storage";
import { accountKey, LS, readJSON, writeJSON } from "../lib/storage";
import { useConfirm } from "./ConfirmProvider";
// Compact, order-independent signature of a filter set (reuses the share serializer, which
@ -28,15 +28,19 @@ import { useConfirm } from "./ConfirmProvider";
// saved view that matches the feed's current filters.
const keyOf = (f: FeedFilters) => filtersToParams(f).toString();
/** The default view's filters, or null. Read synchronously on load (App.loadInitialFilters)
* from a localStorage mirror the widget keeps in sync avoids a flash before the query loads. */
/** The default view's filters, or null. Read synchronously on load (App.loadAccountFilters) from a
* per-account localStorage mirror the widget keeps in sync avoids a flash before the query loads.
* Per-account so one account's default view never seeds another account's feed. */
export function loadDefaultViewFilters(): FeedFilters | null {
return readJSON<FeedFilters | null>(LS.defaultViewFilters, null);
const key = accountKey(LS.defaultViewFilters, getActiveAccount());
return key ? readJSON<FeedFilters | null>(key, null) : null;
}
function syncDefaultMirror(views: SavedView[]): void {
const key = accountKey(LS.defaultViewFilters, getActiveAccount());
if (!key) return;
const def = views.find((v) => v.is_default);
writeJSON(LS.defaultViewFilters, def ? def.filters : null);
writeJSON(key, def ? def.filters : null);
}
export default function SavedViewsWidget({