From 2a51a388d5bbb2e16b885a2ef2d0f00ecb446b56 Mon Sep 17 00:00:00 2001 From: Raghav Chari Date: Fri, 10 Jul 2026 15:03:09 -0400 Subject: [PATCH 1/2] =?UTF-8?q?fix(chat):=20assistant=20messages=20wrap=20?= =?UTF-8?q?instead=20of=20clipping=20(min-width:0=20on=20the=20assistant-m?= =?UTF-8?q?essage=20flex=20column=20+=20text=20parts=20=E2=80=94=20the=20c?= =?UTF-8?q?ontainer=20chain=20had=20it,=20the=20message=20column=20was=20t?= =?UTF-8?q?he=20gap);=20AMICO=20signature=20heads=20EVERY=20assistant=20tu?= =?UTF-8?q?rn,=20not=20only=20interview=20receipt=20cards=20(brand=20ident?= =?UTF-8?q?ity=20shouldn't=20vanish=20in=20plain=20chat)=20=E2=80=94=20reu?= =?UTF-8?q?ses=20Kate's=20amc-sig=20restyle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- packages/ui/src/components/message-part.css | 12 ++ packages/ui/src/components/message-part.tsx | 172 +++++++++++--------- 2 files changed, 104 insertions(+), 80 deletions(-) diff --git a/packages/ui/src/components/message-part.css b/packages/ui/src/components/message-part.css index 78d574d8a1..553849de3d 100644 --- a/packages/ui/src/components/message-part.css +++ b/packages/ui/src/components/message-part.css @@ -1,12 +1,24 @@ [data-component="assistant-message"] { content-visibility: auto; width: 100%; + /* min-width:0 — a flex column won't shrink below its content's intrinsic + width without it, so a long unbroken-ish line (URLs, "overlapping" running + into the next word) pushed the message past the viewport and clipped on the + right. The container chain already sets this; the message column + its + text parts were the gap. */ + min-width: 0; display: flex; flex-direction: column; align-items: flex-start; gap: 12px; } +[data-component="assistant-message"] [data-component="text-part"], +[data-component="assistant-message"] [data-slot="text-part-body"] { + min-width: 0; + max-width: 100%; +} + [data-component="user-message"] { font-family: var(--font-family-sans); font-size: var(--font-size-base); diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index cd4d427050..0f94a3e21e 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -1,4 +1,4 @@ -import { AmicoSpinner } from "../amicode/spinner" +import { AmicoSpinner, AmicoMark } from "../amicode/spinner" import { Component, createEffect, @@ -605,88 +605,100 @@ export function AssistantParts(props: { const last = createMemo(() => grouped().at(-1)?.key) return ( - - {(entryAccessor) => { - const entryType = createMemo(() => entryAccessor().type) - - return ( - - - {(() => { - const parts = createMemo( - () => { + <> + {/* amicode: the AMICO signature heads EVERY assistant turn, not just the + interview receipt cards — the brand identity shouldn't vanish in plain + chat. Shown once the turn has renderable parts; running-state animates + the mark. Styling is Kate's restyle (amc-sig/amc-wordmark), untouched. */} + 0}> + + + AMICO + + + + {(entryAccessor) => { + const entryType = createMemo(() => entryAccessor().type) + + return ( + + + {(() => { + const parts = createMemo( + () => { + const entry = entryAccessor() + if (entry.type !== "context") return emptyTools + return entry.refs + .map((ref) => part().get(ref.messageID)?.get(ref.partID)) + .filter((part): part is ToolPart => !!part && isContextGroupTool(part)) + }, + emptyTools, + { equals: same }, + ) + const busy = createMemo(() => props.working && last() === entryAccessor().key) + + return ( + 0}> + + + ) + })()} + + + {(() => { + const parts = createMemo( + () => { + const entry = entryAccessor() + if (entry.type !== "shell") return emptyTools + return entry.refs + .map((ref) => part().get(ref.messageID)?.get(ref.partID)) + .filter((part): part is ToolPart => !!part && isShellGroupTool(part)) + }, + emptyTools, + { equals: same }, + ) + const busy = createMemo(() => props.working && last() === entryAccessor().key) + + return ( + 0}> + + + ) + })()} + + + {(() => { + const message = createMemo(() => { const entry = entryAccessor() - if (entry.type !== "context") return emptyTools - return entry.refs - .map((ref) => part().get(ref.messageID)?.get(ref.partID)) - .filter((part): part is ToolPart => !!part && isContextGroupTool(part)) - }, - emptyTools, - { equals: same }, - ) - const busy = createMemo(() => props.working && last() === entryAccessor().key) - - return ( - 0}> - - - ) - })()} - - - {(() => { - const parts = createMemo( - () => { + if (entry.type !== "part") return + return msgs().get(entry.ref.messageID) + }) + const item = createMemo(() => { const entry = entryAccessor() - if (entry.type !== "shell") return emptyTools - return entry.refs - .map((ref) => part().get(ref.messageID)?.get(ref.partID)) - .filter((part): part is ToolPart => !!part && isShellGroupTool(part)) - }, - emptyTools, - { equals: same }, - ) - const busy = createMemo(() => props.working && last() === entryAccessor().key) - - return ( - 0}> - - - ) - })()} - - - {(() => { - const message = createMemo(() => { - const entry = entryAccessor() - if (entry.type !== "part") return - return msgs().get(entry.ref.messageID) - }) - const item = createMemo(() => { - const entry = entryAccessor() - if (entry.type !== "part") return - return part().get(entry.ref.messageID)?.get(entry.ref.partID) - }) - - return ( - - - + if (entry.type !== "part") return + return part().get(entry.ref.messageID)?.get(entry.ref.partID) + }) + + return ( + + + + - - ) - })()} - - - ) - }} - + ) + })()} + + + ) + }} + + ) } From 67c10f6911b8a7302855adae35762833c3e531c0 Mon Sep 17 00:00:00 2001 From: Raghav Chari Date: Fri, 10 Jul 2026 15:34:18 -0400 Subject: [PATCH 2/2] fix(chat): prose no longer clips on the right + AMICO signature on every assistant turn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - assistant-message flex column + its text parts lacked min-width:0, so a long line pushed the message past the viewport and clipped (the 'overlapping transfers' cutoff). Add min-width:0 / max-width:100%. - the AMICO mark+wordmark only appeared on interview receipt cards; it now heads plain-prose assistant turns too (suppressed when a turn already has an amicode_* card, which carries its own signature). Reuses Kate's restyle untouched — presence, not repaint. Co-Authored-By: Claude Fable 5 --- packages/ui/src/components/message-part.tsx | 23 ++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index 0f94a3e21e..41320c33be 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -604,13 +604,26 @@ export function AssistantParts(props: { const last = createMemo(() => grouped().at(-1)?.key) + // amicode: does this turn already carry an amicode_* tool card? Those cards + // (card.tsx) render their OWN AMICO signature, so a turn-level one would + // double up. The turn-level signature exists precisely for the OTHER case — + // plain prose replies, which otherwise show no Amico identity at all. + const hasAmicodeCard = createMemo(() => + props.messages.some((message) => + list(data.store.part?.[message.id], emptyParts).some( + (p) => p.type === "tool" && /^amicode_/.test((p as ToolPart).tool ?? ""), + ), + ), + ) + return ( <> - {/* amicode: the AMICO signature heads EVERY assistant turn, not just the - interview receipt cards — the brand identity shouldn't vanish in plain - chat. Shown once the turn has renderable parts; running-state animates - the mark. Styling is Kate's restyle (amc-sig/amc-wordmark), untouched. */} - 0}> + {/* amicode: the AMICO signature heads assistant turns so the brand + identity doesn't vanish in plain chat (it only lived on interview + receipt cards before). Suppressed when a turn already has an + amicode_* card — that card brings its own signature. Kate's restyle + (amc-sig/amc-wordmark) is reused untouched. */} + 0 && !hasAmicodeCard()}> AMICO