From 8f31665fb6ca2fccb523feb2a78fd02e64eebeb2 Mon Sep 17 00:00:00 2001 From: Zerlight Wu Date: Wed, 12 Aug 2026 22:00:57 +0800 Subject: [PATCH 1/2] fix(ui): tighten grouped activity spacing --- apps/webview/e2e/browser-smoke.e2e.mts | 45 +++++++++++++++++++ .../workbench/src/mock/data/showcase.ts | 7 +++ .../presentation/ui/src/chat/activity-run.tsx | 2 +- 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/apps/webview/e2e/browser-smoke.e2e.mts b/apps/webview/e2e/browser-smoke.e2e.mts index 10bfb975..45581d91 100644 --- a/apps/webview/e2e/browser-smoke.e2e.mts +++ b/apps/webview/e2e/browser-smoke.e2e.mts @@ -18,6 +18,7 @@ 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; @@ -126,6 +127,49 @@ 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: 'Activity details: An action failed · Ran a command · Made a file change · Explored 2 times', + }); + 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')); + assert.ok(metrics.children.some((child) => child.tagName === 'DIV')); + 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 +416,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 ( From b3c819f6de586d24290034a73ad81e4c5656c24e Mon Sep 17 00:00:00 2001 From: Zerlight Wu Date: Wed, 12 Aug 2026 23:05:26 +0800 Subject: [PATCH 2/2] test(webview): harden activity hierarchy smoke --- apps/webview/e2e/browser-smoke.e2e.mts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/webview/e2e/browser-smoke.e2e.mts b/apps/webview/e2e/browser-smoke.e2e.mts index 45581d91..aaf28456 100644 --- a/apps/webview/e2e/browser-smoke.e2e.mts +++ b/apps/webview/e2e/browser-smoke.e2e.mts @@ -22,6 +22,8 @@ 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 { @@ -132,7 +134,7 @@ async function verifyActivityRunHierarchy(page: Page): Promise { await page.locator('[data-conversation-title]', { hasText: showcaseThreadTitle }).waitFor(); const runHeader = page.getByRole('button', { - name: 'Activity details: An action failed · Ran a command · Made a file change · Explored 2 times', + name: RE_ACTIVITY_RUN_DETAILS, }); await runHeader.waitFor({ timeout: 15000 }); await runHeader.click(); @@ -160,8 +162,14 @@ async function verifyActivityRunHierarchy(page: Page): Promise { }); assert.ok(metrics.children.length >= 5, 'Mixed activity run did not render every child'); - assert.ok(metrics.children.some((child) => child.slot === 'tooltip-trigger')); - assert.ok(metrics.children.some((child) => child.tagName === 'DIV')); + 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,