From 2ea22d6ceaaea21b3f1e9037c22f5a538ead27d9 Mon Sep 17 00:00:00 2001 From: scotttong Date: Thu, 6 Aug 2026 01:08:04 -0700 Subject: [PATCH] fix(ui): white text on light-mode user chat bubbles (#10952) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The task detail view shows the conversation as chat bubbles. The requester's own messages sit in a solid accent-colored bubble. > - The bubble container sets `text-white`, but the message body renders through `MarkdownBody`. Tailwind prose tokens (`--tw-prose-body`) win over the inherited container color. > - `prose-invert` only lightens the prose text in dark mode. In light mode the prose body stayed its default dark color, so the text read as near-black on the blue bubble and was hard to read. > - This pull request maps the human bubble's prose tokens to the inherited text color in both themes. > - The benefit is that the requester's chat text is readable white-on-blue in light mode, and dark mode stays exactly as it was. ## Linked Issues or Issue Description **What happened?** In light mode, the text inside the user's own chat bubbles in the task detail view rendered as dark (near-black) on the solid blue accent background. This made the requester's messages hard to read. **Expected behavior** The text inside the user's accent-colored chat bubbles should be white in light mode, matching the bubble's `text-white` intent. Dark mode already rendered correctly and should not change. **Steps to reproduce** 1. Open a chat-style task detail view in light mode. 2. Post a message as the requester (human) so it renders in the solid blue accent bubble. 3. Observe the body text renders dark on blue instead of white. **Paperclip version or commit** Reproduces on `master` (branched from `814cb3367`). **Agent adapter(s) involved** Not adapter-specific (core UI bug). ## What Changed - Add the existing `paperclip-markdown-on-accent` class to the human-branch `MarkdownBody` in `TaskChatBubble.tsx`. This class (already used by `IssueChatThread` for the same accent bubble) maps prose body/heading tokens to `currentColor`, so the text follows the bubble's `text-white` in both themes. - Apply the same class to the human-branch `MarkdownBody` in `TaskChatDescriptionBubble.tsx` (the description-as-first-bubble surface) for consistency. - Add unit tests covering that the human accent bubble carries the on-accent class and the agent/neutral bubbles do not. ## Verification - `pnpm check:token-gates` → 3/3 CLEAN. - `pnpm --filter ./ui vitest run src/components/task-chat/TaskChatBubble.test.tsx` → 9/9 passing. - Manual: in light mode, the requester's chat bubble text renders white on blue; agent/neutral bubbles unchanged; dark mode unchanged. This is a visual change. Snapshot baselines are intentionally not updated, per `doc/design/DECISION-SHEET.md` → "Per-change snapshot verification demoted to dormant (Jul 13 2026)". ## Risks Low risk. The change is scoped to the human-branch `MarkdownBody` className on two chat-bubble components and only remaps prose color tokens to the inherited text color. Agent and neutral bubbles are untouched, and dark mode behavior is unchanged. ## Model Used Claude Opus 4.8 (Anthropic), model id `claude-opus-4-8`, ~200K context window, extended thinking mode, with tool use / code execution. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Claude Opus 4.8 --- .../task-chat/TaskChatBubble.test.tsx | 42 +++++++++++++++++++ .../components/task-chat/TaskChatBubble.tsx | 12 +++++- .../task-chat/TaskChatDescriptionBubble.tsx | 9 +++- 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/ui/src/components/task-chat/TaskChatBubble.test.tsx b/ui/src/components/task-chat/TaskChatBubble.test.tsx index e16c4a20f9..9b3c041200 100644 --- a/ui/src/components/task-chat/TaskChatBubble.test.tsx +++ b/ui/src/components/task-chat/TaskChatBubble.test.tsx @@ -66,6 +66,48 @@ describe("TaskChatBubble attachment chips", () => { }); }); +describe("TaskChatBubble accent-bubble text color", () => { + let container: HTMLDivElement; + let root: Root | null = null; + + beforeEach(() => { + container = document.createElement("div"); + document.body.appendChild(container); + root = createRoot(container); + }); + + afterEach(() => { + flushSync(() => root?.unmount()); + root = null; + container.remove(); + }); + + function render(item: TaskChatMessageItem) { + flushSync(() => + root!.render( + + + , + ), + ); + } + + it("marks the human bubble's markdown as on-accent so prose text follows text-white", () => { + render({ id: "m1", kind: "message", author: "human", text: "when a new task is created…" }); + const body = container.querySelector(".paperclip-markdown"); + expect(body).not.toBeNull(); + // Without this class the light-mode prose body color reads as black on blue. + expect(body?.className).toContain("paperclip-markdown-on-accent"); + }); + + it("leaves the neutral agent bubble on default prose colors", () => { + render({ id: "m1", kind: "message", author: "agent", authorName: "CEO", text: "Final reply." }); + const body = container.querySelector(".paperclip-markdown"); + expect(body).not.toBeNull(); + expect(body?.className).not.toContain("paperclip-markdown-on-accent"); + }); +}); + describe("TaskChatBubble interstitial self-talk (PAP-357)", () => { let container: HTMLDivElement; let root: Root | null = null; diff --git a/ui/src/components/task-chat/TaskChatBubble.tsx b/ui/src/components/task-chat/TaskChatBubble.tsx index 549877d381..af8f71e3bb 100644 --- a/ui/src/components/task-chat/TaskChatBubble.tsx +++ b/ui/src/components/task-chat/TaskChatBubble.tsx @@ -99,7 +99,17 @@ export function TaskChatBubble({ item, attachedTurn }: TaskChatBubbleProps) { item.optimistic ? "opacity-80" : null, )} > - + {bodyText} diff --git a/ui/src/components/task-chat/TaskChatDescriptionBubble.tsx b/ui/src/components/task-chat/TaskChatDescriptionBubble.tsx index 025d101f25..28c874ac06 100644 --- a/ui/src/components/task-chat/TaskChatDescriptionBubble.tsx +++ b/ui/src/components/task-chat/TaskChatDescriptionBubble.tsx @@ -148,7 +148,14 @@ export function TaskChatDescriptionBubble({ brief }: TaskChatDescriptionBubblePr // toggle's muted colors need a lift on the solid blue bubble. toggleClassName={isHuman ? "text-white/80 hover:text-white hover:bg-white/10" : undefined} > - + {brief.description}