From 8c3eecb8cc0fccbd165bd3514da9e5eeff513dd0 Mon Sep 17 00:00:00 2001 From: Xinmin Zeng <135568692+fancyboi999@users.noreply.github.com> Date: Thu, 29 Oct 2026 17:10:34 +0800 Subject: [PATCH 1/5] fix(web): sent messages honor their theme foreground --- apps/web/src/components/chat/TimelineUserMessageRow.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/components/chat/TimelineUserMessageRow.tsx b/apps/web/src/components/chat/TimelineUserMessageRow.tsx index 2b231f6ddfe35..118b746558500 100644 --- a/apps/web/src/components/chat/TimelineUserMessageRow.tsx +++ b/apps/web/src/components/chat/TimelineUserMessageRow.tsx @@ -837,7 +837,7 @@ const UserMessageBody = memo(function UserMessageBody(props: { cwd={props.markdownCwd} threadRef={ctx.threadRef ?? undefined} skills={props.skills} - className="text-foreground" + className="text-message-foreground" lineBreaks parseRawHtml={false} renderContextReference={props.renderContextReference} From ae9f08190a4139c388d7e83361751488ebca38be Mon Sep 17 00:00:00 2001 From: Xinmin Zeng Date: Wed, 11 Nov 2026 09:12:55 +0800 Subject: [PATCH 2/5] fix(web): plain-text fences keep the code foreground --- apps/web/src/index.css | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/web/src/index.css b/apps/web/src/index.css index 40daa0da7dcc8..c3b460ad70ad8 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -2190,18 +2190,18 @@ code { background: transparent !important; } -/* Plain-text fences override the highlighter's inline foreground colors. */ +/* Plain-text fences override the highlighter's inline foreground colors. The + frame keeps its own code foreground, so the text inherits that rather than + the surrounding message color. */ :is(.chat-markdown, .composer-tiptap) .chat-markdown-codeblock:is( [data-language="text" i], [data-language="txt" i], [data-language="plain" i], [data-language="plaintext" i] - ) { - &, - & :is(pre, pre *) { - color: inherit !important; - } + ) + :is(pre, pre *) { + color: inherit !important; } /* Paragraphs and code blocks arrive in chunks while a response streams. Fade From 095606220a00035f5da111289eb52c528bca96ce Mon Sep 17 00:00:00 2001 From: Xinmin Zeng <135568692+fancyboi999@users.noreply.github.com> Date: Wed, 11 Nov 2026 09:52:23 +0800 Subject: [PATCH 3/5] fix(web): inline code in sent messages uses the message foreground --- .../components/ChatMarkdown.browser.test.tsx | 24 +++++++++++++++++++ apps/web/src/index.css | 5 ++++ 2 files changed, 29 insertions(+) diff --git a/apps/web/src/components/ChatMarkdown.browser.test.tsx b/apps/web/src/components/ChatMarkdown.browser.test.tsx index b03612507f917..f86cb23327c8a 100644 --- a/apps/web/src/components/ChatMarkdown.browser.test.tsx +++ b/apps/web/src/components/ChatMarkdown.browser.test.tsx @@ -195,6 +195,30 @@ it("keeps decimal markers in nested user lists while documents keep their counte ).toEqual(["decimal", "lower-alpha", "lower-roman"]); }); +it("colors inline code in user messages with the message foreground", async () => { + setup(); + const root = document.documentElement; + root.style.setProperty("--foreground", "rgb(10, 20, 30)"); + root.style.setProperty("--message-foreground", "rgb(240, 200, 40)"); + try { + await using screen = await render( +
+ + + +
, + ); + await expect.element(screen.getByText("pnpm dev").first()).toBeVisible(); + const [message, preview] = [...screen.container.querySelectorAll(".chat-markdown code")]; + const probe = screen.container.querySelector("[data-probe]")!; + expect(getComputedStyle(message!).color).toBe(getComputedStyle(probe).color); + expect(getComputedStyle(preview!).color).not.toBe(getComputedStyle(probe).color); + } finally { + root.style.removeProperty("--foreground"); + root.style.removeProperty("--message-foreground"); + } +}); + it("shows unavailable audio when a workspace embed has no thread", async () => { setup(); await using screen = await renderText("![recording](clip.wav)"); diff --git a/apps/web/src/index.css b/apps/web/src/index.css index c3b460ad70ad8..2e1bb2115a2e8 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -2140,6 +2140,11 @@ code { font-size: 0.85em; } +.chat-markdown.user-message-markdown :not(pre) > code { + background: color-mix(in srgb, var(--contrast-message-foreground) 10%, transparent); + color: var(--contrast-message-foreground); +} + .chat-markdown strong { font-weight: 700; } From 60af80c3c1ecb0c784ae340d3f11116cefcda6a3 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 12 Nov 2026 21:11:21 +0000 Subject: [PATCH 4/5] fix(web): plain-text fences keep matching the text around them Restores the frame override from #27144 and drops the browser test; the colour fix is visual and is proven with captures. AI-assisted; verified with the tests and tools listed above. Co-Authored-By: Claude Opus 5.5 --- .../components/ChatMarkdown.browser.test.tsx | 24 ------------------- apps/web/src/index.css | 12 +++++----- 2 files changed, 6 insertions(+), 30 deletions(-) diff --git a/apps/web/src/components/ChatMarkdown.browser.test.tsx b/apps/web/src/components/ChatMarkdown.browser.test.tsx index 8c9590bfebaf5..24e0b6655ff7c 100644 --- a/apps/web/src/components/ChatMarkdown.browser.test.tsx +++ b/apps/web/src/components/ChatMarkdown.browser.test.tsx @@ -195,30 +195,6 @@ it("keeps decimal markers in nested user lists while documents keep their counte ).toEqual(["decimal", "lower-alpha", "lower-roman"]); }); -it("colors inline code in user messages with the message foreground", async () => { - setup(); - const root = document.documentElement; - root.style.setProperty("--foreground", "rgb(10, 20, 30)"); - root.style.setProperty("--message-foreground", "rgb(240, 200, 40)"); - try { - await using screen = await render( -
- - - -
, - ); - await expect.element(screen.getByText("pnpm dev").first()).toBeVisible(); - const [message, preview] = [...screen.container.querySelectorAll(".chat-markdown code")]; - const probe = screen.container.querySelector("[data-probe]")!; - expect(getComputedStyle(message!).color).toBe(getComputedStyle(probe).color); - expect(getComputedStyle(preview!).color).not.toBe(getComputedStyle(probe).color); - } finally { - root.style.removeProperty("--foreground"); - root.style.removeProperty("--message-foreground"); - } -}); - it("shows unavailable audio when a workspace embed has no thread", async () => { setup(); await using screen = await renderText("![recording](clip.wav)"); diff --git a/apps/web/src/index.css b/apps/web/src/index.css index b3b0226387a14..eaece92bd55d7 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -2193,18 +2193,18 @@ code { background: transparent !important; } -/* Plain-text fences override the highlighter's inline foreground colors. The - frame keeps its own code foreground, so the text inherits that rather than - the surrounding message color. */ +/* Plain-text fences override the highlighter's inline foreground colors. */ :is(.chat-markdown, .composer-tiptap) .chat-markdown-codeblock:is( [data-language="text" i], [data-language="txt" i], [data-language="plain" i], [data-language="plaintext" i] - ) - :is(pre, pre *) { - color: inherit !important; + ) { + &, + & :is(pre, pre *) { + color: inherit !important; + } } /* Paragraphs and code blocks arrive in chunks while a response streams. Fade From 54e03c31afe72ba24db4f8b4ae2fe721f7964c2a Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 12 Nov 2026 21:14:07 +0000 Subject: [PATCH 5/5] fix(web): plain-text fences in sent messages keep the global foreground The message foreground is chosen for the bubble, not the code surface, so a light bubble with dark text would put dark text on a dark code block. AI-assisted; verified with the tests and tools listed above. Co-Authored-By: Claude Opus 5.5 --- apps/web/src/index.css | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/web/src/index.css b/apps/web/src/index.css index eaece92bd55d7..676e120e826de 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -2207,6 +2207,18 @@ code { } } +/* Sent messages color prose with the message foreground, which is not chosen + for the code surface, so their plain-text fences keep the global foreground. */ +.chat-markdown.user-message-markdown + .chat-markdown-codeblock:is( + [data-language="text" i], + [data-language="txt" i], + [data-language="plain" i], + [data-language="plaintext" i] + ) { + color: var(--contrast-foreground) !important; +} + /* Paragraphs and code blocks arrive in chunks while a response streams. Fade each new block in so the chunk does not pop. @starting-style only applies when an element is first inserted, and the rule is gated on data-streaming,