From f2ed4ced67739e95356f4ab0ce25a8c725603e58 Mon Sep 17 00:00:00 2001 From: liuxiaocs7 Date: Sun, 6 Sep 2026 19:16:11 +0800 Subject: [PATCH 1/3] fix(desktop): keep WorkHub chat bubbles on --radius-chat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WorkHub's conversation surface passed `density="compact"` into Astryx's `ChatMessage` / `ChatMessageList`, and `ChatMessageBubble` maps that to `border-radius: var(--radius-container)` (12px). The WorkHub composer stayed on `--radius-chat` (28px), so a bubble and the dock on the same surface disagreed by more than 2x. The main transcript avoided this — #3452 dropped the compact density from its `ChatMessageList`; WorkHub reintroduced it in #3497. Drop `density="compact"` from WorkHub's chat primitives — both `ChatMessageList` calls and the user/assistant `ChatMessage` rows in `WorkHubMessageFrame` — so the bubble falls back to the primitive's `--radius-chat` and rounds together with the composer as one conversation surface, exactly as #3452 fixed the main transcript. `gap={4}` stays: it sets the row gap explicitly and never depended on density. The existing `SubmittedWorkKeepsTargetMetadataInside` story already renders the projected user bubble; its play now also pins the bubble's radius to a `--radius-chat` probe (measured, not read back — ink-ladder-contract forbids the latter), so an upstream token change moves both or fails here. Fixes #4914 Generated-by: Claude Code --- apps/desktop/src/renderer/workhub-surface.tsx | 6 ++---- apps/desktop/stories/workhub.stories.tsx | 21 +++++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src/renderer/workhub-surface.tsx b/apps/desktop/src/renderer/workhub-surface.tsx index 8a7cf93ad2..ded6df065b 100644 --- a/apps/desktop/src/renderer/workhub-surface.tsx +++ b/apps/desktop/src/renderer/workhub-surface.tsx @@ -398,7 +398,6 @@ export function WorkHubSurface(props: {
@@ -508,7 +507,6 @@ export function WorkHubCoordinationStatus(props: {
@@ -769,12 +767,12 @@ function WorkHubMessageFrame(props: { data-state={props.state} data-link-state={props.linkState} > - +

{props.text}

- + ( + '.workhub-projected-turn .workhub-user-bubble', + ); + const bubbleRow = bubble?.parentElement; + if (!bubble || !bubbleRow) throw new Error('WorkHub user bubble is missing'); + const probe = document.createElement('div'); + probe.style.borderRadius = 'var(--radius-chat)'; + bubbleRow.append(probe); + const chatRadius = getComputedStyle(probe).borderTopLeftRadius; + probe.remove(); + expect(chatRadius).not.toBe('0px'); + expect(getComputedStyle(bubble).borderTopLeftRadius).toBe(chatRadius); }, }; From 579323ecfd0153c26d02f8b4d37888cb5e551a58 Mon Sep 17 00:00:00 2001 From: Xiao Liu Date: Sun, 6 Sep 2026 22:28:58 +0800 Subject: [PATCH 2/3] test(desktop): move WorkHub bubble-radius contract to the E2E harness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FIDELITY.md keeps geometry contracts out of a Storybook `play`, and #3452 — the sibling chat-radius fix this PR mirrors — put its regression in the E2E harness. Drop the story-play probe added earlier and assert the pairing where it belongs: in workhub-reconstruction.spec.ts, comparing the projected user bubble's computed border radius against the real composer plate rather than a token literal, so an upstream `--radius-chat` change moves both or fails here. Generated-by: Claude Code --- .../e2e/workhub-reconstruction.spec.ts | 18 ++++++++++++++++ apps/desktop/stories/workhub.stories.tsx | 21 ------------------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/apps/desktop/e2e/workhub-reconstruction.spec.ts b/apps/desktop/e2e/workhub-reconstruction.spec.ts index 397ca975e3..4535791527 100644 --- a/apps/desktop/e2e/workhub-reconstruction.spec.ts +++ b/apps/desktop/e2e/workhub-reconstruction.spec.ts @@ -74,6 +74,24 @@ test('WorkHub rebuilds delegated execution feedback after navigating away and ba page.locator('.workhub-projected-turn', { hasText: routedPrompt }) .locator('.workhub-submitted-state'), ).toHaveText('关联有效 · 已完成'); + + // #4914: WorkHub's conversation is one surface — the user bubble rounds like + // the composer plate beneath it, both resolving Astryx's `--radius-chat`. + // `density="compact"` on WorkHub's chat primitives had pinned the bubble to + // `--radius-container` (12px) while the composer stayed 28px. Compared against + // the real composer plate rather than a literal, so an upstream `--radius-chat` + // change moves both or fails here. + const bubbleRadii = await page.evaluate(() => { + const round = (element: Element | null) => + element ? getComputedStyle(element).borderTopLeftRadius : null; + return { + bubble: round(document.querySelector('.workhub-projected-turn .workhub-user-bubble')), + composerPlate: round(document.querySelector('.workhub-surface .maka-composer-astryx > *')), + }; + }); + expect(bubbleRadii.bubble).toBeTruthy(); + expect(bubbleRadii.bubble).not.toBe('0px'); + expect(bubbleRadii.bubble).toBe(bubbleRadii.composerPlate); }); test('WorkHub replaces the exact linked delegation across Sessions', async ({ diff --git a/apps/desktop/stories/workhub.stories.tsx b/apps/desktop/stories/workhub.stories.tsx index 4af3e3b6be..be53b4e57e 100644 --- a/apps/desktop/stories/workhub.stories.tsx +++ b/apps/desktop/stories/workhub.stories.tsx @@ -134,26 +134,5 @@ export const SubmittedWorkKeepsTargetMetadataInside: Story = { expect(button.getBoundingClientRect().bottom).toBeGreaterThanOrEqual( project.getBoundingClientRect().bottom, ); - - // #4914: WorkHub's conversation is one surface — the user bubble rounds - // like the composer beneath it, both resolving Astryx's `--radius-chat` - // (28px). `density="compact"` on WorkHub's chat primitives swapped the - // bubble to `--radius-container` (12px) while the composer stayed 28px, - // splitting a transcript and a dock on the same surface by more than 2x. - // Measure a probe the token paints rather than reading the token back — - // ink-ladder-contract.test.ts forbids the latter — so an upstream - // `--radius-chat` change moves both or fails here. - const bubble = canvasElement.querySelector( - '.workhub-projected-turn .workhub-user-bubble', - ); - const bubbleRow = bubble?.parentElement; - if (!bubble || !bubbleRow) throw new Error('WorkHub user bubble is missing'); - const probe = document.createElement('div'); - probe.style.borderRadius = 'var(--radius-chat)'; - bubbleRow.append(probe); - const chatRadius = getComputedStyle(probe).borderTopLeftRadius; - probe.remove(); - expect(chatRadius).not.toBe('0px'); - expect(getComputedStyle(bubble).borderTopLeftRadius).toBe(chatRadius); }, }; From 531249fddf2bace53f7f5d995b391aca27e1cfaf Mon Sep 17 00:00:00 2001 From: Xiao Liu Date: Sun, 6 Sep 2026 22:56:49 +0800 Subject: [PATCH 3/3] test(desktop): assert WorkHub bubble radius in the story play MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit e2e-budget.json keeps layout geometry and pure CSS in a Storybook `play`, not the Electron tier, and #4877 already moved the sibling Side Chat / WorkHub geometry there while deleting the old E2E radius checks. Revert the E2E assertion added in the previous commit and put the contract in the existing SubmittedWorkKeepsTargetMetadataInside play, comparing the real user bubble's computed radius against the real composer plate — so the bubble and the dock on one surface must round together or this fails. Generated-by: Claude Code --- .../desktop/e2e/workhub-reconstruction.spec.ts | 18 ------------------ apps/desktop/stories/workhub.stories.tsx | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/apps/desktop/e2e/workhub-reconstruction.spec.ts b/apps/desktop/e2e/workhub-reconstruction.spec.ts index 4535791527..397ca975e3 100644 --- a/apps/desktop/e2e/workhub-reconstruction.spec.ts +++ b/apps/desktop/e2e/workhub-reconstruction.spec.ts @@ -74,24 +74,6 @@ test('WorkHub rebuilds delegated execution feedback after navigating away and ba page.locator('.workhub-projected-turn', { hasText: routedPrompt }) .locator('.workhub-submitted-state'), ).toHaveText('关联有效 · 已完成'); - - // #4914: WorkHub's conversation is one surface — the user bubble rounds like - // the composer plate beneath it, both resolving Astryx's `--radius-chat`. - // `density="compact"` on WorkHub's chat primitives had pinned the bubble to - // `--radius-container` (12px) while the composer stayed 28px. Compared against - // the real composer plate rather than a literal, so an upstream `--radius-chat` - // change moves both or fails here. - const bubbleRadii = await page.evaluate(() => { - const round = (element: Element | null) => - element ? getComputedStyle(element).borderTopLeftRadius : null; - return { - bubble: round(document.querySelector('.workhub-projected-turn .workhub-user-bubble')), - composerPlate: round(document.querySelector('.workhub-surface .maka-composer-astryx > *')), - }; - }); - expect(bubbleRadii.bubble).toBeTruthy(); - expect(bubbleRadii.bubble).not.toBe('0px'); - expect(bubbleRadii.bubble).toBe(bubbleRadii.composerPlate); }); test('WorkHub replaces the exact linked delegation across Sessions', async ({ diff --git a/apps/desktop/stories/workhub.stories.tsx b/apps/desktop/stories/workhub.stories.tsx index be53b4e57e..610f5d637e 100644 --- a/apps/desktop/stories/workhub.stories.tsx +++ b/apps/desktop/stories/workhub.stories.tsx @@ -134,5 +134,21 @@ export const SubmittedWorkKeepsTargetMetadataInside: Story = { expect(button.getBoundingClientRect().bottom).toBeGreaterThanOrEqual( project.getBoundingClientRect().bottom, ); + + // #4914: WorkHub's conversation is one surface — the user bubble rounds + // like the composer plate beneath it, both resolving Astryx's + // `--radius-chat`. `density="compact"` on WorkHub's chat primitives had + // pinned the bubble to `--radius-container` (12px) while the composer + // stayed 28px, splitting a transcript and a dock on the same surface by + // more than 2x. Compared against the real composer plate, not a literal, + // so an upstream `--radius-chat` change moves both or fails here. + const bubble = canvasElement.querySelector( + '.workhub-projected-turn .workhub-user-bubble', + ); + const plate = canvasElement.querySelector('.maka-composer-astryx')?.firstElementChild; + if (!bubble || !plate) throw new Error('WorkHub bubble or composer plate is missing'); + const bubbleRadius = getComputedStyle(bubble).borderTopLeftRadius; + expect(bubbleRadius).not.toBe('0px'); + expect(bubbleRadius).toBe(getComputedStyle(plate).borderTopLeftRadius); }, };