From e7ea2e92f27a71655e55bf51465bf6ad277cf867 Mon Sep 17 00:00:00 2001 From: zipg Date: Thu, 10 Sep 2026 17:44:06 +0800 Subject: [PATCH] fix(object-browser): avoid refresh on tab restore --- .../src/components/objects/ObjectBrowser.vue | 11 +++++----- .../ObjectBrowserScaffoldRefresh.spec.ts | 21 ++++++------------- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/apps/desktop/src/components/objects/ObjectBrowser.vue b/apps/desktop/src/components/objects/ObjectBrowser.vue index 0a0465c734..d6ea061774 100644 --- a/apps/desktop/src/components/objects/ObjectBrowser.vue +++ b/apps/desktop/src/components/objects/ObjectBrowser.vue @@ -2859,14 +2859,13 @@ async function loadObjects(options?: { allowCached?: boolean; preserveExistingRo const cached = options?.allowCached ? getCachedObjectBrowserRowsForScaffold(request.scope) : undefined; if (cached) { - // Scaffold the last-known rows so returning to this tab shows them instantly - // instead of flashing an empty list. A fresh entry (< TTL) is authoritative on - // its own; a stale one is presented and then revalidated in the background. + // Restore the last-known rows when remounting this tab. The cached list is + // authoritative for navigation restores, including entries older than the + // freshness TTL; re-querying here makes every tab switch look like a refresh. + // Explicit refresh and metadata invalidation still bypass this branch. applyObjectBrowserRows(cached.rows); finishOnce(); - if (!cached.stale) return; - scaffoldRefresh = true; - refreshingObjects.value = true; + return; } else { // No scaffold: first load in this scope, cache invalidated by a DDL mutation, // or the caller wants a true reload. If the caller explicitly asked to keep the diff --git a/apps/desktop/src/components/objects/__tests__/ObjectBrowserScaffoldRefresh.spec.ts b/apps/desktop/src/components/objects/__tests__/ObjectBrowserScaffoldRefresh.spec.ts index 6c242a05ea..cccbf4e58e 100644 --- a/apps/desktop/src/components/objects/__tests__/ObjectBrowserScaffoldRefresh.spec.ts +++ b/apps/desktop/src/components/objects/__tests__/ObjectBrowserScaffoldRefresh.spec.ts @@ -17,25 +17,16 @@ function functionBody(name: string): string { } describe("ObjectBrowser scaffold refresh race", () => { - // Scope A hits stale cache and starts a background revalidate (refreshingObjects = true); - // before it settles, the user switches to scope B which hits fresh cache and early-returns. - // A's finally() can no longer run (the load guard's epoch moved on), so the flag must be - // cleared by the newest request (B) at entry — otherwise the toolbar icon spins forever. - it("resets the transient refresh flags at entry, before the cache decision", () => { + it("restores cached rows without revalidating on a tab remount", () => { const body = functionBody("loadObjects"); - const loadingReset = body.indexOf("loadingObjects.value = false;"); - const refreshingReset = body.indexOf("refreshingObjects.value = false;"); const cachedBranch = body.indexOf("const cached ="); + const cachedReturn = body.indexOf("finishOnce();\n return;", cachedBranch); - expect(loadingReset).toBeGreaterThanOrEqual(0); - expect(refreshingReset).toBeGreaterThanOrEqual(0); - // Both resets must precede the cached fresh/stale decision so the newest request - // owns the spinner state even when a superseded request's finally() cannot run. - expect(Math.min(loadingReset, refreshingReset)).toBeLessThan(cachedBranch); - // And neither flag may be turned back on before the branch decides the indicator. - expect(body.indexOf("refreshingObjects.value = true;")).toBeGreaterThan(cachedBranch); - expect(body.indexOf("loadingObjects.value = true;")).toBeGreaterThan(cachedBranch); + expect(cachedBranch).toBeGreaterThanOrEqual(0); + expect(cachedReturn).toBeGreaterThan(cachedBranch); + expect(body.slice(cachedBranch, cachedReturn)).not.toContain("cached.stale"); + expect(body.slice(cachedBranch, cachedReturn)).not.toContain("refreshingObjects.value = true;"); }); it("exposes a non-blocking refresh flag distinct from the blocking full-area load", () => {