From 7630a312862cfa12c70cb8b734fe6c30d2d44d60 Mon Sep 17 00:00:00 2001 From: mirivlad Date: Sat, 27 Jun 2026 13:13:15 +0800 Subject: [PATCH] Add command palette host --- docs/PLUGIN_RUNTIME.md | 7 +- frontend/e2e/command-palette.spec.js | 48 +++ frontend/src/App.svelte | 2 + .../src/lib/plugin-host/VerstakPluginAPI.js | 38 ++- frontend/src/lib/shell/CommandPalette.svelte | 293 ++++++++++++++++++ 5 files changed, 373 insertions(+), 15 deletions(-) create mode 100644 frontend/e2e/command-palette.spec.js create mode 100644 frontend/src/lib/shell/CommandPalette.svelte diff --git a/docs/PLUGIN_RUNTIME.md b/docs/PLUGIN_RUNTIME.md index 090962e..db7f194 100644 --- a/docs/PLUGIN_RUNTIME.md +++ b/docs/PLUGIN_RUNTIME.md @@ -189,7 +189,7 @@ Icon fields use shell icon names rendered through the bundled Lucide SVG wrapper | Боковая панель | `sidebarItems` | Элементы в sidebar слева | ✅ Sidebar.svelte (из ContributionRegistry) | | Основные панели | `views` | Полноценные страницы/панели | ✅ ViewContainer.svelte (PluginBundleHost — real frontend bundle) | | Панели настроек | `settingsPanels` | Панели в Plugin Manager | ✅ PluginManager.svelte (кнопка Settings, открывает modal) | -| Команды | `commands` | Команды для command palette | ✅ ContributionRegistry (UI command palette не реализован) | +| Команды | `commands` | Команды для command palette | ✅ ContributionRegistry + CommandPalette UI | | Open/edit providers | `openProviders` | Провайдеры viewer/editor для Workbench routing | ✅ ContributionRegistry + минимальный Workbench host | ### Планируемые contribution points @@ -388,6 +388,10 @@ contributions summary. понятную ошибку `declared-but-unhandled`. - Handler registry очищается при component unmount, reload/disable flow и `api.dispose()`. +- Shell command palette открывается через `Ctrl+K` / `Cmd+K` или + `Ctrl+Shift+P` / `Cmd+Shift+P`, показывает commands enabled plugins, + фильтрует по title/id/plugin и вызывает зарегистрированные bundled frontend + handlers. `events` @@ -487,6 +491,7 @@ bundled runtime. Это реальный runtime contract для cooperative bun | `api.capabilities.has(id)` | ✅ Работает | Boolean wrapper над `get` | | `api.commands.register(id, handler)` | ✅ Работает | Регистрирует bundled frontend handler для объявленной command | | `api.commands.execute(id, args)` | ✅ Работает | Валидирует declaration/permission/backend state и вызывает bundled handler | +| Command Palette UI | ✅ Работает | `Ctrl/Cmd+K`, фильтр enabled plugin commands, вызов registered frontend handlers | | `api.events.publish(type, payload)` | ✅ Работает | Валидирует permission и публикует во frontend event bus | | `api.events.subscribe(type, handler)` | ✅ Работает | Валидирует permission и подписывает handler на frontend event bus | | `api.files.list(relativeDir)` | ✅ Работает | Список vault-relative директории, `.verstak` скрыта | diff --git a/frontend/e2e/command-palette.spec.js b/frontend/e2e/command-palette.spec.js new file mode 100644 index 0000000..0de6428 --- /dev/null +++ b/frontend/e2e/command-palette.spec.js @@ -0,0 +1,48 @@ +import { test, expect } from '@playwright/test'; +import { waitForAppReady, setupConsoleCollector, resetMockState } from './helpers.js'; + +test.describe('Command Palette', () => { + let consoleCollector; + + test.beforeEach(async ({ page }) => { + consoleCollector = setupConsoleCollector(page); + await resetMockState(page); + await page.goto('/'); + await waitForAppReady(page); + }); + + test.afterEach(async () => { + consoleCollector.assertNoErrors(); + }); + + test('opens with keyboard, filters commands, and executes registered frontend handler', async ({ page }) => { + await page.locator('.sidebar .plugin-item').filter({ hasText: 'Platform Test' }).click(); + await expect(page.locator('.pt-root')).toBeVisible({ timeout: 10000 }); + + await page.keyboard.press(process.platform === 'darwin' ? 'Meta+K' : 'Control+K'); + + const palette = page.locator('.command-palette'); + await expect(palette).toBeVisible(); + await expect(palette.locator('[data-command-id="verstak.platform-test.show-version"]')).toBeVisible(); + + await palette.locator('[data-command-palette-input]').fill('version'); + await expect(palette.locator('[data-command-id="verstak.platform-test.show-version"]')).toBeVisible(); + await expect(palette.locator('[data-command-id="verstak.platform-test.run-tests"]')).not.toBeVisible(); + + await page.keyboard.press('Enter'); + + await expect(palette).not.toBeVisible(); + await expect(page.locator('[data-command-palette-status="success"]')).toContainText('Show Version Info'); + await expect(page.locator('[data-command-palette-status="success"]')).toContainText('handled'); + }); + + test('Escape closes the palette without changing current view', async ({ page }) => { + await page.keyboard.press(process.platform === 'darwin' ? 'Meta+K' : 'Control+K'); + await expect(page.locator('.command-palette')).toBeVisible(); + + await page.keyboard.press('Escape'); + + await expect(page.locator('.command-palette')).not.toBeVisible(); + await expect(page.locator('.plugin-manager')).toBeVisible(); + }); +}); diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index ec934bf..da96eae 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -1,6 +1,7 @@ + +{#if statusMessage} +
+ {statusMessage} +
+{/if} + +{#if open} + +{/if} + +