From 094dfb1f0624f9a8b8dabd56efcb30941ef4331f Mon Sep 17 00:00:00 2001 From: vrustx <279631638@qq.com> Date: Mon, 8 Jun 2026 01:08:23 +0800 Subject: [PATCH] fix(sidebar): locate-in-sidebar reaches the table after a stale tree cache (#715) (#822) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After an app update (typically >15min since last use), the on-disk schema tree cache is past its TTL and counts as stale. When "locate in sidebar" loads the connection's databases, the stale cache is served synchronously and an async `refreshStaleTreeNode` fires in the background; that refresh replaces the database node with a fresh one whose tables haven't loaded yet, evicting the node that locate just populated. By the time `findNodePathForActiveTab` runs, the target table isn't in the tree, so locate only reaches the database. After a restart the cache is fresh, no refresh races, and locate works — matching the report. Make `ensureTreeLoadedForTab` accept `{ force }` (loads everything via awaited calls, bypassing the stale-cache fire-and-forget path) and, in `locateActiveTabInSidebar`, retry once with force when the first `findNodePath` misses. Fresh-cache locates still succeed on the first pass and never force (no regression); `refreshStaleTreeNode` is untouched. Co-authored-by: vrustx Co-authored-by: Claude Opus 4.8 (1M context) --- .../src/components/sidebar/ConnectionTree.vue | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/apps/desktop/src/components/sidebar/ConnectionTree.vue b/apps/desktop/src/components/sidebar/ConnectionTree.vue index c19ede3d79..d73e401505 100644 --- a/apps/desktop/src/components/sidebar/ConnectionTree.vue +++ b/apps/desktop/src/components/sidebar/ConnectionTree.vue @@ -230,7 +230,15 @@ async function locateActiveTabInSidebar() { clearSearchScopeFilter(); } - const nodePath = findNodePathForActiveTab(tab, store.treeNodes); + let nodePath = findNodePathForActiveTab(tab, store.treeNodes); + if (!nodePath) { + // The first load may have served a stale schema cache whose async refresh + // replaced the database node before its tables finished loading, so the + // table isn't in the tree yet. Force a synchronous reload and retry once so + // locate reaches the table, not just the database (issue #715). + await ensureTreeLoadedForTab(tab, { force: true }); + nodePath = findNodePathForActiveTab(tab, store.treeNodes); + } if (!nodePath) return; for (const ancestor of nodePath) { @@ -256,23 +264,30 @@ async function locateActiveTabInSidebar() { await scrollToSidebarNode(match.id); } -async function ensureTreeLoadedForTab(tab: QueryTab) { +async function ensureTreeLoadedForTab(tab: QueryTab, opts?: { force?: boolean }) { const connId = tab.connectionId; if (!connId) return; const config = store.getConfig(connId); if (!config) return; + // When forcing, bypass the cached children check so we reload from the + // source. A stale schema cache otherwise serves children and triggers an + // async background refresh that can replace nodes mid-flight, leaving the + // tree without the target table by the time we search for it (issue #715). + const force = opts?.force ?? false; + const loadOptions = force ? { force: true } : undefined; + // Ensure databases are loaded under the connection const connNode = store.treeNodes.find((n) => n.id === connId); - if (connNode && (!connNode.children || connNode.children.length === 0)) { + if (connNode && (force || !connNode.children || connNode.children.length === 0)) { try { if (config.db_type === "redis") { await store.loadRedisDatabases(connId); } else if (config.db_type === "mongodb" || config.db_type === "elasticsearch") { await store.loadMongoDatabases(connId); } else { - await store.loadDatabases(connId); + await store.loadDatabases(connId, loadOptions); } } catch { return; @@ -283,23 +298,24 @@ async function ensureTreeLoadedForTab(tab: QueryTab) { // Find the database node const dbNode = findDatabaseNode(store.treeNodes, connId, tab.database); - if (!dbNode || (dbNode.children && dbNode.children.length > 0)) return; + if (!dbNode) return; + if (!force && dbNode.children && dbNode.children.length > 0) return; // Load database contents try { if (config.db_type === "sqlserver") { - await store.loadSqlServerDatabaseObjects(connId, tab.database); + await store.loadSqlServerDatabaseObjects(connId, tab.database, loadOptions); } else if (usesTreeSchemaMode(config.db_type) && !connectionUsesDatabaseObjectTreeMode(config)) { - await store.loadSchemas(connId, tab.database); + await store.loadSchemas(connId, tab.database, loadOptions); // If we have a schema, also load tables under that schema if (tab.schema) { const schemaNode = findSchemaNode(store.treeNodes, connId, tab.database, tab.schema); - if (schemaNode && (!schemaNode.children || schemaNode.children.length === 0)) { - await store.loadTables(connId, tab.database, tab.schema); + if (schemaNode && (force || !schemaNode.children || schemaNode.children.length === 0)) { + await store.loadTables(connId, tab.database, tab.schema, loadOptions); } } } else { - await store.loadTables(connId, tab.database); + await store.loadTables(connId, tab.database, undefined, loadOptions); } } catch { // Node just won't have children loaded