Pathfinderby Commenda
AI elements

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.

Example.tsx
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

Send it over Slack.
Alex Morgan
Morning Sam. The Delaware annual report is ready to file. Can you confirm the director list is still current?

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

Priya Shah joined the conversation · 9:14 AM

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.

1 of 2

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

Ran Find documentsFound two ledger exports.

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.

Reviewing the uploaded records.

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.

Connection lost. Your partial reply is preserved.

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

PropTypeBehavior
fromRequired"user"|"assistant"|"system"|"person"Display kind, separate from the app's model role
partsRequiredreadonly Part[]Original ordered typed parts
getPartKeyRequired( part: Part, index: number ) => stringStable unique key within the message
renderPartRequired( part: Part, index: number ) => ReactNodeTyped content renderer; preserve unsupported parts intentionally
stateMessageStateView fields · 2 shapesDone by default
authorConfigObjectView fieldsUser/person Avatar, no assistant avatar
timestampObjectView fieldsHost-formatted accessible time
attachmentConfigAttachmentGroupPropsView fieldsAttachments above the bubble, aligned by author
actionConfigMessageActionConfigView fieldsCopy, controlled feedback, latest-only retry, reply and controlled pin
branchConfigMessageBranchConfigView fieldsControlled zero-based alternative selection
labelstringAccessible article name and layout extension
classNamestringAccessible article name and layout extension
stateObject fields

Choose one of these 2 shapes. Do not combine fields excluded by the selected shape.

Option 1

FieldTypeRequirement
status"streaming" | "done" | "stopped"Required

Option 2

FieldTypeRequirement
status"error"Required
messagestringRequired
authorConfigObject fields
FieldTypeRequirement
identityObjectRequired
showNamebooleanOptional
authorConfig.identityObject fields
FieldTypeRequirement
kind"person"Required
userIdstringOptional
namestringOptional
emailstringOptional
srcstringOptional
timestampObject fields
FieldTypeRequirement
dateTimestringRequired
labelstringRequired
attachmentConfigObject fields
FieldTypeRequirement
itemsreadonly AttachmentItem[]Required
align"start" | "end"Optional
maxRows1 | 2 | 3Optional
getPreview((item: AttachmentItem) => AttachmentProps["previewConfig"])Optional
classNamestringOptional
labelstringOptional
onPreview((item: AttachmentItem) => void)Optional
onRetry((item: AttachmentItem) => void)Optional
onRemove((item: AttachmentItem) => void)Optional
actionConfigObject fields
FieldTypeRequirement
copyObjectOptional
feedbackObjectOptional
retryObjectOptional
onReply(() => void)Optional
pinObjectOptional
actionConfig.copyObject fields
FieldTypeRequirement
textstringRequired
actionConfig.feedbackObject fields
FieldTypeRequirement
value"good" | "bad" | nullRequired
onChange(value: "good" | "bad" | null) => voidRequired
actionConfig.retryObject fields
FieldTypeRequirement
isLatestbooleanRequired
onRetry() => voidRequired
disabledbooleanOptional
actionConfig.pinObject fields
FieldTypeRequirement
isPinnedbooleanRequired
onChange(value: boolean) => voidRequired
branchConfigObject fields
FieldTypeRequirement
indexnumberRequired

Zero-based active alternative; keep the message/part keys stable within each alternative.

countnumberRequired
onChange(index: number) => voidRequired

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.

On this page