From 3fb0fe9eae0da890dff524361bafda134d8eb82e Mon Sep 17 00:00:00 2001 From: Ben Vinegar Date: Tue, 30 Jun 2026 22:34:09 -0400 Subject: [PATCH] fix(render): honor system mode for direct rich surfaces --- .changeset/system-surface-schemes.md | 5 ++++ server/richRender.ts | 2 +- server/surfacePage.ts | 36 ++++++++++++++++++---------- test/richRender.test.ts | 13 ++++++++++ test/surfacePage.test.ts | 31 ++++++++++++++++++++---- 5 files changed, 69 insertions(+), 18 deletions(-) create mode 100644 .changeset/system-surface-schemes.md diff --git a/.changeset/system-surface-schemes.md b/.changeset/system-surface-schemes.md new file mode 100644 index 0000000..330e90f --- /dev/null +++ b/.changeset/system-surface-schemes.md @@ -0,0 +1,5 @@ +--- +"sideshow": patch +--- + +Let direct rich-surface renders without an explicit mode follow the browser's system color scheme. diff --git a/server/richRender.ts b/server/richRender.ts index b3615cd..6b44e4e 100644 --- a/server/richRender.ts +++ b/server/richRender.ts @@ -395,7 +395,7 @@ export async function renderDiff( const options = { diffStyle: part.layout ?? "unified", theme: { dark: shiki.dark, light: shiki.light }, - themeType: opts.mode === "dark" ? "dark" : "light", + themeType: opts.mode ?? "system", preferredHighlighter: "shiki-js", } as const; const rendered = await Promise.all( diff --git a/server/surfacePage.ts b/server/surfacePage.ts index 81690a1..ce26163 100644 --- a/server/surfacePage.ts +++ b/server/surfacePage.ts @@ -31,12 +31,11 @@ const KIT_ACCENTS_DARK: Record = { }; const kitAccentCss = (mode?: Mode): string => schemeCss(KIT_ACCENTS_LIGHT, KIT_ACCENTS_DARK, mode); -// When a scheme is pinned, force the document's used color-scheme to match so -// the UA-painted canvas, scrollbars, and native form controls follow it too -// (the token vars alone don't drive those). Overrides the static -// `color-scheme: light dark` default the kit/base CSS sets. Empty when the -// scheme is left to the OS, preserving the media-query behavior unchanged. -const colorSchemeCss = (mode?: Mode): string => (mode ? `:root{color-scheme:${mode}}` : ""); +// Force the document's used color-scheme so the UA-painted canvas, scrollbars, +// and native form controls follow the same scheme as the theme vars (the vars +// alone don't drive those). Pinned frames get a single scheme; unpinned/direct +// loads opt into both schemes so the browser can resolve the user's system mode. +const colorSchemeCss = (mode?: Mode): string => `:root{color-scheme:${mode ?? "light dark"}}`; // Origins html surfaces may load external resources from. Mirrors the allowlist // agents already know from Claude's inline widget surface. @@ -357,7 +356,8 @@ ${doc.body} // own DOMPurify (securityLevel 'strict') runs first; the opaque origin is the // second boundary. Theme colors are baked into the diagram at render time, so — // like shiki's flip — they're PINNED to the chrome-resolved mode the viewer -// passed (mermaid can't do a media-query flip); absent mode defaults to light. +// passed. On a direct no-mode load, the iframe's own JS chooses the user's +// system scheme before mermaid renders. const MERMAID_CSS = ` body { margin: 0; padding: 14px 16px; background: transparent; text-align: center; } @@ -475,21 +475,33 @@ export function renderMermaidPage(doc: { }): string { const theme = typeof doc.theme === "string" || doc.theme == null ? themeById(doc.theme) : doc.theme; - const palette = doc.mode === "dark" ? theme.dark : theme.light; - const { themeVariables, themeCSS } = mermaidThemeVars(palette, doc.mode); + const enc = (v: unknown) => JSON.stringify(v).replace(/` in the // diagram source can't break out of the module script. - const enc = (v: unknown) => JSON.stringify(v).replace(/ renderDiff({ kind: "diff", patch: "" }), /No diff content/); }); +test("renderDiff: absent mode keeps the diff in system color-scheme", async () => { + const diff: DiffSurface = { + kind: "diff", + files: [{ filename: "f.ts", before: "const x = 1", after: "const x = 2" }], + }; + const { body } = await renderDiff(diff); + assert.match(body, /color-scheme:\s*light dark/); + assert.doesNotMatch( + body.match(/