Skip to content
Twentyfirst UIHalo

Chat Message

AI · ChatMessage

One turn in a conversation with an agent: user bubble on the right, agent reply as rich text on the left.

Props: role ("user" | "assistant" | "system"), name, time, model, text (Markdown subset: paragraphs, bold, code, - lists, 1. lists, ### headings, links,

code blocks), `streaming` (blinking caret, no actions yet), `tint` (agent color), `sources` ({title, icon, url | onClick}), `attachments` ({name, size}), `usage` (tokens · cost text), `onRetry`, `onFeedback("up" | "down")`, `actions` (false hides the row), `before` / `after` (slots for tool calls, reasoning, approvals).

- User messages are ink pills, agent replies are plain text on the surface: the agent's words get the space.
- Actions (copy, regenerate, thumbs) appear once streaming ends.
- Number sources in the order they were used; keep titles short.
- `RichText` and `CodeBlock` are exported on their own for docs, notes and logs.
ME
Mara Ellis

Which invoices are overdue? Fix the reminder job too.

reminders.ts4 KB
Ledger09:41Claude Sonnet 5.5

Three invoices are overdue, €2,520 in total:

  • #2041 Harbor & Co · €860
  • #2058 Pinegate · €1,240
  • #2063 Orbit Foods · €420

The fix for the reminder job:

ts
const due = invoices.filter(i => i.dueAt < now && !i.paid);
await sendReminders(due, { tone: "friendly" });
1,842 tokens · €0.01
Ledger switched to Claude Haiku 5.5
Ledger

Sending reminders now. Harbor & Co first because

Props

ChatMessage

PropTypeNotes
role"user" | "assistant" | "system"Required
name?string
time?string
model?string
text?string
streaming?boolean
tint?Tint
sources?Source[]
attachments?{ name: string; size?: string; kind?: string }[]
usage?string
onRetry?() => void
onFeedback?(v: "up" | "down") => void
actions?boolean
before?ReactNode
after?ReactNode
children?ReactNode
feedback?"up" | "down" | null
className?stringExtra class on the root element.
style?CSSPropertiesInline style on the root element.