From 94c28ce22ef1c0cb2464cfc14f855cb626caf34a Mon Sep 17 00:00:00 2001 From: Brad Harris Date: Thu, 3 Sep 2026 22:20:18 -0600 Subject: [PATCH 1/2] fix(chat): make user and peer post tints readable in every theme Co-Authored-By: Claude Opus 5 (1M context) --- .../src/components/app/chat/chat-entries.tsx | 17 +++++++++++++++-- .../src/components/app/chat/chat-feed.test.tsx | 15 ++++++++++----- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/apps/web/src/components/app/chat/chat-entries.tsx b/apps/web/src/components/app/chat/chat-entries.tsx index 169099b6..933ac289 100644 --- a/apps/web/src/components/app/chat/chat-entries.tsx +++ b/apps/web/src/components/app/chat/chat-entries.tsx @@ -243,11 +243,23 @@ function Avatar({ * reads as one block. */ export const POST_TINT: Record = { - user: "bg-primary/[0.06] hover:bg-primary/[0.09]", - peer: "bg-violet-500/[0.06] hover:bg-violet-500/[0.09]", + // A tint alone is unreliable: in several themes `primary` sits close to the + // page background, so the block all but disappears. The fill is stronger + // now and carries a solid left accent bar, which reads in every theme + // regardless of how much the fill contrasts. + user: "bg-primary/[0.10] hover:bg-primary/[0.14] before:bg-primary/70", + peer: "bg-violet-500/[0.09] hover:bg-violet-500/[0.13] before:bg-violet-500/60", agent: "hover:bg-muted/40", }; +/** + * The accent bar itself: a 2px rule down the left edge of a tinted post, + * drawn as a pseudo-element so it spans the row without shifting the layout. + * Agent prose has no bar (its tint entry sets no `before:` color). + */ +const POST_ACCENT = + "before:absolute before:inset-y-0 before:left-0 before:w-0.5 before:content-['']"; + /** * Post bodies stop growing at a comfortable reading measure; on a wide pane * a paragraph must not run edge to edge. The tint and the header still span @@ -302,6 +314,7 @@ export function Post({ "group relative flex gap-3 transition-colors", side ? cn(SIDE_POST_INDENT, "pr-4") : "px-4", side ? POST_TINT.peer : POST_TINT[author.kind], + author.kind !== "agent" || side ? POST_ACCENT : null, grouped ? "py-1" : "mt-3 pb-1.5 pt-2", rule && "border-t border-border/40" )} diff --git a/apps/web/src/components/app/chat/chat-feed.test.tsx b/apps/web/src/components/app/chat/chat-feed.test.tsx index ca40bd6f..96af30d4 100644 --- a/apps/web/src/components/app/chat/chat-feed.test.tsx +++ b/apps/web/src/components/app/chat/chat-feed.test.tsx @@ -14,6 +14,7 @@ import { POST_BODY_MEASURE, SIDE_POST_INDENT, peerDirectory, + POST_TINT, } from "@/components/app/chat/chat-entries"; import { ChatFeed, @@ -513,7 +514,7 @@ describe("ChatFeed", () => { "Agent agent", ]); // Still a peer post: violet, with the sender's name. - expect(posts[0]!.className).toContain("bg-violet-500/[0.06]"); + expect(posts[0]!.className).toContain(POST_TINT.peer); expect( posts[0]!.querySelector('[data-testid="chat-post-author"]')?.textContent ).toBe("agt_kid"); @@ -704,18 +705,22 @@ describe("ChatFeed", () => { expect(agentPost!.getAttribute("data-rule")).toBeNull(); expect(userOne!.getAttribute("data-author-kind")).toBe("user"); - expect(userOne!.className).toContain("bg-primary/[0.06]"); + expect(userOne!.className).toContain(POST_TINT.user); + // The accent bar carries the distinction in themes where the fill is + // close to the page background. + expect(userOne!.className).toContain("before:w-0.5"); + expect(agentPost!.className).not.toContain("before:w-0.5"); expect(userOne!.getAttribute("data-group-start")).toBe("true"); expect(userOne!.getAttribute("data-rule")).toBe("true"); expect(userOne!.className).toContain("border-t"); // A grouped row keeps the tint (one block) but no boundary of its own. - expect(userTwo!.className).toContain("bg-primary/[0.06]"); + expect(userTwo!.className).toContain(POST_TINT.user); expect(userTwo!.getAttribute("data-group-start")).toBeNull(); expect(userTwo!.getAttribute("data-rule")).toBeNull(); expect(userTwo!.className).not.toContain("border-t"); expect(peer.getAttribute("data-author-kind")).toBe("peer"); - expect(peer.className).toContain("bg-violet-500/[0.06]"); + expect(peer.className).toContain(POST_TINT.peer); expect(peer.getAttribute("data-rule")).toBe("true"); // Bodies stop at a reading measure; the row itself spans the pane. @@ -1140,7 +1145,7 @@ describe("ChatFeed", () => { expect(post.getAttribute("data-side")).toBe("true"); expect(post.className).toContain(SIDE_POST_INDENT); expect(post.className).not.toContain("px-4"); - expect(post.className).toContain("bg-violet-500/[0.06]"); + expect(post.className).toContain(POST_TINT.peer); const body = Array.from(post.querySelectorAll("div")).find((el) => el.className.includes(POST_BODY_MEASURE) ); From d51cf061af00badf846f39ee788c34d5601330d0 Mon Sep 17 00:00:00 2001 From: Brad Harris Date: Fri, 4 Sep 2026 07:28:55 -0600 Subject: [PATCH 2/2] fix(chat): drop the post accent bars, mute agent-to-agent posts Co-Authored-By: Claude Opus 5 (1M context) --- .../src/components/app/chat/chat-entries.tsx | 24 +++++++------------ .../components/app/chat/chat-feed.test.tsx | 8 +++---- docs/chat-surface-plan.md | 2 +- 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/apps/web/src/components/app/chat/chat-entries.tsx b/apps/web/src/components/app/chat/chat-entries.tsx index 933ac289..d4d1cab6 100644 --- a/apps/web/src/components/app/chat/chat-entries.tsx +++ b/apps/web/src/components/app/chat/chat-entries.tsx @@ -243,23 +243,18 @@ function Avatar({ * reads as one block. */ export const POST_TINT: Record = { - // A tint alone is unreliable: in several themes `primary` sits close to the - // page background, so the block all but disappears. The fill is stronger - // now and carries a solid left accent bar, which reads in every theme - // regardless of how much the fill contrasts. - user: "bg-primary/[0.10] hover:bg-primary/[0.14] before:bg-primary/70", - peer: "bg-violet-500/[0.09] hover:bg-violet-500/[0.13] before:bg-violet-500/60", + // Only the user's own posts get a fill. At 6% `primary` sat too close to the + // page background in several themes to notice; 10% reads everywhere. + // + // No left accent bar: a rule down the post's left edge competed with the + // connected-agent border the sidebar draws on the pane's left edge. + user: "bg-primary/[0.10] hover:bg-primary/[0.14]", + // Agent-to-agent traffic is a side conversation the user is overhearing, so + // it recedes — indent and muted body carry it, with no fill of its own. + peer: "hover:bg-muted/30", agent: "hover:bg-muted/40", }; -/** - * The accent bar itself: a 2px rule down the left edge of a tinted post, - * drawn as a pseudo-element so it spans the row without shifting the layout. - * Agent prose has no bar (its tint entry sets no `before:` color). - */ -const POST_ACCENT = - "before:absolute before:inset-y-0 before:left-0 before:w-0.5 before:content-['']"; - /** * Post bodies stop growing at a comfortable reading measure; on a wide pane * a paragraph must not run edge to edge. The tint and the header still span @@ -314,7 +309,6 @@ export function Post({ "group relative flex gap-3 transition-colors", side ? cn(SIDE_POST_INDENT, "pr-4") : "px-4", side ? POST_TINT.peer : POST_TINT[author.kind], - author.kind !== "agent" || side ? POST_ACCENT : null, grouped ? "py-1" : "mt-3 pb-1.5 pt-2", rule && "border-t border-border/40" )} diff --git a/apps/web/src/components/app/chat/chat-feed.test.tsx b/apps/web/src/components/app/chat/chat-feed.test.tsx index 96af30d4..a4d64bed 100644 --- a/apps/web/src/components/app/chat/chat-feed.test.tsx +++ b/apps/web/src/components/app/chat/chat-feed.test.tsx @@ -513,7 +513,7 @@ describe("ChatFeed", () => { // Not in the list any more: the generic agent icon. "Agent agent", ]); - // Still a peer post: violet, with the sender's name. + // Still a peer post: muted side-conversation treatment, sender's name. expect(posts[0]!.className).toContain(POST_TINT.peer); expect( posts[0]!.querySelector('[data-testid="chat-post-author"]')?.textContent @@ -706,10 +706,8 @@ describe("ChatFeed", () => { expect(userOne!.getAttribute("data-author-kind")).toBe("user"); expect(userOne!.className).toContain(POST_TINT.user); - // The accent bar carries the distinction in themes where the fill is - // close to the page background. - expect(userOne!.className).toContain("before:w-0.5"); - expect(agentPost!.className).not.toContain("before:w-0.5"); + // No accent bar: it competed with the sidebar's connected-agent border. + expect(userOne!.className).not.toContain("before:w-0.5"); expect(userOne!.getAttribute("data-group-start")).toBe("true"); expect(userOne!.getAttribute("data-rule")).toBe("true"); expect(userOne!.className).toContain("border-t"); diff --git a/docs/chat-surface-plan.md b/docs/chat-surface-plan.md index 50ad61d3..7e1ab669 100644 --- a/docs/chat-surface-plan.md +++ b/docs/chat-surface-plan.md @@ -378,7 +378,7 @@ changes; the flag, routes and tools are unchanged. share a parent, otherwise **agent** — also the fallback when the sender is no longer in the list (generic bot icon). `agentRelation` in `lib/agent-lineage.ts`; the same chip sits on the Messages panel's thread - headers so the two agree. The violet tint and the outgoing "to " + headers so the two agree. The muted side-conversation treatment and the outgoing "to " line stay. - Entries that arrive after the feed first rendered — new posts, status lines, media, and posts edited in place — fade in (200 ms opacity with a