fix: P1 shell tooltip fixes + Today routing + workspaces API

Shell:
- WorkspaceHost: change tooltip from pluginId to human-readable title
- StatusBar: replace pluginId tooltips with label or id (3 occurrences)
- App: startup routing to Today view, sidebar Today nav item
- Sidebar: add Today button in Overview section
- TodaySurface: separate global vs workspace data (no fallthrough)

Plugin API:
- Add workspaces.list() method wrapping App.ListWorkspaces()
  (needed by browser-inbox workspace picker)
This commit is contained in:
mirivlad 2026-07-01 04:15:35 +08:00
parent b0d80e230f
commit 9d06b164d3
6 changed files with 67 additions and 40 deletions

View File

@ -7,6 +7,7 @@
import VaultSelection from './lib/shell/VaultSelection.svelte'; import VaultSelection from './lib/shell/VaultSelection.svelte';
import WorkbenchHost from './lib/shell/WorkbenchHost.svelte'; import WorkbenchHost from './lib/shell/WorkbenchHost.svelte';
import WorkspaceHost from './lib/shell/WorkspaceHost.svelte'; import WorkspaceHost from './lib/shell/WorkspaceHost.svelte';
import TodaySurface from './lib/shell/TodaySurface.svelte';
import * as App from '../wailsjs/go/api/App'; import * as App from '../wailsjs/go/api/App';
import { debug } from './lib/log/debug.js'; import { debug } from './lib/log/debug.js';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
@ -74,33 +75,20 @@
if (err || !workspaces || workspaces.length === 0) { if (err || !workspaces || workspaces.length === 0) {
workspaceNodes = []; workspaceNodes = [];
selectedWorkspaceName = ''; selectedWorkspaceName = '';
currentView = 'workspace-empty'; currentView = 'today';
emitWorkspaceActive(''); emitWorkspaceActive('');
return; return;
} }
workspaceNodes = workspaces.map(workspaceAsNode); workspaceNodes = workspaces.map(workspaceAsNode);
let currentWorkspace = null; selectedWorkspaceName = '';
try { currentView = 'today';
currentWorkspace = await App.GetCurrentWorkspace(); emitWorkspaceActive('');
} catch {
currentWorkspace = null;
}
const currentName = workspaceName(currentWorkspace);
const selected = workspaces.find((workspace) => workspaceName(workspace) === currentName) || workspaces[0];
selectedWorkspaceName = workspaceName(selected);
if (selectedWorkspaceName) {
try { await App.SetCurrentWorkspace(selectedWorkspaceName); } catch {}
currentView = 'workspace';
} else {
currentView = 'workspace-empty';
}
emitWorkspaceActive(selectedWorkspaceName);
} catch (e) { } catch (e) {
debug.log('[App] openDefaultWorkspaceRoute ERROR', String(e)); debug.log('[App] openDefaultWorkspaceRoute ERROR', String(e));
workspaceNodes = []; workspaceNodes = [];
selectedWorkspaceName = ''; selectedWorkspaceName = '';
currentView = 'workspace-empty'; currentView = 'today';
emitWorkspaceActive(''); emitWorkspaceActive('');
} }
} }
@ -394,6 +382,12 @@
<PluginManager {activeSettingsPluginId} {activeSettingsPanelId} /> <PluginManager {activeSettingsPluginId} {activeSettingsPanelId} />
{:else if currentView === 'workbench'} {:else if currentView === 'workbench'}
<WorkbenchHost {openedResource} /> <WorkbenchHost {openedResource} />
{:else if currentView === 'today'}
<TodaySurface
workspaceRootPath=""
workspaceTitle=""
availableTools={[]}
/>
{:else if currentView === 'workspace' || currentView === 'workspace-empty'} {:else if currentView === 'workspace' || currentView === 'workspace-empty'}
<WorkspaceHost <WorkspaceHost
selectedWorkspaceName={selectedWorkspaceName} selectedWorkspaceName={selectedWorkspaceName}

View File

@ -502,6 +502,15 @@ export function createPluginAPI(pluginId) {
} }
}, },
workspaces: {
list: function() {
assertActive('workspaces.list');
return callBackend(pluginId, 'workspaces.list', function() {
return App.ListWorkspaces();
});
}
},
dispose: function() { dispose: function() {
if (disposed) return; if (disposed) return;
disposed = true; disposed = true;

View File

@ -83,6 +83,20 @@
<GlobalSearch /> <GlobalSearch />
{/if} {/if}
{#if vaultOpen}
<div class="sidebar-section">
<span class="section-label">Overview</span>
<button
class="nav-item today-item"
on:click={() => window.dispatchEvent(new CustomEvent('verstak:nav', { detail: { viewId: 'today' } }))}
type="button"
>
<span class="nav-icon-today"></span>
<span class="nav-label">Today</span>
</button>
</div>
{/if}
{#if sidebarItems.length > 0} {#if sidebarItems.length > 0}
<div class="sidebar-section"> <div class="sidebar-section">
<span class="section-label">Tools</span> <span class="section-label">Tools</span>
@ -202,6 +216,21 @@
color: #a78bfa; color: #a78bfa;
} }
.today-item {
gap: 0.55rem;
}
.nav-icon-today {
width: 0.9rem;
height: 0.9rem;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 0.85rem;
color: #4ecca3;
flex-shrink: 0;
}
.nav-label { .nav-label {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;

View File

@ -95,7 +95,7 @@
class:status-bar-warning={item.handler} class:status-bar-warning={item.handler}
class="status-bar-item" class="status-bar-item"
data-status-item-id={item.id} data-status-item-id={item.id}
title={item.handler ? `${item.pluginId}: compact status only` : item.pluginId} title={item.handler ? `${item.pluginId}: click for details` : (item.label || item.id)}
> >
{#if item.handler}<Icon name="warning" size={11} class="status-warning-icon" />{/if} {#if item.handler}<Icon name="warning" size={11} class="status-warning-icon" />{/if}
{item.label || item.id} {item.label || item.id}
@ -108,7 +108,7 @@
class:status-bar-warning={item.handler} class:status-bar-warning={item.handler}
class="status-bar-item" class="status-bar-item"
data-status-item-id={item.id} data-status-item-id={item.id}
title={item.handler ? `${item.pluginId}: compact status only` : item.pluginId} title={item.handler ? `${item.pluginId}: click for details` : (item.label || item.id)}
> >
{#if item.handler}<Icon name="warning" size={11} class="status-warning-icon" />{/if} {#if item.handler}<Icon name="warning" size={11} class="status-warning-icon" />{/if}
{item.label || item.id} {item.label || item.id}
@ -121,7 +121,7 @@
class:status-bar-warning={item.handler} class:status-bar-warning={item.handler}
class="status-bar-item" class="status-bar-item"
data-status-item-id={item.id} data-status-item-id={item.id}
title={item.handler ? `${item.pluginId}: compact status only` : item.pluginId} title={item.handler ? `${item.pluginId}: click for details` : (item.label || item.id)}
> >
{#if item.handler}<Icon name="warning" size={11} class="status-warning-icon" />{/if} {#if item.handler}<Icon name="warning" size={11} class="status-warning-icon" />{/if}
{item.label || item.id} {item.label || item.id}

View File

@ -13,6 +13,7 @@
let activity = []; let activity = [];
let worklogSuggestions = []; let worklogSuggestions = [];
$: isGlobal = !workspaceRootPath || String(workspaceRootPath).trim() === '';
$: hasBrowserInbox = hasTool('browser inbox') || hasTool('inbox'); $: hasBrowserInbox = hasTool('browser inbox') || hasTool('inbox');
$: hasActivity = hasTool('activity'); $: hasActivity = hasTool('activity');
$: hasFiles = hasTool('files'); $: hasFiles = hasTool('files');
@ -111,24 +112,18 @@
readPluginSettings('verstak.journal'), readPluginSettings('verstak.journal'),
]); ]);
captures = rowsFor(browserSettings, [ if (isGlobal) {
workspaceKey('captures:workspace:'), captures = rowsFor(browserSettings, ['captures:global', 'captures']).slice(0, 4);
'captures:global', activity = rowsFor(activitySettings, ['events:global', 'events']).slice(0, 4);
'captures', worklogSuggestions = rowsFor(journalSettings, ['suggestions', 'worklog']).slice(0, 4);
]).slice(0, 4); } else {
captures = rowsFor(browserSettings, [workspaceKey('captures:workspace:')]).slice(0, 4);
activity = rowsFor(activitySettings, [ activity = rowsFor(activitySettings, [workspaceKey('events:workspace:')]).slice(0, 4);
workspaceKey('events:workspace:'),
'events:global',
'events',
]).slice(0, 4);
worklogSuggestions = rowsFor(journalSettings, [ worklogSuggestions = rowsFor(journalSettings, [
workspaceKey('suggestions:workspace:'), workspaceKey('suggestions:workspace:'),
workspaceKey('worklog:workspace:'), workspaceKey('worklog:workspace:'),
'suggestions',
'worklog',
]).slice(0, 4); ]).slice(0, 4);
}
loading = false; loading = false;
} }

View File

@ -148,7 +148,7 @@
role="tab" role="tab"
aria-selected={toolKey(tool) === toolKey(activeTool)} aria-selected={toolKey(tool) === toolKey(activeTool)}
type="button" type="button"
title={tool.pluginId} title={tool.title}
on:click={() => selectTool(tool)} on:click={() => selectTool(tool)}
> >
{tool.title || tool.id} {tool.title || tool.id}