feat: replace Today workspace surface with Overview

This commit is contained in:
2026-07-10 02:36:00 +08:00
parent 5b4c792de5
commit b845e8b5e2
7 changed files with 686 additions and 234 deletions
+1 -1
View File
@@ -52,7 +52,7 @@ test.describe('Command Palette', () => {
const palette = page.locator('.command-palette');
await expect(palette).toBeVisible();
const items = palette.locator('.command-palette-item');
await expect(items.nth(0)).toHaveAttribute('data-command-id', 'verstak.shell.open-today');
await expect(items.nth(0)).toHaveAttribute('data-command-id', 'verstak.shell.open-overview');
await expect(items.nth(1)).toHaveAttribute('data-command-id', 'verstak.shell.open-files');
await expect(items.nth(2)).toHaveAttribute('data-command-id', 'verstak.shell.open-activity');
await expect(items.nth(3)).toHaveAttribute('data-command-id', 'verstak.shell.open-browser-inbox');
+5 -5
View File
@@ -99,19 +99,19 @@ test.describe('E: Plugin Manager layout', () => {
await expect(selected).toHaveText('Test');
});
test('workspace tools render Today first with Files as one tab', async ({ page }) => {
test('workspace tools render Overview first with Files as one tab', async ({ page }) => {
await page.locator('.wt-label').filter({ hasText: 'Project' }).click();
const tabs = page.locator('.workspace-tabs');
await expect(tabs).toBeVisible({ timeout: 10000 });
const todayTab = tabs.locator('[role="tab"]').filter({ hasText: 'Today' });
const overviewTab = tabs.locator('[role="tab"]').filter({ hasText: 'Overview' });
const filesTab = tabs.locator('[role="tab"]').filter({ hasText: 'Files' });
await expect(todayTab).toBeVisible();
await expect(todayTab).toHaveAttribute('aria-selected', 'true');
await expect(overviewTab).toBeVisible();
await expect(overviewTab).toHaveAttribute('aria-selected', 'true');
await expect(filesTab).toBeVisible();
await expect(filesTab).toHaveAttribute('aria-selected', 'false');
await expect(page.locator('.workspace-tool')).toHaveCount(0);
await expect(page.locator('.today-root')).toBeVisible();
await expect(page.locator('[data-overview-root]')).toBeVisible();
await filesTab.click();
await expect(page.locator('.files-root')).toBeVisible();
+1 -1
View File
@@ -99,7 +99,7 @@ test.describe('UX follow-up fixes', () => {
expect(workspaceBox.width).toBeGreaterThan(340);
expect(workspaceBox.y).toBeGreaterThan(sidebarBox.y + sidebarBox.height - 1);
await expect(page.locator('.workspace-header [data-global-search-input]')).toBeVisible();
await expect(page.getByRole('tab', { name: 'Today' })).toBeVisible();
await expect(page.getByRole('tab', { name: 'Overview' })).toBeVisible();
const hasHorizontalOverflow = await page.evaluate(() => document.documentElement.scrollWidth > window.innerWidth);
expect(hasHorizontalOverflow).toBe(false);
+103 -33
View File
@@ -1,7 +1,7 @@
import { test, expect } from '@playwright/test';
import { waitForAppReady, setupConsoleCollector, resetMockState } from './helpers.js';
test.describe('UX Today workspace flow', () => {
test.describe('UX Overview workspace flow', () => {
let consoleCollector;
test.beforeEach(async ({ page }) => {
@@ -15,37 +15,40 @@ test.describe('UX Today workspace flow', () => {
consoleCollector.assertNoErrors();
});
test('workspace opens with Today before plugin tools', async ({ page }) => {
test('workspace opens with Overview before plugin tools', async ({ page }) => {
await expect(page.locator('.workspace-host')).toBeVisible({ timeout: 10000 });
const tabs = page.getByRole('tab');
await expect(tabs.nth(0)).toHaveText('Today');
await expect(tabs.nth(0)).toHaveText('Overview');
await expect(tabs.nth(1)).toHaveText('Files');
await expect(page.getByRole('tab', { name: 'Today' })).toHaveAttribute('aria-selected', 'true');
await expect(page.getByRole('tab', { name: 'Overview' })).toHaveAttribute('aria-selected', 'true');
await expect(page.getByRole('tab', { name: 'Today' })).toHaveCount(0);
const today = page.locator('.today-root');
await expect(today).toBeVisible();
await expect(today.locator('[data-today-section="captured"]')).toContainText('Captured');
await expect(today.locator('[data-today-section="activity"]')).toContainText('Recent Activity');
await expect(today.locator('[data-today-section="worklog"]')).toContainText('Worklog Suggestions');
await expect(today.locator('[data-today-section="quick-actions"]')).toContainText('Quick Actions');
await expect(today).toContainText('No browser captures yet');
await expect(today).toContainText('No activity events yet');
const overview = page.locator('[data-overview-root]');
await expect(overview).toBeVisible();
await expect(overview.locator('[data-overview-section="continue"]')).toContainText('Continue working');
await expect(overview.locator('[data-overview-section="recent"]')).toContainText('Recent changes');
await expect(overview.locator('[data-overview-section="attention"]')).toContainText('Needs attention');
await expect(overview.locator('[data-overview-section="quick-actions"]')).toContainText('Quick actions');
await expect(overview.locator('[data-overview-summary="notes"]')).toContainText('recent changes');
await expect(overview.locator('[data-overview-summary="captures"]')).toContainText('unprocessed captures');
await expect(overview).toContainText('No clear resume point yet');
await expect(overview).toContainText('No meaningful changes for this filter yet');
});
test('Today quick action opens Browser Inbox workspace tool', async ({ page }) => {
await page.locator('[data-today-action="browser-inbox"]').click();
test('Overview quick action opens Browser Inbox workspace tool', async ({ page }) => {
await page.locator('[data-overview-action="browser-inbox"]').click();
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 }) => {
test('Overview prioritizes resume work and filters meaningful recent changes', async ({ page }) => {
await page.evaluate(async () => {
await window.go.api.App.WritePluginSettings('verstak.browser-inbox', {
'captures:workspace:Project': [
{
captureId: 'today-capture-1',
captureId: 'overview-capture-1',
capturedAt: '2026-06-30T08:00:00.000Z',
kind: 'page',
url: 'https://example.com/research',
@@ -54,7 +57,7 @@ test.describe('UX Today workspace flow', () => {
workspaceRootPath: 'Project',
},
{
captureId: 'today-capture-2',
captureId: 'overview-capture-2',
capturedAt: '2026-06-30T08:15:00.000Z',
kind: 'selection',
title: 'Quote to process',
@@ -66,41 +69,108 @@ test.describe('UX Today workspace flow', () => {
await window.go.api.App.WritePluginSettings('verstak.activity', {
'events:workspace:Project': [
{
activityId: 'today-activity-1',
activityId: 'overview-selected-file',
occurredAt: '2026-06-30T08:50:00.000Z',
type: 'file.selected',
title: 'Selected file',
summary: 'Project/draft.md',
workspaceRootPath: 'Project',
},
{
activityId: 'overview-opened-file',
occurredAt: '2026-06-30T08:40:00.000Z',
type: 'file.opened',
title: 'draft.md',
summary: 'Project/draft.md',
workspaceRootPath: 'Project',
},
{
activityId: 'overview-note',
occurredAt: '2026-06-30T08:25:00.000Z',
type: 'note.saved',
title: 'Saved research note',
summary: 'Project/Notes/Research.md',
title: 'Overview',
summary: 'Project/Notes/Overview.md',
workspaceRootPath: 'Project',
},
{
activityId: 'overview-file',
occurredAt: '2026-06-30T08:20:00.000Z',
type: 'file.changed',
title: 'draft.md',
summary: 'Project/draft.md',
workspaceRootPath: 'Project',
},
{
activityId: 'overview-workspace-selected',
occurredAt: '2026-06-30T08:10:00.000Z',
type: 'case.selected',
title: 'Workspace selected',
workspaceRootPath: 'Project',
},
],
});
await window.go.api.App.WritePluginSettings('verstak.journal', {
'suggestions:workspace:Project': [
'worklog:workspace:Project': [
{
entryId: 'today-worklog-1',
entryId: 'overview-journal-1',
date: '2026-06-30',
title: 'Write project summary',
summary: 'Turn recent captures into a worklog entry',
minutes: 35,
workspaceRootPath: 'Project',
},
],
'suggestions:workspace:Project': [
{
suggestionId: 'overview-suggestion-1',
date: '2026-06-30',
title: 'Project work on 2026-06-30',
minutes: 50,
workspaceRootPath: 'Project',
},
],
});
});
await page.locator('.today-header').getByRole('button', { name: 'Refresh' }).click();
await page.locator('[data-overview-action="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 overview = page.locator('[data-overview-root]');
await expect(overview.locator('[data-overview-summary="notes"]')).toContainText('1');
await expect(overview.locator('[data-overview-summary="files"]')).toContainText('1');
await expect(overview.locator('[data-overview-summary="captures"]')).toContainText('2');
await expect(overview.locator('[data-overview-summary="journal"]')).toContainText('1');
await expect(overview.locator('[data-overview-summary="attention"]')).toContainText('3');
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();
const resume = overview.locator('[data-overview-section="continue"]');
await expect(resume).toContainText('Opened file "draft.md"');
await expect(resume).not.toContainText('Research Report');
await resume.locator('[data-overview-action="continue-primary"]').click();
await expect(page.getByRole('tab', { name: 'Browser Inbox' })).toHaveAttribute('aria-selected', 'true');
await expect(page.locator('.browser-inbox-root')).toBeVisible({ timeout: 10000 });
await expect(page.getByRole('tab', { name: 'Files' })).toHaveAttribute('aria-selected', 'true');
await expect(page.locator('.files-root')).toBeVisible({ timeout: 10000 });
await page.getByRole('tab', { name: 'Overview' }).click();
const recent = overview.locator('[data-overview-section="recent"]');
await expect(recent).toContainText('Edited note "Overview"');
await expect(recent).toContainText('Changed file "draft.md"');
await expect(recent).toContainText('Captured page "Research Report"');
await expect(recent).toContainText('Added journal entry "Write project summary"');
await expect(recent).not.toContainText('Selected file');
await expect(recent).not.toContainText('Workspace selected');
await expect(recent).not.toContainText('file.opened');
await overview.locator('[data-overview-filter="notes"]').click();
await expect(recent).toContainText('Edited note "Overview"');
await expect(recent).not.toContainText('Changed file "draft.md"');
await expect(recent).not.toContainText('Research Report');
await overview.locator('[data-overview-filter="captures"]').click();
await expect(recent).toContainText('Captured page "Research Report"');
await expect(recent).toContainText('Captured selection "Quote to process"');
await expect(recent).not.toContainText('Edited note "Overview"');
await overview.locator('[data-overview-filter="journal"]').click();
await expect(recent).toContainText('Added journal entry "Write project summary"');
await expect(recent).not.toContainText('Changed file "draft.md"');
});
});