diff --git a/apps/desktop/src/components/layout/ContentArea.vue b/apps/desktop/src/components/layout/ContentArea.vue index 4d961fb692..8e8cf7758d 100644 --- a/apps/desktop/src/components/layout/ContentArea.vue +++ b/apps/desktop/src/components/layout/ContentArea.vue @@ -278,7 +278,7 @@ const columnInfoError = ref(undefined); const dataGridRef = ref(); const queryEditorRef = ref>(); const elasticsearchJsonResponsePanelRef = ref(); -const tableStructureEditorRef = ref<{ applyChanges: () => Promise }>(); +const tableStructureEditorRef = ref<{ applyChanges: () => Promise; focusSearch: () => boolean }>(); const standaloneResultToolbarRef = ref(null); const standaloneResultToolbarWidth = ref(0); const standaloneResultToolbarViewportWidth = ref(0); @@ -917,6 +917,7 @@ function focusSearch(): boolean { if (props.activeTab.mode === "consul") return consulWorkspaceRef.value?.focusSearch() ?? false; if (props.activeTab.mode === "databases") return databaseBrowserRef.value?.focusSearch() ?? false; if (props.activeTab.mode === "objects") return objectBrowserRef.value?.focusSearch() ?? false; + if (props.activeTab.mode === "structure") return tableStructureEditorRef.value?.focusSearch() ?? false; if (props.activeTab.mode === "query") { // The shared result surface (resultOnly) owns the grid, not the editor; // route its search to the DataGrid instead of the missing QueryEditor. diff --git a/apps/desktop/src/components/layout/__tests__/SqlEditorWorkspace.spec.ts b/apps/desktop/src/components/layout/__tests__/SqlEditorWorkspace.spec.ts index 6be65187ca..88712f684d 100644 --- a/apps/desktop/src/components/layout/__tests__/SqlEditorWorkspace.spec.ts +++ b/apps/desktop/src/components/layout/__tests__/SqlEditorWorkspace.spec.ts @@ -30,6 +30,11 @@ describe("SQL editor workspace single-group tracer bullet", () => { expect(contentAreaSource).toContain("resultOnly?: boolean"); }); + it("routes structure-tab search to the table editor instead of the sidebar", () => { + expect(contentAreaSource).toContain('if (props.activeTab.mode === "structure") return tableStructureEditorRef.value?.focusSearch() ?? false;'); + expect(contentAreaSource).toContain("focusSearch: () => boolean"); + }); + it("keeps AppTabBar as the global special-page bar while regular tabs move to groups", () => { const tabBarSource = readFileSync(new URL("../AppTabBar.vue", import.meta.url), "utf8"); expect(tabBarSource).not.toContain("hideRegularTabs"); diff --git a/apps/desktop/src/components/structure/TableStructureEditor.vue b/apps/desktop/src/components/structure/TableStructureEditor.vue index 943e9620bd..2d72a2d75b 100644 --- a/apps/desktop/src/components/structure/TableStructureEditor.vue +++ b/apps/desktop/src/components/structure/TableStructureEditor.vue @@ -3025,6 +3025,22 @@ function focusIndexSearch() { }); } +function focusSearch(): boolean { + if (activeTab.value === "columns") { + focusColumnSearch(); + return true; + } + if (activeTab.value === "indexes") { + focusIndexSearch(); + return true; + } + if (activeTab.value === "ddl") { + ddlSearchPanelRef.value?.openSearch(); + return true; + } + return false; +} + function scrollToIndexSearchMatch(direction: 1 | -1 = 1) { const query = indexSearchText.value.trim(); if (!query) { @@ -3653,7 +3669,7 @@ async function applyChanges() { } } -defineExpose({ applyChanges }); +defineExpose({ applyChanges, focusSearch }); function addItemForActiveTab(): boolean { if (activeTab.value === "columns" && canAddColumn.value) { @@ -3719,10 +3735,10 @@ function onStructureEditorKeydown(event: KeyboardEvent) { return; } if (isPlainModShortcut(event, "f")) { - event.preventDefault(); - event.stopPropagation(); - if (activeTab.value === "columns") focusColumnSearch(); - else if (activeTab.value === "ddl") ddlSearchPanelRef.value?.openSearch(); + if (focusSearch()) { + event.preventDefault(); + event.stopPropagation(); + } return; } if (isPlainModShortcut(event, "s")) { diff --git a/apps/desktop/src/components/structure/__tests__/TableStructureEditor.ddlSearchSource.spec.ts b/apps/desktop/src/components/structure/__tests__/TableStructureEditor.ddlSearchSource.spec.ts index 0d2b168f27..00432cb292 100644 --- a/apps/desktop/src/components/structure/__tests__/TableStructureEditor.ddlSearchSource.spec.ts +++ b/apps/desktop/src/components/structure/__tests__/TableStructureEditor.ddlSearchSource.spec.ts @@ -7,11 +7,17 @@ describe("TableStructureEditor DDL search wiring", () => { it("uses a CodeMirror editor and the shared search panel", () => { expect(source).toContain('import EditorSearchPanel from "@/components/editor/EditorSearchPanel.vue";'); expect(source).toContain('key: "Mod-f"'); - expect(source).toContain('else if (activeTab.value === "ddl") ddlSearchPanelRef.value?.openSearch();'); + expect(source).toMatch(/if \(activeTab\.value === "ddl"\) \{\s*ddlSearchPanelRef\.value\?\.openSearch\(\);\s*return true;/); expect(source).not.toContain("ddlPreRef"); expect(source).not.toContain("onDdlKeydown"); }); + it("routes the global search command to the active searchable structure tab", () => { + expect(source).toMatch(/function focusSearch\(\): boolean \{[\s\S]*activeTab\.value === "columns"[\s\S]*focusColumnSearch\(\);[\s\S]*activeTab\.value === "indexes"[\s\S]*focusIndexSearch\(\);[\s\S]*activeTab\.value === "ddl"[\s\S]*ddlSearchPanelRef\.value\?\.openSearch\(\)/); + expect(source).toContain("defineExpose({ applyChanges, focusSearch });"); + expect(source).toMatch(/if \(isPlainModShortcut\(event, "f"\)\) \{\s*if \(focusSearch\(\)\)/); + }); + it("keeps the DDL editable only where an executable script makes sense", () => { // Editable for an existing table with loaded DDL; the create-table flow has // no DDL tab and an empty baseline only renders a placeholder string, which