fix(ui): a page's sub-tab is remembered per browser tab, not forever

Settings, Configuration, Statistics and Users kept the last sub-tab in localStorage, so every
fresh tab opened Configuration on "Plex" as if that were its home. useTabPersistedState keeps it
in sessionStorage (account-scoped): a reload and a trip to another page keep it, a new tab starts
on the first sub-tab. The pre-selects (a Review link, the Google-link return) write the same store;
a value an older build left in localStorage is cleared on first read.
This commit is contained in:
2026-10-02 00:49:46 +02:00
parent be156fc52b
commit e4c80fb7bb
10 changed files with 117 additions and 15 deletions
+57
View File
@@ -42,6 +42,8 @@ export const LS = {
channelDiscoveryCardSize: "siftlode.channelDiscoveryCardSize",
channelsTable: "siftlode.channelsTable",
channelDiscoveryTable: "siftlode.channelDiscoveryTable",
// A page's sub-tab: sessionStorage, per browser tab (useTabPersistedState), like adminUsersTab and
// statsTab below.
settingsTab: "siftlode.settingsTab",
configTab: "siftlode.configTab",
auditTable: "siftlode.auditTable",
@@ -174,6 +176,61 @@ export function useAccountPersistedState(
return [value, set];
}
// --- a page's sub-tab, per browser tab ---------------------------------------------------------
// Which sub-tab a page was left on (Settings, Configuration, Statistics, Users) is kept for THIS browser
// tab only — sessionStorage, account-scoped: it survives a reload and a trip to another page, and a new
// tab opens the page on its first sub-tab (the owner, E31: a position remembered forever in
// localStorage made Configuration open on "Plex" in every fresh tab, as if that were its home).
/** The sub-tab stored for this browser tab, or null. */
function getTabRaw(base: string): string | null {
const k = accountKey(base);
try {
return k ? sessionStorage.getItem(k) : null;
} catch {
return null;
}
}
/** Pre-select a page's sub-tab before navigating there (a "Review" link, the Google-link return). */
export function setTabRaw(base: string, value: string): void {
const k = accountKey(base);
if (!k) return;
try {
sessionStorage.setItem(k, value);
} catch {
/* ignore */
}
}
/** `useAccountPersistedState` for a page's sub-tab, kept per browser tab (see above). */
export function useTabPersistedState(
base: string,
fallback = "",
): [string, (value: string) => void] {
const [value, setValue] = useState<string>(() => {
// Before E31 the position lived in localStorage; a value left there would never be read again.
const old = accountKey(base);
if (old) {
try {
localStorage.removeItem(old);
} catch {
/* ignore */
}
}
return getTabRaw(base) ?? fallback;
});
const set = useCallback(
(next: string) => {
setValue(next);
setTabRaw(base, next);
},
[base],
);
return [value, set];
}
/** A per-account persisted JSON OBJECT: `patch(partial)` merges + saves, unknown/missing keys fall
* back to `defaults` (via readAccountMerged) so adding a field later is safe. Used for bundles of
* related per-account settings (e.g. the Plex player prefs: volume, offsets, seek steps, sub style). */