Pathfinderby Commenda
AI elements

Conversation

A typed transcript with reading-position preservation and app-owned composition.

Help me set up our entities.
I found three entities. Which one should I set up first?

Usage

Example.tsx
import { Conversation } from '@commenda/pathfinder/conversation';
import { Message } from '@commenda/pathfinder/message';

<Conversation
  messages={messages}
  getMessageKey={(message) => message.id}
  renderMessage={(message, { isLast }) => (
    <Message
      from={message.role}
      parts={message.parts}
      getPartKey={getPartKey}
      renderPart={renderPart}
      actionConfig={{
        retry: { isLatest: isLast, onRetry: regenerate },
      }}
    />
  )}
  announcement={turnAnnouncement}
  footer={<Composer {...composerProps} />}
  className="conversation-layout"
/>;

Give the container a bounded height or flex: 1; min-height: 0 inside a bounded flex layout. Conversation owns its scrolling viewport, not the page scroll. Change its React key when switching conversation IDs.

Docked request and recap

Help me set up our entities.
I found three entities. Which one should I set up first?

The live question sits 12px above Composer. Acceptance appends a stable recap part to the originating reply and removes the docked card. Temporary dismissal keeps the question pending and provides a way to reopen it. Escape belongs to the focused nested overlay first; the request handles it only after the overlay closes. Typing in Composer never activates a question's numeric shortcuts.

Empty conversation

Start a conversation.

Supply emptyState with suggested prompts or introductory content. Message fetching, permissions and transport remain in the host.

History and stream following

Use Load earlier messages, scroll into the history, and choose Append reply or Stream chunk. The viewport follows only within 32px of the bottom. Earlier rows retain their keys and the first visible row retains its offset. Jump to latest resumes following. ResizeObserver also handles delayed image/font/content growth. Scroll positioning is immediate and respects reduced-motion users without animated jumps.

historyConfig reports loading and an optional inline error. The host guards overlapping loads, keeps previously loaded items, and retries via onLoadMore. This component does not virtualize an unbounded history: paginate on the server and profile realistic histories before adding a virtualization layer.

API reference

PropTypeBehavior
messagesRequiredreadonly Item[]Original typed application/SDK objects
getMessageKeyRequired( message: Item, index: number ) => stringStable unique identity across prepend/append
renderMessageRequired( message: Item, context: { index: number; isLast: boolean; } ) => ReactNodeHost maps roles, states and parts
historyConfigObjectView fieldsApp-owned earlier-history loading
emptyStateReactNodeEmpty transcript content
footerReactNodeComposer, docked requests and related controls
announcementstringConcise turn-level live status
labelstringConversation and transcript accessible names
classNamestringBounded layout; no default fixed height
historyConfigObject fields
FieldTypeRequirement
hasMorebooleanRequired
isLoadingbooleanRequired
onLoadMore() => voidRequired
errorstringOptional

Design rules and integration

  • CONVERSATION-01: Preserve original objects and keyed part identity. Do not flatten text around tool calls or discard unknown custom parts.
  • CONVERSATION-02: Do not pull a reader to the bottom while they inspect history. Preserve the visible row during prepend and delayed layout changes.
  • CONVERSATION-03: The transcript is a keyboard-focusable region, not a per-token live log. Use announcement for “Response complete”, “Stopped” or an error once per transition.
  • CONVERSATION-04: Transport, providers, execution, feedback persistence and message storage remain in the application. Compose existing Pathfinder components through the footer and typed render callbacks.

The API follows the separation in Vercel AI Elements Conversation and Message, using Pathfinder's direct props instead of requiring compound children. Existing useChat messages can pass through unchanged; adapt rendering at the boundary. Migration of the existing application is out of scope. Download/export, virtualization and automatic message trimming are not performed here.

On this page