From f32c3f08dc88f440848ea4a21f381e5d7d3af169 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Thu, 2 Jul 2026 00:16:51 +0200 Subject: [PATCH] fix(auth): pin each tab to its account so cross-tab changes don't leak in MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A tab that never explicitly picked an account rode the session's default, so when ANOTHER tab changed that default (adding or switching an account), this tab silently swapped identity on its next refetch — the account chip updated while the feed/saved-views lagged until reload. - Pin every tab to whatever account it first loaded as (write the sessionStorage override on load when none is set), so its requests always carry its own account header and cross-tab default changes can't reach it. - 'Add account' now clears this tab's pin before the Google redirect, so on return the tab adopts the freshly-added account (and pins that) — the current tab switches to it while other tabs keep their own, matching the Gmail add-account model. --- frontend/src/App.tsx | 18 +++++++++++++++++- frontend/src/components/NavSidebar.tsx | 4 ++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index b03b2e9..58c26ae 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,7 +1,14 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { api, HttpError, setUnauthorizedHandler, type FeedFilters } from "./lib/api"; +import { + api, + getActiveAccount, + HttpError, + setActiveAccount, + setUnauthorizedHandler, + type FeedFilters, +} from "./lib/api"; import { setLanguage, isSupported, type LangCode } from "./i18n"; import { applyTheme, @@ -367,6 +374,15 @@ export default function App() { refetchInterval: (query) => (query.state.data ? 60_000 : false), }); + // Pin this tab to whatever account it first loaded as. Without this, a tab that never picked + // an account (it was riding the session's default) would silently swap identity when ANOTHER + // tab changes the default — e.g. that other tab adds/switches an account. Once pinned, this + // tab always sends its own account header, so cross-tab changes can't reach it. An explicit + // switch on THIS tab sets the override itself (so this no-ops then). + useEffect(() => { + if (meQuery.data && getActiveAccount() == null) setActiveAccount(meQuery.data.id); + }, [meQuery.data?.id]); + // Once signed in, drop any leftover query string from the pre-login flow (e.g. // ?access=requested, ?verify, ?reset). The logged-in app reads nothing from the URL, so the // address bar stays clean. Gated on auth so the logged-out Welcome page still reads its params. diff --git a/frontend/src/components/NavSidebar.tsx b/frontend/src/components/NavSidebar.tsx index 578a7ec..db5dde3 100644 --- a/frontend/src/components/NavSidebar.tsx +++ b/frontend/src/components/NavSidebar.tsx @@ -402,6 +402,10 @@ export default function NavSidebar({