From b28039cf0d2f31d781ea50d4de15e0f3e4b2a78e Mon Sep 17 00:00:00 2001 From: mirivlad Date: Tue, 30 Jun 2026 20:57:58 +0800 Subject: [PATCH] Fix global search folder navigation --- frontend/e2e/ux-followup.spec.js | 15 +++++++++ frontend/src/lib/shell/GlobalSearch.svelte | 31 +++++++++++++++++- frontend/src/lib/shell/WorkspaceHost.svelte | 36 +++++++++++++++++++-- 3 files changed, 78 insertions(+), 4 deletions(-) diff --git a/frontend/e2e/ux-followup.spec.js b/frontend/e2e/ux-followup.spec.js index 4fe1e62..e846ba6 100644 --- a/frontend/e2e/ux-followup.spec.js +++ b/frontend/e2e/ux-followup.spec.js @@ -31,6 +31,21 @@ test.describe('UX follow-up fixes', () => { await expect(page.locator('[data-global-search-results]')).toContainText('project-only.txt', { timeout: 10000 }); }); + test('global search folder results open the workspace Files context', async ({ page }) => { + const search = page.locator('[data-global-search-input]'); + await search.fill('Project/Notes'); + + const folderResult = page.locator('[data-global-search-result-type="Folder"][data-global-search-result-path="Project/Notes"]'); + await expect(folderResult).toBeVisible({ timeout: 10000 }); + await folderResult.click(); + + await expect(page.locator('.workspace-title')).toHaveText('Project', { timeout: 10000 }); + await expect(page.getByRole('tab', { name: 'Files' })).toHaveAttribute('aria-selected', 'true'); + await expect(page.locator('.files-root')).toBeVisible(); + await expect(page.locator('.files-breadcrumb')).toContainText('Notes'); + await expect(page.locator('.files-item')).toContainText('Overview.md'); + }); + test('plugin settings modal gives complex panels enough space', async ({ page }) => { await openPluginManager(page); await page.locator('.plugin-card').filter({ hasText: 'verstak.platform-test' }).getByRole('button', { name: 'Settings' }).click(); diff --git a/frontend/src/lib/shell/GlobalSearch.svelte b/frontend/src/lib/shell/GlobalSearch.svelte index 48d1567..c50db5a 100644 --- a/frontend/src/lib/shell/GlobalSearch.svelte +++ b/frontend/src/lib/shell/GlobalSearch.svelte @@ -180,6 +180,7 @@ rank: entry.type === 'folder' ? 30 : 40, action: entry.type === 'folder' ? 'file-folder' : 'file', path, + nodes, }); } @@ -209,6 +210,28 @@ })); return; } + if (item.action === 'file-folder') { + const parts = String(item.path || '').split('/').filter(Boolean); + const workspaceName = parts[0] || ''; + const localPath = parts.slice(1).join('/'); + if (workspaceName) { + window.__filesHistoryByWorkspace = window.__filesHistoryByWorkspace || {}; + window.__filesHistoryByWorkspace[workspaceName] = { + stack: [localPath], + index: 0, + currentPath: localPath, + }; + const detail = { workspaceName }; + if (Array.isArray(item.nodes) && item.nodes.length > 0) detail.nodes = item.nodes; + window.dispatchEvent(new CustomEvent('verstak:workspace-selected', { + detail + })); + window.dispatchEvent(new CustomEvent('verstak:workspace-open-tool', { + detail: { kind: 'files' } + })); + } + return; + } if (item.action === 'file') { const response = await App.OpenWorkbenchResource('verstak.search', { kind: 'vault-file', @@ -241,7 +264,13 @@
{#if results.length} {#each results as item} - diff --git a/frontend/src/lib/shell/WorkspaceHost.svelte b/frontend/src/lib/shell/WorkspaceHost.svelte index 889bb95..beb60b8 100644 --- a/frontend/src/lib/shell/WorkspaceHost.svelte +++ b/frontend/src/lib/shell/WorkspaceHost.svelte @@ -2,6 +2,7 @@ import PluginBundleHost from '../plugin-host/PluginBundleHost.svelte'; import TodaySurface from './TodaySurface.svelte'; import * as App from '../../../wailsjs/go/api/App'; + import { onDestroy, onMount } from 'svelte'; export let selectedWorkspaceName = ''; export let nodes = []; @@ -11,6 +12,7 @@ let plugins = []; let workspaceTools = []; let toolsLoaded = false; + let requestedToolKind = ''; const todayTool = { id: '__today', title: 'Today', pluginId: 'verstak.shell', component: 'TodaySurface', shell: true }; const toolOrder = new Map([ @@ -31,8 +33,23 @@ $: if (displayedTools.length > 0 && (!activeToolKey || (toolsLoaded && !displayedTools.some(tool => toolKey(tool) === activeToolKey)))) { activeToolKey = toolKey(todayTool); } + $: if (requestedToolKind && workspaceTools.length > 0) { + const match = findWorkspaceTool(requestedToolKind); + if (match) { + requestedToolKind = ''; + selectTool(match); + } + } $: if (selectedWorkspaceName) loadTools(); + onMount(() => { + window.addEventListener('verstak:workspace-open-tool', handleWorkspaceOpenTool); + }); + + onDestroy(() => { + window.removeEventListener('verstak:workspace-open-tool', handleWorkspaceOpenTool); + }); + function toolKey(tool) { return `${tool?.pluginId || ''}:${tool?.id || ''}`; } @@ -64,16 +81,29 @@ })); } - function openWorkspaceTool(event) { - const kind = String(event?.detail?.kind || '').toLowerCase(); - const match = workspaceTools.find(tool => { + function findWorkspaceTool(kind) { + kind = String(kind || '').toLowerCase(); + return workspaceTools.find(tool => { const text = `${tool?.title || ''} ${tool?.id || ''} ${tool?.pluginId || ''}`.toLowerCase(); if (kind === 'browser-inbox') return text.includes('browser') || text.includes('inbox'); return text.includes(kind); }); + } + + function requestWorkspaceTool(kind) { + requestedToolKind = String(kind || '').toLowerCase(); + const match = findWorkspaceTool(requestedToolKind); if (match) selectTool(match); } + function openWorkspaceTool(event) { + requestWorkspaceTool(event?.detail?.kind); + } + + function handleWorkspaceOpenTool(event) { + requestWorkspaceTool(event?.detail?.kind); + } + async function loadTools() { try { toolsLoaded = false;