diff --git a/frontend/src/components/Sidebar.locate-toolbar.test.tsx b/frontend/src/components/Sidebar.locate-toolbar.test.tsx index 44f3f9d..6bc6cce 100644 --- a/frontend/src/components/Sidebar.locate-toolbar.test.tsx +++ b/frontend/src/components/Sidebar.locate-toolbar.test.tsx @@ -1009,6 +1009,23 @@ describe('Sidebar locate toolbar', () => { expect(css).toMatch(/\.gn-v2-explorer-filter-tabs button \{[^}]*flex: 0 0 auto;[^}]*white-space: nowrap;/s); }); + it('shows a pending state while a connection root is loading', () => { + const css = readV2ThemeCss(); + const source = readSidebarSource(); + const treeLoaderSource = readSourceFile('./sidebar/useSidebarTreeLoaders.tsx'); + const titleRenderSource = readSourceFile('./sidebar/useSidebarTitleRender.tsx'); + const v2ContextMenuSource = readSourceFile('./sidebar/useSidebarV2ContextMenu.tsx'); + + expect(source).toContain("export type SidebarConnectionState = 'loading' | 'success' | 'error';"); + expect(treeLoaderSource).toContain("setConnectionStates(prev => ({ ...prev, [conn.id]: 'loading' }));"); + expect(titleRenderSource).toContain("let status: 'loading' | 'success' | 'error' | 'default' = 'default';"); + expect(titleRenderSource).toContain("if (connectionStates[node.key] === 'loading') status = 'loading';"); + expect(v2ContextMenuSource).toContain("const statusMap = new Map();"); + expect(v2ContextMenuSource).toContain("value === 'loading' ? 'loading'"); + expect(css).toMatch(/\.gn-v2-tree-status\.is-loading::before \{[^}]*border: 2px solid rgba\(37, 99, 235, 0\.24\);[^}]*animation: gn-v2-tree-status-spin 0\.8s linear infinite;/s); + expect(css).toMatch(/@keyframes gn-v2-tree-status-spin \{[^}]*to \{ transform: rotate\(360deg\); \}/s); + }); + it('keeps v2 tree status dots circular while using virtual horizontal scroll for long labels', () => { const css = readV2ThemeCss(); const source = readSidebarSource(); diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 4142c45..002b7b9 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -175,6 +175,7 @@ import { shouldCloseV2CommandSearchOnGlobalKey, shouldRunV2CommandSearchEnter, sortSidebarTableEntries, + type SidebarConnectionState, type SidebarTreeNode as TreeNode, type V2CommandSearchItem, } from './sidebarV2Utils'; @@ -704,8 +705,8 @@ const Sidebar: React.FC<{ return () => window.removeEventListener('keydown', handleV2CommandSearchGlobalKeyDown, true); }, [closeV2CommandSearch, isV2CommandSearchOpen]); - // Connection Status State: key -> 'success' | 'error' - const [connectionStates, setConnectionStates] = useState>({}); + // Connection Status State: key -> 'loading' | 'success' | 'error' + const [connectionStates, setConnectionStates] = useState>({}); const [isTreeDragging, setIsTreeDragging] = useState(false); // Create Database Modal diff --git a/frontend/src/components/sidebar/useSidebarTitleRender.tsx b/frontend/src/components/sidebar/useSidebarTitleRender.tsx index f2b40c8..750e6d0 100644 --- a/frontend/src/components/sidebar/useSidebarTitleRender.tsx +++ b/frontend/src/components/sidebar/useSidebarTitleRender.tsx @@ -9,6 +9,7 @@ import { SIDEBAR_SQL_EDITOR_DRAG_MIME, encodeSidebarSqlEditorDragPayload } from import { resolveSidebarObjectDragText, } from '../sidebarCoreUtils'; +import type { SidebarConnectionState } from '../sidebarV2Utils'; import { shouldHideSchemaPrefix, splitQualifiedName, @@ -16,7 +17,7 @@ import { import { resolveV2ObjectGroupTitle } from './sidebarHelpers'; type UseSidebarTitleRenderArgs = { - connectionStates: Record; + connectionStates: Record; isV2Ui: boolean; renderV2TreeTitle: (node: any, hoverTitle: string, statusBadge: React.ReactNode) => React.ReactNode; handleAddExternalSQLDirectory: (node: any) => Promise; @@ -36,16 +37,18 @@ export const useSidebarTitleRender = ({ treeDragSelectSuppressUntilRef, setIsTreeDragging, }: UseSidebarTitleRenderArgs) => useCallback((node: any) => { - let status: 'success' | 'error' | 'default' = 'default'; + let status: 'loading' | 'success' | 'error' | 'default' = 'default'; if (node.type === 'connection' || node.type === 'database') { - if (connectionStates[node.key] === 'success') status = 'success'; + if (connectionStates[node.key] === 'loading') status = 'loading'; + else if (connectionStates[node.key] === 'success') status = 'success'; else if (connectionStates[node.key] === 'error') status = 'error'; } + const legacyBadgeStatus = status === 'loading' ? 'processing' : status; const statusBadge = node.type === 'connection' || node.type === 'database' ? ( isV2Ui ?