R1+R2: shell and chat restyled to the reference system

Shell (main-chat-shell-spec): rail rebuilt around a 48px squircle claw
avatar stack with a 2px coral active ring + name beneath + online dot,
dashed 'New claw' tile, lucide-icon nav pills with the live credits
balance shown inline (coral when negative, aria-hidden so the nav link
name stays 'Credits'); 80px transparent chat header with 36px round
sessions/new-session icon buttons and the 42px coral-glow Computer
launcher; 768px centered content rail. RosterList computes the active
claw from the pathname so AgentRosterItem stays presentational.

Chat (chat-message-components): asymmetric layout — user pill #1A1A1A
radius 24/24/4 with inset white ring + dual shadow, capped 75%, vs the
bubble-less assistant message (50px squircle avatar, 15px gap, plain
14px/1.7 text); messageSlideIn entrance; 126px welcome avatar with the
24px/600/-0.6px heading (claw name in coral) and lucide-led suggestion
chips; floating 24px-radius neutral-800 composer card with the cream
Send pill; StepTrace rows restyled to the system.

Brand marks (Chrome, Slack) vendored to public/services from the bundle.
83 unit tests, full 29-journey functional E2E green.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-10 19:41:31 -05:00
co-authored by Claude Fable 5
parent 3ec1d3b829
commit 6e061515b8
17 changed files with 361 additions and 128 deletions
+17 -8
View File
@@ -10,11 +10,13 @@ import { StepTrace } from "./steps/StepTrace";
type DecideFn = (approvalId: string, decision: "approve" | "reject") => void;
/* User pill (measured): #1A1A1A fill, radius 24/24/4 (the tucked tail),
1px inset white ring, dual shadow, capped at 75%, right-aligned. */
function UserBubble({ message }: { message: UiMessage }) {
return (
<div className="flex justify-end">
<div className="flex w-full justify-end motion-safe:animate-[messageSlideIn_var(--duration-normal)_var(--ease-app)]">
<p
className={`max-w-[70%] whitespace-pre-wrap rounded-(--radius) bg-surface-warm-muted px-3 py-2 text-sm shadow-(--shadow-bubble) ${
className={`w-fit max-w-full rounded-[24px] rounded-br-[4px] bg-bubble-user px-3.5 py-2.5 text-sm leading-[1.7] whitespace-pre-wrap text-bubble-user-text shadow-bubble-user ring-1 ring-white/[0.07] ring-inset md:max-w-[75%] ${
message.status === "sending" ? "opacity-60" : ""
}`}
>
@@ -24,6 +26,8 @@ function UserBubble({ message }: { message: UiMessage }) {
);
}
/* Assistant message (measured): NO bubble — 50px avatar, 15px gap, plain
text flowing directly. */
function AgentMessage({
message,
agent,
@@ -34,10 +38,15 @@ function AgentMessage({
onDecide: DecideFn;
}) {
return (
<div className="flex gap-2">
<Avatar name={agent.name} accent={agent.accent} size="sm" />
<div className="min-w-0 flex-1">
<p className="whitespace-pre-wrap text-sm leading-relaxed">
<div className="flex items-start gap-[15px] motion-safe:animate-[messageSlideIn_var(--duration-normal)_var(--ease-app)]">
<Avatar
name={agent.name}
accent={agent.accent}
size="chat"
shape="squircle"
/>
<div className="min-w-0 flex-1 pt-3">
<p className="text-sm leading-[1.7] whitespace-pre-wrap text-neutral-200">
{message.text}
{message.status === "streaming" && (
<span
@@ -64,7 +73,7 @@ function AgentMessage({
);
}
/** The transcript (§5b): user bubbles right, agent messages left. */
/** The transcript (measured): user pills right, bare assistant text left. */
export function MessageList({
messages,
agent,
@@ -84,7 +93,7 @@ export function MessageList({
return (
<div
aria-live="polite"
className="flex flex-1 flex-col gap-4 overflow-y-auto px-4 py-6"
className="flex flex-1 flex-col gap-2 overflow-y-auto py-6"
>
{messages.map((message) =>
message.role === "user" ? (