Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions webviews/test/agent-pane-turn-tokens-muted-browser.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// The running turn's token count reads quieter than its words (agent-pane-turn-tokens-muted.test.tsx),
// resolved in Chromium from the real stylesheets under a host theme: "Working for 35s" and
// "Thinking" in the theme's secondary text, "· 679.9K tokens" in its tertiary, in tabular numerals.
import { describe, expect, setDefaultTimeout, test } from "bun:test";
import { readFileSync } from "node:fs";
import path from "node:path";
import { chromium, type Browser } from "playwright";
import { requireBrowserLane } from "./support/requireBrowserLane";
import { tokenLines, tokenLineMarkup } from "./support/turnTokenLines";

await requireBrowserLane("agent-pane-turn-tokens-muted-browser.test.tsx", async () => {
setDefaultTimeout(60_000);
let browser: Browser;
try {
browser = await chromium.launch({ headless: true });
} catch {
console.warn("agent-pane-turn-tokens-muted-browser: skipping (run `bunx playwright install chromium`)");
describe.skip("turn tokens muted", () => test("chromium", () => {}));
return;
}
const src = path.resolve(import.meta.dir, "../src");
const css = [
"agent-session/shared/styles.css",
"agent-session/acpmux/styles.css",
"agent-session/acpmux/conversation/conversation.css",
]
.map((file) => readFileSync(path.join(src, file), "utf8"))
.join("\n");
// The host's theme (AgentPaneTheme.swift: mutedText is textSecondary, softText textTertiary).
const theme =
"--agent-text: rgb(230, 230, 230); --agent-muted: rgb(160, 160, 170); --agent-soft: rgb(110, 110, 120);";

describe("the count reads quieter than the line's words", () => {
for (const [name, line] of Object.entries(tokenLines))
test(name, async () => {
const page = await browser.newPage();
try {
// The static status look: the word Thinking holds still in its muted color, no band.
await page.setContent(
`<html data-status-indicators="static" style="${theme}"><head><style>${css}</style></head>` +
`<body>${tokenLineMarkup(line)}</body></html>`,
);
const looks = await page.evaluate(() => {
const style = (selector: string) => getComputedStyle(document.querySelector(selector)!);
return {
words: style(".cv-thinking, .cv-worked__label").color,
tokens: style(".cv-worked__tokens").color,
sep: style(".cv-worked__sep").color,
numerals: style(".cv-worked__tokens").fontVariantNumeric,
};
});
expect(looks.words).toBe("rgb(160, 160, 170)");
expect(looks.tokens).toBe("rgb(110, 110, 120)");
expect(looks.sep).toBe(looks.tokens);
expect(looks.numerals).toContain("tabular-nums");
} finally {
await page.close();
}
});
test("closes the browser", async () => {
await browser.close();
});
});
});
44 changes: 44 additions & 0 deletions webviews/test/agent-pane-turn-tokens-muted.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// The running turn's token count is secondary to its words (Leo, 2026-10-21: "the token font
// shouldnt be this same color"). "Working for 35s" (or "Thinking 35s") keeps the line's muted color;
// "· 679.9K tokens" after it takes the tool rows' quieter one, the theme's tertiary text
// (`--cv-text-dim`, `--agent-soft`, the host's textTertiary), never a literal color, in tabular
// numerals so the count ticks in place. Both lines that say it, the Codex layout's live line at the
// bottom (Thinking.tsx) and the line over the turn with that layout off (WorkingFor.tsx), draw the
// count in the same span, so one rule covers both. agent-pane-turn-tokens-muted-browser.test.tsx
// resolves the real stylesheets in Chromium.
import { expect, test } from "bun:test";
import { readFileSync } from "node:fs";
import path from "node:path";
import { tokenLines, tokenLineMarkup } from "./support/turnTokenLines";

const conversationCss = readFileSync(
path.resolve(import.meta.dir, "../src/agent-session/acpmux/conversation/conversation.css"),
"utf8",
);
/// The declarations of the rule whose selector is exactly `selector`.
const rule = (selector: string) => {
const at = conversationCss.indexOf(`\n${selector} {`);
if (at < 0) throw new Error(`conversation.css has no rule for ${selector}`);
return conversationCss.slice(at, conversationCss.indexOf("}", at));
};

for (const [name, line] of Object.entries(tokenLines))
test(`${name}: the count is its own span, which the quieter rule keys on`, () => {
const html = tokenLineMarkup(line);
expect(html).toContain(
'<span class="cv-worked__count"><span class="cv-worked__sep" aria-hidden="true">·</span><span class="cv-worked__tokens">679.9K tokens</span></span>',
);
// The line's words come before the count's span, outside it.
expect(html.indexOf("cv-worked__count")).toBeGreaterThan(html.search(/Working for|Thinking/));
});

test("the count takes the theme's tertiary text token, in tabular numerals, never a literal color", () => {
const tokens = rule(".cv-worked__count:has(> .cv-worked__tokens)");
expect(tokens).toContain("color: var(--cv-text-dim);");
expect(tokens).toContain("font-variant-numeric: tabular-nums;");
expect(tokens).not.toMatch(/#[0-9a-f]{3,8}\b|rgba?\(|hsla?\(|oklch\(/i);
// The line's words keep the muted (secondary) token; the count's is the tertiary one.
expect(rule(".cv-worked")).toContain("color: var(--cv-text-muted);");
expect(conversationCss).toContain("--cv-text-muted: var(--agent-muted);");
expect(conversationCss).toContain("--cv-text-dim: var(--agent-soft);");
});
33 changes: 33 additions & 0 deletions webviews/test/support/turnTokenLines.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// l10n-allow-file: test fixtures, not shipped UI.
// The two lines that say a running turn's tokens, at 35s and 679,900 tokens: the Codex layout's live
// line at the bottom (Thinking.tsx) and the line over the turn with that layout off (WorkingFor.tsx).
import type { ReactElement } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { Thinking } from "../../src/agent-session/acpmux/conversation/Thinking";
import {
WorkingCountsContext,
WorkingFor,
type WorkingCounts,
} from "../../src/agent-session/acpmux/conversation/WorkingFor";
import type { AcpmuxRow } from "../../src/agent-session/acpmux/model";

const START = Date.UTC(2026, 9, 21, 9, 0, 0);
const now = () => START + 35_000;
const counts: WorkingCounts = {
agents: 0,
commands: 0,
sessions: 0,
openAgents: () => {},
openCommands: () => {},
openSessions: () => {},
tokens: 679_900,
};
const working: AcpmuxRow = { id: "working", kind: "working", version: 1, at: START };

export const tokenLines: Record<string, ReactElement> = {
"the live line at the bottom (Thinking.tsx)": <Thinking since={START} now={now} below />,
"the line over the turn (WorkingFor.tsx)": <WorkingFor row={working} now={now} />,
};

export const tokenLineMarkup = (line: ReactElement) =>
renderToStaticMarkup(<WorkingCountsContext.Provider value={counts}>{line}</WorkingCountsContext.Provider>);