From ccd769c61fd81291b5443fd83520894be3398577 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Mon, 26 Oct 2026 14:32:09 +1000 Subject: [PATCH 01/10] fix(session-ui): hide a folded message's duplicate footer actions Active message transitions forced visibility: visible, so Rewind, Fork, Move to queue and Delete stayed exposed (and focusable) in the hidden footer of a folded run message, beside the copy shown next to its bubble. Active transitions now inherit visibility. Unquarantines the session-queue tests that caught it. --- .../app/e2e/regression/session-queue.spec.ts | 17 +++++++++++------ .../session-ui/src/components/message-part.css | 8 ++++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/app/e2e/regression/session-queue.spec.ts b/packages/app/e2e/regression/session-queue.spec.ts index bccaa288b5bb..2b5086d217e2 100644 --- a/packages/app/e2e/regression/session-queue.spec.ts +++ b/packages/app/e2e/regression/session-queue.spec.ts @@ -851,7 +851,7 @@ for (const delivery of ["queue", "steer"] as const) { }) } -test("reverting to another prompt replaces the file chips of an earlier restore @quarantine", async ({ page }) => { +test("reverting to another prompt replaces the file chips of an earlier restore", async ({ page }) => { const mock = createQueueMock( [], [ @@ -1012,7 +1012,7 @@ for (const [action, status] of [ }) } -test("waiting steers group under one Pending line, each keeping its own actions beside its bubble @quarantine", async ({ +test("waiting steers group under one Pending line, each keeping its own actions beside its bubble", async ({ page, }) => { const mock = createQueueMock( @@ -1028,7 +1028,7 @@ test("waiting steers group under one Pending line, each keeping its own actions await expect(first.locator('[data-component="user-message"]')).toHaveAttribute("data-pending", "true") // The run's last steer carries the one Pending line; the first folds its line away. - await expect(last.locator('[data-slot="user-message-meta"]')).toContainText("Pending") + await expect(last.locator('[data-slot="user-message-meta"]')).toContainText("Pending", { useInnerText: true }) await expect(first.locator('[data-slot="user-message-meta"]')).toBeHidden() await first.locator('[data-slot="user-message-text"]').hover() @@ -1084,7 +1084,7 @@ test("/undo returns a pending steer's review comment to the composer", async ({ }) for (const delivery of ["steer", "queue"] as const) { - test(`keeps finished tools above a pending ${delivery === "queue" ? "queue-to-steer" : "steer"} follow-up${delivery === "steer" ? " @quarantine" : ""}`, async ({ + test(`keeps finished tools above a pending ${delivery === "queue" ? "queue-to-steer" : "steer"} follow-up`, async ({ page, }, testInfo) => { const model = { id: "queue-model", providerID: "opencode" } @@ -1171,7 +1171,10 @@ for (const delivery of ["steer", "queue"] as const) { await expect(bubble).toHaveCSS("color", colors.pending.text) await expect(bubble).toHaveCSS("transition-duration", "0s") await expect(pending.locator('[data-slot="user-message-copy-wrapper"]')).toHaveCSS("opacity", "1") - await expect(pending.locator('[data-slot="user-message-meta"]')).toHaveText(/^Pending\s*·\s*Queue Model$/) + // Pending stands in for the time of a steer that has not been sent. + await expect(pending.locator('[data-slot="user-message-meta"]')).toHaveText(/^Pending\s*·\s*Queue Model$/, { + useInnerText: true, + }) await bubble.evaluate((element) => (element.dataset.deliveryMarker = "pending")) } @@ -1253,7 +1256,9 @@ for (const delivery of ["steer", "queue"] as const) { if (colors) { await expect(bubble).toHaveAttribute("data-delivery-marker", "pending") - await expect(pending.locator('[data-slot="user-message-meta"]')).toHaveText("Queue Model") + await expect(pending.locator('[data-slot="user-message-meta"]')).toHaveText(/^Queue Model\s*·\s*Just now$/, { + useInnerText: true, + }) await expect(bubble).toHaveCSS("background-color", colors.delivered.background) await expect(bubble).toHaveCSS("color", colors.delivered.text) // Only delivery animates the colour; reduced motion switches instantly. diff --git a/packages/session-ui/src/components/message-part.css b/packages/session-ui/src/components/message-part.css index 4c435321e657..41f2c6ccd223 100644 --- a/packages/session-ui/src/components/message-part.css +++ b/packages/session-ui/src/components/message-part.css @@ -339,12 +339,13 @@ visibility: hidden; margin-inline-end: calc(-1 * var(--user-message-transition-gap)); - /* Labels and actions slide in as they arrive and leave at once, like the Pending label on delivery. */ + /* Labels and actions slide in as they arrive and leave at once, like the Pending label on delivery. Active ones + inherit visibility, so a folded message's hidden footer also hides them. */ &[data-active="true"] { grid-template-columns: 1fr; margin-inline-end: 0; opacity: 1; - visibility: visible; + visibility: inherit; transition: grid-template-columns 200ms ease-out, margin-inline-end 200ms ease-out, @@ -407,8 +408,7 @@ } } - &[data-run-meta-hidden] [data-slot="user-message-copy-wrapper"], - &[data-run-meta-hidden] [data-slot="user-message-meta-tail"] { + &[data-run-meta-hidden] [data-slot="user-message-copy-wrapper"] { visibility: hidden; } From 9bf385e4d769cf30d36dfb8f8e42a14106fb1e20 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Mon, 26 Oct 2026 14:32:10 +1000 Subject: [PATCH 02/10] test(app): assert the semantic user bubble and rendered message meta #57463 replaced the blue user bubble with layer-02/text-base, and #58089 keeps the hidden Pending label and time in the meta DOM, so read rendered text. --- packages/app/e2e/regression/new-session.spec.ts | 6 +++--- .../e2e/regression/session-timeline-accessibility.spec.ts | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/app/e2e/regression/new-session.spec.ts b/packages/app/e2e/regression/new-session.spec.ts index 075f4135585f..96d3aab6df0a 100644 --- a/packages/app/e2e/regression/new-session.spec.ts +++ b/packages/app/e2e/regression/new-session.spec.ts @@ -704,7 +704,7 @@ for (const row of [ accent: false, }, ]) { - test(`keeps the user message blue for ${row.name} @quarantine`, async ({ page }) => { + test(`keeps the user message grey for ${row.name}`, async ({ page }) => { const inventory: WorktreeDirectory[] = [{ directory: accentRoot }, { directory: accentWorkspace, strategy: "git" }] const listed = page.waitForResponse( @@ -750,8 +750,8 @@ for (const row of [ await expectBackground(send, "icon-button-contrast") const message = page.locator('[data-slot="user-message-text"]') await expect(message).toHaveText("Check this fixture workspace.") - await expectToken(message, "background-color", "--v2-blue-100") - await expectToken(message, "color", "--v2-blue-700") + await expectToken(message, "background-color", "--v2-background-bg-layer-02") + await expectToken(message, "color", "--v2-text-text-base") if (!row.accent) return diff --git a/packages/app/e2e/regression/session-timeline-accessibility.spec.ts b/packages/app/e2e/regression/session-timeline-accessibility.spec.ts index 4078ce66774a..2375e103b53f 100644 --- a/packages/app/e2e/regression/session-timeline-accessibility.spec.ts +++ b/packages/app/e2e/regression/session-timeline-accessibility.spec.ts @@ -75,7 +75,7 @@ function lines(count: number) { } for (const visible of [false, true]) { - test(`${visible ? "shows" : "omits"} the agent name in message meta when agents are ${visible ? "on @quarantine" : "off"}`, async ({ + test(`${visible ? "shows" : "omits"} the agent name in message meta when agents are ${visible ? "on" : "off"}`, async ({ page, }, testInfo) => { await setupTimeline(page, { @@ -93,7 +93,8 @@ for (const visible of [false, true]) { const meta = [user.locator('[data-slot="user-message-meta"]'), assistant.locator('[data-slot="text-part-meta"]')] for (const item of meta) { - if (visible) await expect(item).toContainText(/^Build\s·\s/) + // A delivered message keeps its hidden Pending label in the DOM, so read only the rendered text. + if (visible) await expect(item).toContainText(/^Build\s*·/, { useInnerText: true }) else await expect(item).not.toContainText("Build") } }) From 6960a41789d52ca7ad8f5adc259a0994ce773575 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Mon, 26 Oct 2026 14:40:37 +1000 Subject: [PATCH 03/10] fix(app): repair app component tests broken by the beta merge - Size composer suggestion menus for their 6px padding (#58418 changed it from 8px), so history shows no clipped row under Search. - Isolate the session story's stacking like the app's chat panel, so the web search provider menu opens above the dock. - Expect the virtualizer's 82px tail (#57273, #57883) in the offset-only reconnect case. - Pause the clock and wait for fonts before measuring the activity panel, which a late Inter swap widened on Windows. - Remove the @quarantine tags from the fixed tests. --- .../component-tests/activity-chips.spec.ts | 4 + packages/app/component-tests/composer.spec.ts | 6 +- .../timeline-virtualizer.spec.ts | 88 +++++++++---------- .../app/component-tests/websearch.spec.ts | 59 ++++++------- packages/app/src/composer/editor/editor.tsx | 2 +- packages/app/src/session/story-model.tsx | 3 +- 6 files changed, 81 insertions(+), 81 deletions(-) diff --git a/packages/app/component-tests/activity-chips.spec.ts b/packages/app/component-tests/activity-chips.spec.ts index 3add92d44ed4..dcc7c882c8eb 100644 --- a/packages/app/component-tests/activity-chips.spec.ts +++ b/packages/app/component-tests/activity-chips.spec.ts @@ -1,7 +1,9 @@ import { expect, story } from "../../stories/playwright/story" story("shows how long each background item has been running, ticking while open", async ({ mount, page }) => { + // Time moves only when the test advances it, so waiting for fonts cannot add a tick. await page.clock.install({ time: new Date("2026-10-19T12:00:00Z") }) + await page.clock.pauseAt(new Date("2026-10-19T12:00:00Z")) await mount("session-activity-chips--subagents-and-shells") await page.locator('[data-slot="session-activity-chip"]').click() @@ -9,6 +11,8 @@ story("shows how long each background item has been running, ticking while open" // The story starts its subagents 12 s and 65 s ago, and its shells 62 min and 341 s ago. await expect(elapsed).toHaveText(["12s", "1m 05s", "1h 02m", "5m 41s"]) + // A late Inter swap widens the panel; measure after fonts settle. + await page.evaluate(() => document.fonts.ready.then(() => undefined)) const width = await page.locator('[data-slot="session-activity-panel"]').evaluate((node) => node.clientWidth) await page.clock.runFor(1_000) diff --git a/packages/app/component-tests/composer.spec.ts b/packages/app/component-tests/composer.spec.ts index 5bd24552dd02..95dde796838d 100644 --- a/packages/app/component-tests/composer.spec.ts +++ b/packages/app/component-tests/composer.spec.ts @@ -472,7 +472,7 @@ story("grows suggestions while preserving visible timeline context", async ({ mo const boundary = component.locator('[data-slot="composer-suggestion-boundary-story"]') const suggestions = component.locator('[data-component="composer-suggestions"]') - await expect(suggestions).toHaveCSS("max-height", "166px") + await expect(suggestions).toHaveCSS("max-height", "164px") await expect(suggestions).toHaveCSS("scroll-padding-bottom", "18px") await expect.poll(() => suggestions.evaluate((element) => element.scrollHeight > element.clientHeight)).toBe(true) await expect @@ -497,12 +497,12 @@ story("grows suggestions while preserving visible timeline context", async ({ mo await boundary.evaluate((element) => { element.style.height = "400px" }) - await expect(suggestions).toHaveCSS("max-height", "306px") + await expect(suggestions).toHaveCSS("max-height", "304px") }) story("ignores a detached suggestion boundary", async ({ mount }) => { const component = await mount("opencode-composer-flow--detached-command-suggestion-boundary") - await expect(component.locator('[data-component="composer-suggestions"]')).toHaveCSS("max-height", "306px") + await expect(component.locator('[data-component="composer-suggestions"]')).toHaveCSS("max-height", "304px") }) // ThemeProvider writes resolved token values into a