Skip to main content

Documentation Index

Fetch the complete documentation index at: https://chatjs.dev/docs/llms.txt

Use this file to discover all available pages before exploring further.

This cookbook used to describe the route-transition provider. That provider has been removed from the active architecture. The current first-message flow is owned by persistent chat runtimes instead of a temporary route transition. For the full migration plan, see CHAT_RUNTIME_MIGRATION_PLAN.md at the repo root.

The Problem

The first-message flow has competing requirements:
  1. A new chat needs a real URL like /chat/:id or /project/:projectId/chat/:chatId
  2. The submitted message and pending assistant placeholders must survive route changes
  3. Normal navigation between unrelated chats must not leak state
  4. Backend queries for persisted chat data must not run before the provisional chat is confirmed
  5. Streams should continue even when the route tree changes
Using a route-local controller makes the App Router navigation own too much of the chat lifecycle. A route remount can stop the stream, lose optimistic state, or fetch persisted chat data before the server has confirmed the row.

Current Solution

ChatRuntimeRegistryProvider owns live chat runtime entries. A provisional chat starts by registering a runtime with:
  • the generated chat id
  • an owned chat store
  • the submitted user message
  • pending primary submission data
  • parallel request specs
  • persistenceStatus: "provisional"
MountedChatRuntimes renders controllers outside the route tree, so streaming continues while navigation changes which chat is visible. ChatRouteHost is now a view resolver. It decides whether the visible route is backed by a live runtime store or by persisted query data. Persisted chat and message queries are enabled only when there is no live runtime or when the live runtime is confirmed.

Parts Involved

PartResponsibility
parseChatIdFromPathnameClassifies /, /chat/:id, /project/:projectId, /project/:projectId/chat/:chatId, /share/:id, and passthrough routes
ChatRuntimeRegistryProviderOwns live runtime entries and provisional/confirmed persistence state
useChatRuntimeApiProvides the public runtime boundary for lookup, provisional startup, confirmation, and runtime-targeted sends
MountedChatRuntimesKeeps runtime controllers mounted outside route transitions
useDraftChatId / resetDraftChatIdGenerates per-home and per-project draft ids and advances them after promotion
useStartProvisionalChatCreates the provisional runtime, adds optimistic messages, and updates browser history
ChatRouteHostChooses live runtime data or persisted query data for the visible route
ChatSystemRenders the chat tree against either a route-owned store or an external runtime store
ChatSyncOwns the transport for a mounted controller and reports data-chatConfirmed
RuntimeConfirmationControllerInvalidates persisted queries and runs secondary parallel requests after confirmation
/api/chatIdempotently creates the chat/user/assistant records, streams the primary response, and emits data-chatConfirmed
/api/chat/preparePrepares secondary parallel requests against an already-created chat

Flow

  1. ChatRouteHost renders / or /project/:projectId with a draft id.
  2. MultimodalInput or SuggestedActions calls buildDraftChatSubmission.
  3. useStartProvisionalChat registers a provisional runtime, adds the user and pending assistant messages, and calls window.history.pushState.
  4. The destination route re-renders through ChatRouteHost.
  5. If a live runtime exists for the destination chat id, ChatRouteHost uses its store and suppresses route-owned ChatSync.
  6. The mounted runtime’s ChatSync sends the primary request and receives stream data.
  7. On data-chatConfirmed, ChatSync marks the runtime confirmed.
  8. RuntimeConfirmationController invalidates persisted chat queries and runs secondary parallel requests.
  9. Later route navigation only swaps the visible runtime or persisted data source; it does not own stream lifetime.

Runtime Keys

Draft routes include the draft id in the base key:
path:${pathname}:draft:${draftChatId}
Persisted routes use:
path:${pathname}
When a live runtime backs the route, the visible ChatSystem uses the runtime id instead. That keeps the visible tree attached to the runtime-owned store without reintroducing a separate transition key model.

Query Gating

Persisted chat queries should only run when one of these is true:
  • there is no live runtime for the route chat id
  • the live runtime has persistenceStatus: "confirmed"
This prevents provisional routes from calling chat.getChatById or chat.getChatMessages before the server has confirmed the chat.

Parallel Responses

The runtime entry carries all parallel response metadata:
  • the first request is sent by the mounted runtime controller
  • pending assistant placeholders are added when the provisional runtime starts
  • after confirmation, secondary request specs run from RuntimeConfirmationController
  • failed secondary requests are converted from pending placeholders into empty assistant messages with activeStreamId: null