diff --git a/frontend/e2e/sidebar-opens-view.spec.js b/frontend/e2e/sidebar-opens-view.spec.js index c3b0949..a32ec57 100644 --- a/frontend/e2e/sidebar-opens-view.spec.js +++ b/frontend/e2e/sidebar-opens-view.spec.js @@ -28,10 +28,22 @@ test.describe('B: Sidebar opens plugin view by item.view', () => { }); test('Sidebar item exists with correct label', async ({ page }) => { + await expect(page.locator('.sidebar .nav-item').filter({ hasText: 'Plugin Manager' })).not.toBeVisible(); + await expect(page.locator('.sidebar .plugin-item').filter({ hasText: 'Activity' })).toBeVisible(); + await expect(page.locator('.sidebar .plugin-item').filter({ hasText: 'Browser Inbox' })).toBeVisible(); + const sidebarItem = page.locator('.sidebar .plugin-item').filter({ hasText: 'Platform Test' }); await expect(sidebarItem).toBeVisible(); }); + test('Global Activity and Browser Inbox sidebar items open plugin views', async ({ page }) => { + await page.locator('.sidebar .plugin-item').filter({ hasText: 'Activity' }).click(); + await expect(page.locator('.view-container .view-header h2')).toHaveText('Activity', { timeout: 10000 }); + + await page.locator('.sidebar .plugin-item').filter({ hasText: 'Browser Inbox' }).click(); + await expect(page.locator('.view-container .view-header h2')).toHaveText('Browser Inbox', { timeout: 10000 }); + }); + test('Click sidebar item opens diagnostics view by view ID, not sidebar ID', async ({ page }) => { const sidebarItem = page.locator('.sidebar .plugin-item').filter({ hasText: 'Platform Test' }); await expect(sidebarItem).toBeVisible(); diff --git a/frontend/e2e/status-bar.spec.js b/frontend/e2e/status-bar.spec.js index b75195e..68ccf07 100644 --- a/frontend/e2e/status-bar.spec.js +++ b/frontend/e2e/status-bar.spec.js @@ -18,9 +18,24 @@ test.describe('Status Bar host', () => { test('renders enabled plugin statusBarItems', async ({ page }) => { const statusBar = page.locator('.status-bar'); await expect(statusBar).toBeVisible(); + await expect(statusBar.locator('.vault-status')).toContainText('Vault: open'); await expect(statusBar.locator('[data-status-item-id="verstak.platform-test.status"]')).toContainText('All Tests Pass'); }); + test('opens settings menu with plugin manager and plugin settings', async ({ page }) => { + await page.locator('[data-settings-menu-button]').click(); + + await expect(page.locator('[data-settings-action="plugin-manager"]')).toBeVisible(); + await expect(page.locator('[data-settings-panel-id="verstak.sync.settings"]')).toBeVisible(); + + await page.locator('.sidebar .plugin-item').filter({ hasText: 'Platform Test' }).click(); + await expect(page.locator('.view-container .view-header h2')).toHaveText('Platform Diagnostics'); + + await page.locator('[data-settings-menu-button]').click(); + await page.locator('[data-settings-action="plugin-manager"]').click(); + await expect(page.locator('.plugin-manager')).toBeVisible(); + }); + test('refreshes statusBarItems after disabling plugin', async ({ page }) => { const pluginCard = page.locator('.plugin-card').filter({ hasText: 'verstak.platform-test' }); await expect(page.locator('[data-status-item-id="verstak.platform-test.status"]')).toBeVisible(); diff --git a/frontend/src/lib/shell/Sidebar.svelte b/frontend/src/lib/shell/Sidebar.svelte index ec6afda..d454b6a 100644 --- a/frontend/src/lib/shell/Sidebar.svelte +++ b/frontend/src/lib/shell/Sidebar.svelte @@ -14,10 +14,6 @@ let sidebarItems = []; let errorMessage = ''; - let navItems = [ - { id: 'plugin-manager', label: 'Plugin Manager', icon: 'puzzle' }, - ]; - $: vaultOpen = vaultStatus.status === 'open'; async function loadSidebar() { @@ -66,11 +62,6 @@ window.removeEventListener('verstak:plugins-changed', loadSidebar); }); - function handleNav(id) { - debug.log('[Sidebar] handleNav:', id); - window.dispatchEvent(new CustomEvent('verstak:nav', { detail: { viewId: id } })); - } - function handleSidebarItem(item) { debug.log('[Sidebar] handleSidebarItem:', item.id, '-> view:', item.view); // Use item.view (the view contribution ID) if available, fall back to item.id @@ -85,22 +76,9 @@ - - {#if sidebarItems.length > 0}
@@ -165,20 +138,12 @@ font-weight: 600; } - .sidebar-nav { - display: flex; - flex-direction: column; - padding: 0.5rem 0.75rem; - gap: 0.15rem; - } - .sidebar-section { display: flex; flex-direction: column; padding: 0.5rem 0.75rem; gap: 0.15rem; - border-top: 1px solid #0f3460; - margin-top: 0.25rem; + border-bottom: 1px solid #0f3460; } :global(workspace-tree) { @@ -240,19 +205,6 @@ border-top: 1px solid #0f3460; } - .vault-indicator { - font-size: 0.7rem; - color: #666; - } - - .vault-indicator.vault-open { - color: #4ecca3; - } - - .vault-indicator.vault-closed { - color: #a0a0b8; - } - .sidebar-error { display: flex; align-items: center; diff --git a/frontend/src/lib/shell/StatusBar.svelte b/frontend/src/lib/shell/StatusBar.svelte index 527123c..e7682ec 100644 --- a/frontend/src/lib/shell/StatusBar.svelte +++ b/frontend/src/lib/shell/StatusBar.svelte @@ -1,20 +1,29 @@ diff --git a/frontend/src/lib/test/wails-mock.js b/frontend/src/lib/test/wails-mock.js index a08bfdb..a2538cc 100644 --- a/frontend/src/lib/test/wails-mock.js +++ b/frontend/src/lib/test/wails-mock.js @@ -157,11 +157,59 @@ }, rootPath: '/tmp/verstak-test/plugins/sync', error: '' + }, + 'verstak.activity': { + status: 'loaded', + enabled: true, + manifest: { + schemaVersion: 1, + id: 'verstak.activity', + name: 'Activity', + version: '0.1.0', + apiVersion: '0.1.0', + description: 'Workspace-scoped activity log for public plugin events.', + source: 'official', + icon: 'activity', + provides: ['activity.log', 'activity.provider', 'activity.reconstruction'], + permissions: ['events.subscribe', 'storage.namespace', 'ui.register'], + frontend: { entry: 'frontend/dist/index.js' }, + contributes: { + views: [{ id: 'verstak.activity.view', title: 'Activity', icon: 'activity', component: 'ActivityView' }], + sidebarItems: [{ id: 'verstak.activity.sidebar', title: 'Activity', icon: 'activity', view: 'verstak.activity.view', position: 20 }], + workspaceItems: [{ id: 'verstak.activity.workspace', title: 'Activity', icon: 'activity', component: 'ActivityView' }] + } + }, + rootPath: '/tmp/verstak-test/plugins/activity', + error: '' + }, + 'verstak.browser-inbox': { + status: 'loaded', + enabled: true, + manifest: { + schemaVersion: 1, + id: 'verstak.browser-inbox', + name: 'Browser Inbox', + version: '0.1.0', + apiVersion: '0.1.0', + description: 'Workspace-scoped inbox for browser captures.', + source: 'official', + icon: 'inbox', + provides: ['browser.inbox'], + permissions: ['events.subscribe', 'storage.namespace', 'ui.register'], + frontend: { entry: 'frontend/dist/index.js' }, + contributes: { + views: [{ id: 'verstak.browser-inbox.view', title: 'Browser Inbox', icon: 'inbox', component: 'BrowserInboxView' }], + sidebarItems: [{ id: 'verstak.browser-inbox.sidebar', title: 'Browser Inbox', icon: 'inbox', view: 'verstak.browser-inbox.view', position: 30 }], + workspaceItems: [{ id: 'verstak.browser-inbox.workspace', title: 'Browser Inbox', icon: 'inbox', component: 'BrowserInboxView' }] + } + }, + rootPath: '/tmp/verstak-test/plugins/browser-inbox', + error: '' } }; var vaultStatus = { status: 'open', path: '/tmp/verstak-test/vault', vaultId: 'test-vault-001' }; - var vaultPluginState = { enabledPlugins: ['verstak.platform-test', 'verstak.default-editor', 'verstak.files', 'verstak.sync'], disabledPlugins: [], desiredPlugins: [{ id: 'verstak.platform-test', version: '0.1.0', source: 'official' }, { id: 'verstak.default-editor', version: '0.1.0', source: 'official' }, { id: 'verstak.files', version: '0.1.0', source: 'official' }, { id: 'verstak.sync', version: '0.1.0', source: 'official' }] }; + var vaultPluginState = { enabledPlugins: ['verstak.platform-test', 'verstak.default-editor', 'verstak.files', 'verstak.sync', 'verstak.activity', 'verstak.browser-inbox'], disabledPlugins: [], desiredPlugins: [{ id: 'verstak.platform-test', version: '0.1.0', source: 'official' }, { id: 'verstak.default-editor', version: '0.1.0', source: 'official' }, { id: 'verstak.files', version: '0.1.0', source: 'official' }, { id: 'verstak.sync', version: '0.1.0', source: 'official' }, { id: 'verstak.activity', version: '0.1.0', source: 'official' }, { id: 'verstak.browser-inbox', version: '0.1.0', source: 'official' }] }; var appSettings = { currentVaultPath: '/tmp/verstak-test/vault', recentVaults: [] }; var workbenchPreferences = {}; var openedResources = []; @@ -922,6 +970,24 @@ }.toString() + ')();'; } + function activityBundle() { + return [ + "(function(){", + "var ActivityView={mount:function(containerEl){containerEl.innerHTML='';var root=document.createElement('div');root.className='activity-root';root.setAttribute('data-plugin-id','verstak.activity');var title=document.createElement('h2');title.textContent='Activity';var body=document.createElement('div');body.textContent='Global activity feed';root.appendChild(title);root.appendChild(body);containerEl.appendChild(root);},unmount:function(containerEl){containerEl.innerHTML='';}};", + "window.VerstakPluginRegister('verstak.activity',{components:{ActivityView:ActivityView}});", + "})();" + ].join(''); + } + + function browserInboxBundle() { + return [ + "(function(){", + "var BrowserInboxView={mount:function(containerEl){containerEl.innerHTML='';var root=document.createElement('div');root.className='browser-inbox-root';root.setAttribute('data-plugin-id','verstak.browser-inbox');var title=document.createElement('h2');title.textContent='Browser Inbox';var body=document.createElement('div');body.textContent='Global browser inbox';root.appendChild(title);root.appendChild(body);containerEl.appendChild(root);},unmount:function(containerEl){containerEl.innerHTML='';}};", + "window.VerstakPluginRegister('verstak.browser-inbox',{components:{BrowserInboxView:BrowserInboxView}});", + "})();" + ].join(''); + } + function platformTestBundle() { return [ "(function(){", @@ -1152,6 +1218,12 @@ if (pluginId === 'verstak.files' && assetPath === 'frontend/dist/index.js') { return Promise.resolve(filesPluginBundle()); } + if (pluginId === 'verstak.activity' && assetPath === 'frontend/dist/index.js') { + return Promise.resolve(activityBundle()); + } + if (pluginId === 'verstak.browser-inbox' && assetPath === 'frontend/dist/index.js') { + return Promise.resolve(browserInboxBundle()); + } return Promise.resolve(''); }, GetPluginCapability: function (pluginId, capId) { @@ -1583,10 +1655,58 @@ }, rootPath: '/tmp/verstak-test/plugins/sync', error: '' + }, + 'verstak.activity': { + status: 'loaded', + enabled: true, + manifest: { + schemaVersion: 1, + id: 'verstak.activity', + name: 'Activity', + version: '0.1.0', + apiVersion: '0.1.0', + description: 'Workspace-scoped activity log for public plugin events.', + source: 'official', + icon: 'activity', + provides: ['activity.log', 'activity.provider', 'activity.reconstruction'], + permissions: ['events.subscribe', 'storage.namespace', 'ui.register'], + frontend: { entry: 'frontend/dist/index.js' }, + contributes: { + views: [{ id: 'verstak.activity.view', title: 'Activity', icon: 'activity', component: 'ActivityView' }], + sidebarItems: [{ id: 'verstak.activity.sidebar', title: 'Activity', icon: 'activity', view: 'verstak.activity.view', position: 20 }], + workspaceItems: [{ id: 'verstak.activity.workspace', title: 'Activity', icon: 'activity', component: 'ActivityView' }] + } + }, + rootPath: '/tmp/verstak-test/plugins/activity', + error: '' + }, + 'verstak.browser-inbox': { + status: 'loaded', + enabled: true, + manifest: { + schemaVersion: 1, + id: 'verstak.browser-inbox', + name: 'Browser Inbox', + version: '0.1.0', + apiVersion: '0.1.0', + description: 'Workspace-scoped inbox for browser captures.', + source: 'official', + icon: 'inbox', + provides: ['browser.inbox'], + permissions: ['events.subscribe', 'storage.namespace', 'ui.register'], + frontend: { entry: 'frontend/dist/index.js' }, + contributes: { + views: [{ id: 'verstak.browser-inbox.view', title: 'Browser Inbox', icon: 'inbox', component: 'BrowserInboxView' }], + sidebarItems: [{ id: 'verstak.browser-inbox.sidebar', title: 'Browser Inbox', icon: 'inbox', view: 'verstak.browser-inbox.view', position: 30 }], + workspaceItems: [{ id: 'verstak.browser-inbox.workspace', title: 'Browser Inbox', icon: 'inbox', component: 'BrowserInboxView' }] + } + }, + rootPath: '/tmp/verstak-test/plugins/browser-inbox', + error: '' } }; vaultStatus = { status: 'open', path: '/tmp/verstak-test/vault', vaultId: 'test-vault-001' }; - vaultPluginState = { enabledPlugins: ['verstak.platform-test', 'verstak.default-editor', 'verstak.files', 'verstak.sync'], disabledPlugins: [], desiredPlugins: [{ id: 'verstak.platform-test', version: '0.1.0', source: 'official' }, { id: 'verstak.default-editor', version: '0.1.0', source: 'official' }, { id: 'verstak.files', version: '0.1.0', source: 'official' }, { id: 'verstak.sync', version: '0.1.0', source: 'official' }] }; + vaultPluginState = { enabledPlugins: ['verstak.platform-test', 'verstak.default-editor', 'verstak.files', 'verstak.sync', 'verstak.activity', 'verstak.browser-inbox'], disabledPlugins: [], desiredPlugins: [{ id: 'verstak.platform-test', version: '0.1.0', source: 'official' }, { id: 'verstak.default-editor', version: '0.1.0', source: 'official' }, { id: 'verstak.files', version: '0.1.0', source: 'official' }, { id: 'verstak.sync', version: '0.1.0', source: 'official' }, { id: 'verstak.activity', version: '0.1.0', source: 'official' }, { id: 'verstak.browser-inbox', version: '0.1.0', source: 'official' }] }; appSettings = { currentVaultPath: '/tmp/verstak-test/vault', recentVaults: [] }; workbenchPreferences = {}; openedResources = []; diff --git a/frontend/src/lib/ui/Icon.svelte b/frontend/src/lib/ui/Icon.svelte index ef99109..dd9f636 100644 --- a/frontend/src/lib/ui/Icon.svelte +++ b/frontend/src/lib/ui/Icon.svelte @@ -11,6 +11,7 @@ * - NO dependency on system icon fonts * - If name is not found, renders a default circle icon */ + import Activity from 'lucide-svelte/icons/activity'; import Briefcase from 'lucide-svelte/icons/briefcase'; import ChevronDown from 'lucide-svelte/icons/chevron-down'; import ChevronLeft from 'lucide-svelte/icons/chevron-left'; @@ -18,6 +19,7 @@ import Circle from 'lucide-svelte/icons/circle'; import FlaskConical from 'lucide-svelte/icons/flask-conical'; import Folder from 'lucide-svelte/icons/folder'; + import Inbox from 'lucide-svelte/icons/inbox'; import LayoutGrid from 'lucide-svelte/icons/layout-grid'; import PanelsTopLeft from 'lucide-svelte/icons/panels-top-left'; import Pencil from 'lucide-svelte/icons/pencil'; @@ -34,6 +36,7 @@ export let className = ''; const icons = { + activity: Activity, case: Briefcase, chevronDown: ChevronDown, chevronLeft: ChevronLeft, @@ -43,6 +46,7 @@ flask: FlaskConical, folder: Folder, gear: Settings, + inbox: Inbox, logo: PanelsTopLeft, plugin: Plug, puzzle: Puzzle,