diff --git a/apps/webview/e2e/browser-smoke.e2e.mts b/apps/webview/e2e/browser-smoke.e2e.mts index 10bfb975..aaf28456 100644 --- a/apps/webview/e2e/browser-smoke.e2e.mts +++ b/apps/webview/e2e/browser-smoke.e2e.mts @@ -18,9 +18,12 @@ const viteCli = fileURLToPath(new URL('../../bin/vite.js', import.meta.resolve(' const newSessionDefaultsKey = 'linkcode.workbench.new-session-defaults:v7'; const mockThreadTitle = 'Wire the workbench to the daemon'; const mockChatThreadTitle = 'Prototype without git'; +const showcaseThreadTitle = 'Mocked streaming showcase'; const longThreadTitle = 'Long thread · navigation testbed'; const longThreadTurns = 48; const maxMountedRows = 10; +const RE_ACTIVITY_RUN_DETAILS = + /^Activity details: .*failed.*ran .*command.*made .*file change.*explored.*$/iu; const RE_LONG_THREAD_TURN = /Turn (\d+) —/g; interface ViteServer { @@ -126,6 +129,55 @@ async function verifyNewChatIsolation(page: Page, appErrors: string[]): Promise< assertNoApplicationErrors(appErrors); } +async function verifyActivityRunHierarchy(page: Page): Promise { + await page.locator('[data-thread-title]', { hasText: showcaseThreadTitle }).click(); + await page.locator('[data-conversation-title]', { hasText: showcaseThreadTitle }).waitFor(); + + const runHeader = page.getByRole('button', { + name: RE_ACTIVITY_RUN_DETAILS, + }); + await runHeader.waitFor({ timeout: 15000 }); + await runHeader.click(); + + const metrics = await runHeader.evaluate((header) => { + const group = header.closest('[data-slot="collapsible"]'); + const body = group?.querySelector( + ':scope > [data-slot="collapsible-panel"] [data-slot="scroll-area-content"] > div', + ); + if (!(body instanceof HTMLElement)) throw new Error('Missing expanded activity body'); + + const children = [...body.children].map((row) => { + const child = row.firstElementChild; + if (!(child instanceof HTMLElement)) throw new Error('Missing activity child header'); + return { + paddingBlockStart: Number.parseFloat(getComputedStyle(child).paddingBlockStart), + slot: child.dataset.slot, + tagName: child.tagName, + }; + }); + return { + children, + paddingBlockStart: Number.parseFloat(getComputedStyle(header).paddingBlockStart), + }; + }); + + assert.ok(metrics.children.length >= 5, 'Mixed activity run did not render every child'); + assert.ok( + metrics.children.some((child) => child.slot === 'tooltip-trigger'), + `No tooltip-backed activity row rendered: ${JSON.stringify(metrics.children)}`, + ); + assert.ok( + metrics.children.some((child) => child.tagName === 'DIV'), + `No bodyless activity row rendered: ${JSON.stringify(metrics.children)}`, + ); + assert.ok( + metrics.children.every( + (child) => child.paddingBlockStart > 0 && child.paddingBlockStart < metrics.paddingBlockStart, + ), + `Activity children were not denser than the group: ${JSON.stringify(metrics)}`, + ); +} + async function verifyLongThreadVirtualization(page: Page): Promise { await page.evaluate( ({ flags, source }) => { @@ -372,6 +424,7 @@ async function verifyMockEntry(browser: Browser): Promise { const recoveryPrompt = `${firstPrompt}-after-reload`; await sendPrompt(page, recoveryPrompt, appErrors); await verifyNewChatIsolation(page, appErrors); + await verifyActivityRunHierarchy(page); await verifyLongThreadVirtualization(page); assertNoApplicationErrors(appErrors); await page.close(); diff --git a/packages/client/workbench/src/mock/data/showcase.ts b/packages/client/workbench/src/mock/data/showcase.ts index dca4fe44..84d81164 100644 --- a/packages/client/workbench/src/mock/data/showcase.ts +++ b/packages/client/workbench/src/mock/data/showcase.ts @@ -700,6 +700,13 @@ export function createShowcaseToolBursts(terminalId = SHOWCASE_TERMINAL_ID): Sho content: [], rawInput: { path: 'packages/presentation/ui/src/chat/activity-groups.ts' }, }, + { + toolCallId: 'mock-activity-read-bodyless', + title: 'Inspect activity boundaries', + kind: 'read', + status: 'completed', + content: [], + }, { toolCallId: 'mock-activity-execute-failed', title: 'Run compact activity check', diff --git a/packages/presentation/ui/src/chat/activity-run.tsx b/packages/presentation/ui/src/chat/activity-run.tsx index 2f6dbbba..5b185336 100644 --- a/packages/presentation/ui/src/chat/activity-run.tsx +++ b/packages/presentation/ui/src/chat/activity-run.tsx @@ -154,7 +154,7 @@ export function ActivityRun({ ) : null} - + {run.items.map((item) => { if (item.kind === 'reasoning') { return (