diff --git a/frontend/e2e/ux-today.spec.js b/frontend/e2e/ux-today.spec.js index cd2e753..f585b1b 100644 --- a/frontend/e2e/ux-today.spec.js +++ b/frontend/e2e/ux-today.spec.js @@ -39,4 +39,68 @@ test.describe('UX Today workspace flow', () => { await expect(page.getByRole('tab', { name: 'Browser Inbox' })).toHaveAttribute('aria-selected', 'true'); await expect(page.locator('.browser-inbox-root')).toBeVisible({ timeout: 10000 }); }); + + test('Today summarizes available work and highlights the next resume item', async ({ page }) => { + await page.evaluate(async () => { + await window.go.api.App.WritePluginSettings('verstak.browser-inbox', { + 'captures:workspace:Project': [ + { + captureId: 'today-capture-1', + capturedAt: '2026-06-30T08:00:00.000Z', + kind: 'page', + url: 'https://example.com/research', + title: 'Research Report', + domain: 'example.com', + workspaceRootPath: 'Project', + }, + { + captureId: 'today-capture-2', + capturedAt: '2026-06-30T08:15:00.000Z', + kind: 'selection', + title: 'Quote to process', + domain: 'example.com', + workspaceRootPath: 'Project', + }, + ], + }); + await window.go.api.App.WritePluginSettings('verstak.activity', { + 'events:workspace:Project': [ + { + activityId: 'today-activity-1', + occurredAt: '2026-06-30T08:25:00.000Z', + type: 'note.saved', + title: 'Saved research note', + summary: 'Project/Notes/Research.md', + workspaceRootPath: 'Project', + }, + ], + }); + await window.go.api.App.WritePluginSettings('verstak.journal', { + 'suggestions:workspace:Project': [ + { + entryId: 'today-worklog-1', + title: 'Write project summary', + summary: 'Turn recent captures into a worklog entry', + minutes: 35, + workspaceRootPath: 'Project', + }, + ], + }); + }); + + await page.locator('.today-header').getByRole('button', { name: 'Refresh' }).click(); + + const today = page.locator('.today-root'); + await expect(today.locator('[data-today-summary="captures"]')).toContainText('2'); + await expect(today.locator('[data-today-summary="activity"]')).toContainText('1'); + await expect(today.locator('[data-today-summary="worklog"]')).toContainText('1'); + + const resume = today.locator('[data-today-section="resume"]'); + await expect(resume).toContainText('Resume next'); + await expect(resume).toContainText('Research Report'); + await resume.locator('[data-today-action="resume-primary"]').click(); + + await expect(page.getByRole('tab', { name: 'Browser Inbox' })).toHaveAttribute('aria-selected', 'true'); + await expect(page.locator('.browser-inbox-root')).toBeVisible({ timeout: 10000 }); + }); }); diff --git a/frontend/src/lib/shell/TodaySurface.svelte b/frontend/src/lib/shell/TodaySurface.svelte index 9de76af..7cd7f5a 100644 --- a/frontend/src/lib/shell/TodaySurface.svelte +++ b/frontend/src/lib/shell/TodaySurface.svelte @@ -16,6 +16,12 @@ $: hasBrowserInbox = hasTool('browser inbox') || hasTool('inbox'); $: hasActivity = hasTool('activity'); $: hasFiles = hasTool('files'); + $: summaryItems = [ + { key: 'captures', label: 'Captured', count: captures.length }, + { key: 'activity', label: 'Activity', count: activity.length }, + { key: 'worklog', label: 'Worklog', count: worklogSuggestions.length }, + ]; + $: resumeItem = nextResumeItem(captures, worklogSuggestions, activity, hasActivity); onMount(() => { loadToday(); @@ -66,6 +72,37 @@ return item.title || item.summary || item.date || item.entryId || 'Worklog item'; } + function nextResumeItem(captureRows, worklogRows, activityRows, activityAvailable) { + if (captureRows.length > 0) { + const capture = captureRows[0]; + return { + title: captureTitle(capture), + meta: capture.url || capture.domain || capture.kind || 'Browser capture', + kind: 'browser-inbox', + action: 'Open Inbox', + }; + } + if (worklogRows.length > 0) { + const item = worklogRows[0]; + return { + title: worklogTitle(item), + meta: item.minutes ? item.minutes + ' min suggested' : item.date || 'Suggested worklog', + kind: 'activity', + action: activityAvailable ? 'Review Activity' : 'Refresh', + }; + } + if (activityRows.length > 0) { + const item = activityRows[0]; + return { + title: activityTitle(item), + meta: item.occurredAt || item.receivedAt || item.type || 'Activity event', + kind: 'activity', + action: 'Open Activity', + }; + } + return null; + } + async function loadToday() { loading = true; const [browserSettings, activitySettings, journalSettings] = await Promise.all([ @@ -110,6 +147,36 @@ +
Recent captures, activity, and worklog suggestions will appear here.
+ {:else if resumeItem} + {resumeItem.title} +{resumeItem.meta}
+ {:else} + No pending workspace signals +Start with files, capture something from the browser, or review plugin activity.
+ {/if} +