Converts a stored `HistoricalMessage[]` into ordered `StreamFrame` playback frames that animate a chat surface identically to the live NATS/SSE stream — without requiring a network connection. ## Key Components ### Types - **`StreamFrame`** — A single rendered playback step containing the partial message list, `StreamingPhase`, `typing` indicator state, and `delayMs` hold time before advancing. ### Functions | Function | Description | |---|---| | `normalizeHistoricalMessageData` | Normalizes `messageData` (single item or array) to `MessageData[]`. | | `isHistoricalUserTurn` | Returns `true` when a message has an `owner` (user-authored); assistant turns have none. | | `buildStreamFrames` | Core export — expands a full message list into a deterministic sequence of `StreamFrame`s. | ### Playback Cadence Constants | Constant | Value | Role | |---|---|---| | `THINK_MS` | 550 ms | Pause before assistant reply begins streaming | | `TOKEN_MS` | 55 ms | Delay per word revealed during text streaming | | `USER_MS` | 480 ms | Dwell after a user turn lands | | `SEGMENT_MS` | 340 ms | Pop-in delay for tool/approval card segments | | `POST_MS` | 240 ms | Dwell after a completed assistant turn | ## Usage Example ```typescript import { buildStreamFrames } from './scripted-stream' import type { HistoricalMessage } from '../types' const conversation: HistoricalMessage[] = [ { owner: 'user', content: 'How do I reset a password?', messageData: null }, { content: 'Here is how you reset a password…', messageData: [{ type: 'TEXT', text: 'Here is how you reset a password…' }], }, ] const frames = buildStreamFrames(conversation) // Drive an EmbeddableChat in previewMode, e.g. inside useScriptedStream: frames.forEach((frame, i) => { setTimeout(() => { renderChat(frame.messages, frame.phase, frame.typing) }, frames.slice(0, i).reduce((sum, f) => sum + f.delayMs, 0)) }) ``` > **Note:** Frame count for any shared message prefix is deterministic — a host that appends a continuation after an approval resumes seamlessly without rebuilding earlier frames. Pair with `useScriptedStream` for the scheduling clock.