Session message style (#46538)

Co-authored-by: David Hill <iamdavidhill@gmail.com>
This commit is contained in:
Brendan Allan
2026-09-01 12:49:16 +08:00
committed by GitHub
Unverified
parent 71779ae5de
commit d46ed9e9db
9 changed files with 75 additions and 13 deletions
@@ -75,11 +75,12 @@ for (const theme of ["light", "dark"] as const) {
await expectBackground(view.send, "contrast")
const message = page.locator('[data-slot="user-message-text"]')
await expect(message).toHaveText("Check this fixture workspace.")
await expectBackground(
await expectToken(
message,
scenario.accent ? "accent" : theme === "light" ? "layer-02" : "layer-01",
"background-color",
scenario.accent ? "--v2-background-bg-accent" : "--v2-state-bg-info",
)
await expectToken(message, "color", scenario.accent ? "--v2-text-text-contrast" : "--v2-text-text-accent")
})
}
@@ -245,3 +246,16 @@ async function expectBackground(element: Locator, token: string, property = "bac
}, token)
await expect(element).toHaveCSS(property, new RegExp(color.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")))
}
async function expectToken(element: Locator, property: string, token: string) {
const color = await element.evaluate((element, token) => {
const probe = document.createElement("span")
probe.hidden = true
probe.style.color = `var(${token})`
element.append(probe)
const color = getComputedStyle(probe).color
probe.remove()
return color
}, token)
await expect(element).toHaveCSS(property, color)
}
@@ -155,6 +155,16 @@ describe("isSessionNotFoundError", () => {
expect(isSessionNotFoundError(new Error(body.message, { cause: { body, status: 404 } }), body.sessionID)).toBe(true)
})
test("matches a structured error stored directly as the cause", () => {
const body = {
_tag: "SessionNotFoundError",
sessionID: "ses_missing",
message: "Session not found",
} satisfies SessionNotFoundError
expect(isSessionNotFoundError(new Error("Unknown error", { cause: body }), body.sessionID)).toBe(true)
})
test("rejects errors for other sessions and other 404 responses", () => {
const body = {
_tag: "SessionNotFoundError",
+3 -4
View File
@@ -36,10 +36,9 @@ export function formatServerError(error: unknown, translate?: Translator, fallba
}
function unwrapNamedError(error: unknown): unknown {
if (error instanceof Error && error.cause && typeof error.cause === "object" && "body" in error.cause) {
return (error.cause as Record<string, unknown>).body
}
return error
if (!(error instanceof Error) || !error.cause || typeof error.cause !== "object") return error
if ("body" in error.cause) return (error.cause as Record<string, unknown>).body
return error.cause
}
// Client-synthesized session not-found errors share one constructor and
@@ -478,7 +478,11 @@ export function createTimelineVirtualizer(input: Input) {
}
return (
<div class="relative w-full h-full min-w-0" data-workspace-session={props.workspaceSession() ? "" : undefined}>
<div
class="relative w-full h-full min-w-0"
data-workspace-session={props.workspaceSession() ? "" : undefined}
data-local-session={!props.workspaceSession() ? "" : undefined}
>
<div
class="absolute left-1/2 -translate-x-1/2 z-[60] pointer-events-none transition-all duration-200 ease-out"
classList={{
@@ -1,4 +1,5 @@
import { fileURLToPath } from "node:url"
import type { Page } from "@playwright/test"
import { expect, story } from "../../storybook/playwright/story"
const png = Buffer.from(
@@ -129,6 +130,14 @@ story("mounts cached completed Markdown with sanitized HTML and decorations", as
await expect(markdown.getByRole("heading")).toHaveText("Completed response")
await expect(markdown.locator("script, [onerror], [href^='javascript:']")).toHaveCount(0)
await expect(markdown.locator('code[data-inline-code-kind="path"]')).toHaveText("src/file.ts")
await expect(markdown.locator('code[data-inline-code-kind="path"]')).toHaveCSS(
"color",
await resolvedColor(page, "--v2-text-text-code-path"),
)
await expect(markdown.locator('code[data-inline-code-kind="path"]')).toHaveCSS(
"background-color",
await resolvedColor(page, "--v2-background-bg-code-path"),
)
await expect(markdown.getByRole("link", { name: "https://example.com/docs" })).toHaveAttribute("target", "_blank")
await expect(markdown.getByRole("link", { name: "https://example.com/docs" })).toHaveAttribute(
"rel",
@@ -153,6 +162,17 @@ story("mounts cached completed Markdown with sanitized HTML and decorations", as
await expect(markdown).toHaveAttribute("data-markdown-ready", "")
})
async function resolvedColor(page: Page, token: string) {
return page.evaluate((token) => {
const probe = document.createElement("span")
probe.style.color = `var(${token})`
document.body.append(probe)
const color = getComputedStyle(probe).color
probe.remove()
return color
}, token)
}
story("keeps inline code backgrounds 18px tall", async ({ page }) => {
await page.evaluate(async (fixture) => {
const { mountMarkdown } = await import(fixture)
@@ -1,5 +1,6 @@
[data-component="markdown"] {
--markdown-inline-code-path-color: var(--v2-text-text-code-accent);
--markdown-inline-code-path-color: var(--v2-text-text-code-path);
--markdown-inline-code-path-background: var(--v2-background-bg-code-path);
--markdown-inline-code-color: var(--v2-text-text-code-accent);
--markdown-inline-code-bg-mix: 8%;
--markdown-shell-code-background: var(--v2-background-bg-layer-02);
@@ -290,11 +291,7 @@
:not(pre) > code[data-inline-code-kind="path"] {
color: var(--markdown-inline-code-path-color);
background: color-mix(
in oklch,
var(--markdown-inline-code-path-color) var(--markdown-inline-code-bg-mix),
transparent
);
background: var(--markdown-inline-code-path-background);
}
a.external-link > code[data-inline-code-kind="url"] {
@@ -1547,3 +1547,9 @@
background: var(--v2-background-bg-accent);
color: var(--v2-text-text-contrast);
}
:root body [data-local-session] [data-component="user-message"] [data-slot="user-message-text"] {
background: var(--v2-state-bg-info);
color: var(--v2-text-text-accent);
border-radius: 8px;
}
+8
View File
@@ -13,6 +13,7 @@
--v2-background-bg-contrast: var(--v2-grey-1000);
--v2-background-bg-button-neutral: var(--v2-grey-50);
--v2-background-bg-accent: var(--v2-blue-600);
--v2-background-bg-code-path: var(--v2-cyan-100);
/* ── Text ── */
--v2-text-text-base: var(--v2-grey-1100);
@@ -23,6 +24,7 @@
--v2-text-text-accent: var(--v2-blue-600);
--v2-text-text-accent-hover: var(--v2-blue-700);
--v2-text-text-code-accent: var(--v2-blue-900);
--v2-text-text-code-path: var(--v2-cyan-900);
/* ── Icon ── */
--v2-icon-icon-base: var(--v2-grey-800);
@@ -151,6 +153,7 @@
--v2-background-bg-contrast: var(--v2-grey-700);
--v2-background-bg-button-neutral: var(--v2-alpha-light-6);
--v2-background-bg-accent: var(--v2-blue-600);
--v2-background-bg-code-path: var(--v2-cyan-1200);
--v2-text-text-base: var(--v2-grey-200);
--v2-text-text-muted: var(--v2-grey-500);
@@ -160,6 +163,7 @@
--v2-text-text-accent: var(--v2-blue-400);
--v2-text-text-accent-hover: var(--v2-blue-300);
--v2-text-text-code-accent: var(--v2-blue-400);
--v2-text-text-code-path: var(--v2-cyan-400);
--v2-icon-icon-base: var(--v2-grey-400);
--v2-icon-icon-muted: var(--v2-grey-600);
@@ -265,6 +269,7 @@
--v2-background-bg-contrast: var(--v2-grey-1000);
--v2-background-bg-button-neutral: var(--v2-grey-50);
--v2-background-bg-accent: var(--v2-blue-600);
--v2-background-bg-code-path: var(--v2-cyan-100);
--v2-text-text-base: var(--v2-grey-1100);
--v2-text-text-muted: var(--v2-grey-700);
@@ -274,6 +279,7 @@
--v2-text-text-accent: var(--v2-blue-600);
--v2-text-text-accent-hover: var(--v2-blue-700);
--v2-text-text-code-accent: var(--v2-blue-900);
--v2-text-text-code-path: var(--v2-cyan-900);
--v2-icon-icon-base: var(--v2-grey-800);
--v2-icon-icon-muted: var(--v2-grey-600);
@@ -388,6 +394,7 @@
--v2-background-bg-contrast: var(--v2-grey-700);
--v2-background-bg-button-neutral: var(--v2-alpha-light-6);
--v2-background-bg-accent: var(--v2-blue-600);
--v2-background-bg-code-path: var(--v2-cyan-1200);
--v2-text-text-base: var(--v2-grey-100);
--v2-text-text-muted: var(--v2-grey-500);
@@ -397,6 +404,7 @@
--v2-text-text-accent: var(--v2-blue-400);
--v2-text-text-accent-hover: var(--v2-blue-300);
--v2-text-text-code-accent: var(--v2-blue-400);
--v2-text-text-code-path: var(--v2-cyan-400);
--v2-icon-icon-base: var(--v2-grey-400);
--v2-icon-icon-muted: var(--v2-grey-600);
+4
View File
@@ -42,11 +42,13 @@ const light: Record<string, V2ColorValue> = {
"v2-background-bg-contrast": ref("v2-grey-900"),
"v2-background-bg-button-neutral": ref("v2-grey-100"),
"v2-background-bg-accent": ref("v2-blue-600"),
"v2-background-bg-code-path": ref("v2-cyan-100"),
"v2-text-text-inverse": ref("v2-grey-100"),
"v2-text-text-contrast": ref("v2-grey-100"),
"v2-text-text-accent": ref("v2-blue-600"),
"v2-text-text-accent-hover": ref("v2-blue-700"),
"v2-text-text-code-accent": ref("v2-blue-900"),
"v2-text-text-code-path": ref("v2-cyan-900"),
"v2-border-border-muted": ref("v2-alpha-dark-8"),
"v2-border-border-base": ref("v2-alpha-dark-10"),
"v2-border-border-strong": ref("v2-alpha-dark-20"),
@@ -107,11 +109,13 @@ const dark: Record<string, V2ColorValue> = {
"v2-background-bg-contrast": ref("v2-grey-700"),
"v2-background-bg-button-neutral": ref("v2-alpha-light-6"),
"v2-background-bg-accent": ref("v2-blue-600"),
"v2-background-bg-code-path": ref("v2-cyan-1200"),
"v2-text-text-inverse": ref("v2-grey-1000"),
"v2-text-text-contrast": ref("v2-grey-100"),
"v2-text-text-accent": ref("v2-blue-400"),
"v2-text-text-accent-hover": ref("v2-blue-300"),
"v2-text-text-code-accent": ref("v2-blue-400"),
"v2-text-text-code-path": ref("v2-cyan-400"),
"v2-border-border-muted": ref("v2-alpha-light-8"),
"v2-border-border-base": ref("v2-alpha-light-10"),
"v2-border-border-strong": ref("v2-alpha-light-20"),