fix(sidebar): locate-in-sidebar reaches the table after a stale tree cache (#715) (#822)

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 <vrustx@vrustxdeMac-mini.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
vrustx
2026-06-08 01:08:23 +08:00
committed by GitHub
co-authored by vrustx Claude Opus 4.8
parent e6062b2b07
commit 094dfb1f06
@@ -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