fix(ui): make playground chat bubbles theme-aware (#37978)

The playground message bubble painted its fill, border and avatar circle from
inline hex values, so in dark mode both bubbles stayed near-white while the text
inherited the dark foreground: the message body was unreadable. The MCP-events
placeholder bubble in ChatUI carried the same three fills.

They move onto the tokens the rest of the sweep already uses, so the assistant
surface is bg-card over border-border and the user surface is the info tint at
the same weight the other selected-state surfaces take. Light mode keeps the
same colour family it had.

The regression test asserts the token classes and that no inline style survives
on either surface, which is the exact shape the bug took.

(cherry picked from commit 3fb1009f81)
This commit is contained in:
yuneng-jiang 2026-08-24 10:13:24 -07:00 committed by Yuneng Jiang
parent 6b29016987
commit c4867ccd18
No known key found for this signature in database
3 changed files with 23 additions and 22 deletions

View file

@ -87,6 +87,21 @@ describe("ChatMessageBubble", () => {
expect(screen.getByText("Hi there")).toBeInTheDocument();
});
it.each([
{ role: "user" as const, bubble: ["bg-info/10", "border-info/20"], avatar: "bg-info/20" },
{ role: "assistant" as const, bubble: ["bg-card", "border-border"], avatar: "bg-muted" },
])("should paint the $role surface from theme tokens, not fixed colours", ({ role, bubble, avatar }) => {
render(<ChatMessageBubble {...defaultProps} message={{ role, content: "Hello" }} />);
const header = screen.getByText(role).closest("div") as HTMLElement;
const surface = header.parentElement as HTMLElement;
expect(surface).toHaveClass(...bubble);
expect(surface).not.toHaveAttribute("style");
expect(header.firstElementChild).toHaveClass(avatar);
expect(header.firstElementChild).not.toHaveAttribute("style");
});
it("should show model badge for assistant messages when model is provided", () => {
render(<ChatMessageBubble {...defaultProps} message={{ role: "assistant", content: "Reply", model: "gpt-4" }} />);

View file

@ -46,20 +46,16 @@ function ChatMessageBubble({
return (
<div className={`mb-4 min-w-0 ${isUser ? "text-right" : "text-left"}`}>
<div
className="inline-block min-w-0 max-w-[92%] overflow-hidden rounded-lg p-3 shadow-xs sm:max-w-[85%] sm:px-4"
style={{
backgroundColor: isUser ? "#f0f8ff" : "#ffffff",
border: isUser ? "1px solid #e6f0fa" : "1px solid #f0f0f0",
textAlign: "left",
}}
className={`inline-block min-w-0 max-w-[92%] overflow-hidden rounded-lg border p-3 text-left text-card-foreground shadow-xs sm:max-w-[85%] sm:px-4 ${
isUser ? "border-info/20 bg-info/10" : "border-border bg-card"
}`}
>
{/* Header: role icon + name + model badge */}
<div className="mb-1.5 flex min-w-0 items-center gap-2">
<div
className="flex items-center justify-center w-6 h-6 rounded-full mr-1"
style={{
backgroundColor: isUser ? "#e6f0fa" : "#f5f5f5",
}}
className={`flex items-center justify-center w-6 h-6 rounded-full mr-1 ${
isUser ? "bg-info/20" : "bg-muted"
}`}
>
{isUser ? (
<User className="size-3 text-info" aria-hidden="true" />

View file

@ -1784,19 +1784,9 @@ const ChatUI: React.FC<ChatUIProps> = ({
chatHistory.length > 0 &&
chatHistory[chatHistory.length - 1].role === "user" && (
<div className="mb-4 text-left">
<div
className="inline-block max-w-[80%] rounded-lg p-3.5 px-4 shadow-xs"
style={{
backgroundColor: "#ffffff",
border: "1px solid #f0f0f0",
textAlign: "left",
}}
>
<div className="inline-block max-w-[80%] rounded-lg border border-border bg-card p-3.5 px-4 text-left text-card-foreground shadow-xs">
<div className="mb-1.5 flex items-center gap-2">
<div
className="mr-1 flex h-6 w-6 items-center justify-center rounded-full"
style={{ backgroundColor: "#f5f5f5" }}
>
<div className="mr-1 flex h-6 w-6 items-center justify-center rounded-full bg-muted">
<Bot className="size-3 text-muted-foreground" aria-hidden="true" />
</div>
<strong className="text-sm capitalize">Assistant</strong>