Improve Today workspace resume surface
This commit is contained in:
parent
80e53ff252
commit
67468e92f7
|
|
@ -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 });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 @@
|
|||
<button type="button" on:click={loadToday}>Refresh</button>
|
||||
</div>
|
||||
|
||||
<div class="today-summary" aria-label="Today summary">
|
||||
{#each summaryItems as item}
|
||||
<div class="today-summary-item" data-today-summary={item.key}>
|
||||
<strong>{loading ? '...' : item.count}</strong>
|
||||
<span>{item.label}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<section class="today-resume" data-today-section="resume">
|
||||
<div class="today-resume-copy">
|
||||
<span>Resume next</span>
|
||||
{#if loading}
|
||||
<strong>Loading workspace signals...</strong>
|
||||
<p>Recent captures, activity, and worklog suggestions will appear here.</p>
|
||||
{:else if resumeItem}
|
||||
<strong>{resumeItem.title}</strong>
|
||||
<p>{resumeItem.meta}</p>
|
||||
{:else}
|
||||
<strong>No pending workspace signals</strong>
|
||||
<p>Start with files, capture something from the browser, or review plugin activity.</p>
|
||||
{/if}
|
||||
</div>
|
||||
{#if resumeItem}
|
||||
<button type="button" data-today-action="resume-primary" on:click={() => openTool(resumeItem.kind)}>{resumeItem.action}</button>
|
||||
{:else if hasFiles}
|
||||
<button type="button" data-today-action="resume-primary" on:click={() => openTool('files')}>Open Files</button>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<div class="today-grid">
|
||||
<section class="today-panel" data-today-section="captured">
|
||||
<div class="today-panel-head">
|
||||
|
|
@ -231,6 +298,83 @@
|
|||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.today-summary {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 0.75rem 0;
|
||||
}
|
||||
|
||||
.today-summary-item {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
gap: 0.15rem;
|
||||
padding: 0.65rem 0.75rem;
|
||||
border: 1px solid rgba(78, 204, 163, 0.16);
|
||||
border-radius: 8px;
|
||||
background: #121a2c;
|
||||
}
|
||||
|
||||
.today-summary-item strong {
|
||||
color: #f4f7fb;
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.today-summary-item span {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: #8b8ba8;
|
||||
font-size: 0.74rem;
|
||||
}
|
||||
|
||||
.today-resume {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
margin: 0.75rem 0.75rem 0;
|
||||
padding: 0.85rem 1rem;
|
||||
border: 1px solid rgba(78, 204, 163, 0.24);
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(135deg, rgba(78, 204, 163, 0.11), rgba(15, 52, 96, 0.14));
|
||||
}
|
||||
|
||||
.today-resume-copy {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
gap: 0.22rem;
|
||||
}
|
||||
|
||||
.today-resume-copy span {
|
||||
color: #4ecca3;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.today-resume-copy strong {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: #f4f7fb;
|
||||
font-size: 0.98rem;
|
||||
}
|
||||
|
||||
.today-resume-copy p {
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: #9aa4bd;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.today-panel {
|
||||
min-width: 0;
|
||||
min-height: 10rem;
|
||||
|
|
@ -323,5 +467,18 @@
|
|||
.today-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.today-summary {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.today-resume {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.today-resume button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in New Issue