From 9e2b085f25c03f6d4fba9384d11d7392f82ea644 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Tue, 11 Aug 2026 15:49:17 +0530 Subject: [PATCH] fix(appMode): also preserve a fresh cross-tab hint through the AuthProvider hydrate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to #31316. That PR taught `hydrateAndResolveAppMode` to skip its boot-time `writeAppMode` when a session tuple already exists — fine for a returning tab or an in-tab toggle. But the *newly-opened* tab path (cmd+click from an AI tab, or any browser-issued "new tab in the same context") starts with an empty session tuple and a *fresh* `omAppModeHint` in localStorage. My previous fix passed straight through that guard and wrote `DEFAULT_APP_MODE` to the session tuple, which then satisfied `useResolvedAppMode`'s "valid session" check — the resolver returned early and never consulted the hint. Result: the sibling tab's active mode failed to carry, breaking exactly the cross-tab-inheritance behaviour the hint exists for. Extend the guard to skip the write when either signal is present: 1. A session tuple this tab already owns. 2. A fresh cross-tab hint (`isAppModeHintFresh(readAppModeHint())`). `useResolvedAppMode` already handles the hint-adoption path correctly once the tuple isn't in the way — it writes the hint's mode if it's registered, waits for the registry when it isn't, and falls through cleanly if the hint isn't fresh. Fresh boot with neither signal is unchanged. Fixes the following Collate Playwright regressions that #31316 alone did not resolve: - AppModeResolver.spec.ts:137 (new tab in the same browser inherits AI mode via the cross-tab hint) - AppMode.spec.ts:155 (uninstall → refresh → reinstall → refresh lands user in default mode) - AppModeSidebarState.spec.ts:139 (sub-nav resets when the user re-enters AI mode) A stale (TTL-expired) hint continues to fall through to the default — `isAppModeHintFresh` returns false and the guard doesn't fire — so `AppModeResolver.spec.ts:227` ("stale hint does not carry AI") remains green. --- .../Auth/AuthProviders/AuthProvider.tsx | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx index fc2c2bf56c53..ee31175f72a0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx @@ -65,6 +65,8 @@ import { import { useApplicationStore } from '../../../hooks/useApplicationStore'; import { clearAppMode, + isAppModeHintFresh, + readAppModeHint, readAppModeSession, resolveEffectiveAppMode, resolveInitialAppMode, @@ -162,16 +164,27 @@ const hydrateAndResolveAppMode = async (user: User): Promise => { const appDefault = translateWireMode(appConfig?.defaultAppMode ?? null); setAppDefaultMode(appDefault); - // If this tab already has a session tuple (a manual toggle earlier in - // this tab, or a reload of one) leave it alone. `useResolvedAppMode` - // is the authoritative source and treats the tuple as the highest- - // priority signal once persona information is available — writing - // here would clobber a valid in-tab choice with the boot-time best- - // effort resolve (which cannot yet see persona) and break "toggle to - // AI, then reload" for users who did not tick "remember". + // Skip the boot-time write when this tab already has a signal that + // `useResolvedAppMode` will resolve authoritatively — the resolver is + // the single source of truth once it has persona + registry + // information, and writing to the session tuple here poisons the + // subsequent resolve. Two signals count: + // + // 1. A session tuple this tab already owns (returning tab, or a + // manual toggle earlier in this tab). + // 2. A fresh cross-tab `omAppModeHint` — the mechanism by which a + // sibling tab's active mode carries into a newly-opened tab. + // Once we write `DEFAULT_APP_MODE` here, the resolver's session- + // tuple check is satisfied by our write and it never consults + // the hint, so a cmd+click from an AI tab silently boots the new + // tab into Classic. if (readAppModeSession()?.mode) { return; } + const hint = readAppModeHint(); + if (isAppModeHintFresh(hint) && hint?.mode) { + return; + } const userPref = derivePreferencesFromList(prefsRes.preferences).appMode ?? null;