Message
Ordered content, authors, actions, and recoverable reply states.
You’re missing two documents for the GST F5 return: the sales ledger and the purchase ledger. Your GST registration is already on file.
Usage
Pass your existing typed parts directly. The renderer receives the original part, including tool IDs, metadata and custom fields.
import { Message } from '@commenda/pathfinder/message';
import { Markdown } from '@commenda/pathfinder/markdown';
<Message
from="assistant"
parts={message.parts}
getPartKey={(part, index) => part.id ?? `${message.id}:${index}`}
renderPart={(part) => {
switch (part.type) {
case 'text':
return <Markdown content={part.text} />;
case 'request-recap':
return <ResponseRecap {...part.recap} />;
default:
return renderCustomPart(part);
}
}}
actionConfig={{
copy: { text: plainText },
feedback: { value: feedback, onChange: saveFeedback },
retry: { isLatest: isLatestReply, onRetry: regenerate },
}}
/>;Use a stable tool/part ID where available. An index fallback is safe only for append-only SDK parts; never reorder those parts. Keep Markdown a separate subpath import. Message does not import a parser or require an AI SDK provider.
User and people threads
from is a presentation choice. A person in a shared thread does not introduce a new AI SDK role. User avatars come from authorConfig; set showName for shared conversations. People threads show the name automatically. Assistant replies never render the author avatar.
System events
Keep system events short. They are visual timeline events, not model system instructions.
Versions
You’re missing two documents for the GST F5 return: the sales ledger and the purchase ledger. Your GST registration is already on file.
The host stores alternatives and swaps the selected message. Keep the active index within [0, count - 1]. Controls appear only with at least two alternatives; changing version does not call the model.
Interleaved activity and content
You’re missing two documents for the GST F5 return: the sales ledger and the purchase ledger. Your GST registration is already on file.
Reasoning and tool calls remain ordered parts of their originating reply. Applications map their existing tool state into ToolCall without flattening the transcript.
Streaming and stopped replies
You’re missing two documents for the GST F5 return: the sales ledger and the purchase ledger. Your GST registration is already on file.
Hide actions while streaming. A stopped reply retains its parts and exposes its configured actions. Put activity components in the original parts array when activity is associated with that turn.
Partial failure
You’re missing two documents for the GST F5 return: the sales ledger and the purchase ledger. Your GST registration is already on file.
Errors preserve streamed content and hide the regular action toolbar. Retry appears inline only for an eligible latest assistant reply. The host handles retry failures and supplies the next state; Message never starts network requests.
API reference
| Prop | Type | Behavior |
|---|---|---|
fromRequired | "user"|"assistant"|"system"|"person" | Display kind, separate from the app's model role |
partsRequired | readonly Part[] | Original ordered typed parts |
getPartKeyRequired | (
part: Part,
index: number
) => string | Stable unique key within the message |
renderPartRequired | (
part: Part,
index: number
) => ReactNode | Typed content renderer; preserve unsupported parts intentionally |
state | MessageStateView fields · 2 shapes | Done by default |
authorConfig | ObjectView fields | User/person Avatar, no assistant avatar |
timestamp | ObjectView fields | Host-formatted accessible time |
attachmentConfig | AttachmentGroupPropsView fields | Attachments above the bubble, aligned by author |
actionConfig | MessageActionConfigView fields | Copy, controlled feedback, latest-only retry, reply and controlled pin |
branchConfig | MessageBranchConfigView fields | Controlled zero-based alternative selection |
label | string | Accessible article name and layout extension |
className | string | Accessible article name and layout extension |
stateObject fields
Choose one of these 2 shapes. Do not combine fields excluded by the selected shape.
Option 1
| Field | Type | Requirement |
|---|---|---|
status | "streaming" | "done" | "stopped" | Required |
Option 2
| Field | Type | Requirement |
|---|---|---|
status | "error" | Required |
message | string | Required |
timestampObject fields
| Field | Type | Requirement |
|---|---|---|
dateTime | string | Required |
label | string | Required |
attachmentConfigObject fields
| Field | Type | Requirement |
|---|---|---|
items | readonly AttachmentItem[] | Required |
align | "start" | "end" | Optional |
maxRows | 1 | 2 | 3 | Optional |
getPreview | ((item: AttachmentItem) => AttachmentProps["previewConfig"]) | Optional |
className | string | Optional |
label | string | Optional |
onPreview | ((item: AttachmentItem) => void) | Optional |
onRetry | ((item: AttachmentItem) => void) | Optional |
onRemove | ((item: AttachmentItem) => void) | Optional |
actionConfigObject fields
| Field | Type | Requirement |
|---|---|---|
copy | Object | Optional |
feedback | Object | Optional |
retry | Object | Optional |
onReply | (() => void) | Optional |
pin | Object | Optional |
actionConfig.copyObject fields
| Field | Type | Requirement |
|---|---|---|
text | string | Required |
actionConfig.feedbackObject fields
| Field | Type | Requirement |
|---|---|---|
value | "good" | "bad" | null | Required |
onChange | (value: "good" | "bad" | null) => void | Required |
actionConfig.retryObject fields
| Field | Type | Requirement |
|---|---|---|
isLatest | boolean | Required |
onRetry | () => void | Required |
disabled | boolean | Optional |
actionConfig.pinObject fields
| Field | Type | Requirement |
|---|---|---|
isPinned | boolean | Required |
onChange | (value: boolean) => void | Required |
branchConfigObject fields
| Field | Type | Requirement |
|---|---|---|
index | number | Required Zero-based active alternative; keep the message/part keys stable within each alternative. |
count | number | Required |
onChange | (index: number) => void | Required |
actionConfig.copy accepts { text }. Feedback accepts { value: 'good' | 'bad' | null, onChange }; clicking a chosen thumb clears it. Retry accepts { isLatest, onRetry, disabled? }. People-thread actions use onReply and pin: { isPinned, onChange }. Callbacks own persistence and error reporting; only copy writes to the clipboard locally.
Design rules and accessibility
- MESSAGE-01: User bubble pearl 300, person bubble pearl 200, radius 12, padding 12 vertically/16 horizontally, content at most 520px. Rendered frames use Copy/15, superseding the stale Copy/13 description.
- MESSAGE-02: Assistant parts stack 16px apart without an avatar. Generated files, requests and recaps stay in their originating reply.
- MESSAGE-03: Actions sit 8px below per the component description. No actions while streaming; branches require alternatives; retry requires latest-reply eligibility. Copy changes to a check for two seconds, selected feedback changes fill rather than weight.
- MESSAGE-04: Keep partial output and show the error inline. Do not announce each token or replace the reply with a toast.
Long messages wrap; turn long pasted documents into attachments at the application boundary. Screen readers navigate named articles and labeled action buttons. The Storybook worst-case switch retains Unicode, unbroken text and long author fixtures.