feat(demo): Stage 2 PR-2 — PTY transcript replay + recorder tool#222
Merged
luokerenx4 merged 1 commit intoMay 29, 2026
Merged
Conversation
The Terminal in demo mode now plays back a recorded PTY transcript using
xterm.js, replacing the static "terminal disabled" stub for workspaces
that have a registered transcript fixture. Stage 1's stub remains the
fallback for any workspace without one.
Transcript format (`ui/src/demo/types.ts`): `{label, durationMs, frames:
[{atMs, bytesB64}]}`. bytesB64 is base64-encoded raw PTY output bytes;
ANSI escapes, colors, cursor moves all replay faithfully because they
ride along inside the bytes.
DemoTerminalReplay (`ui/src/demo/DemoTerminalReplay.tsx`) — looks up a
transcript by workspace id, instantiates xterm with the same theme +
addons as the real Terminal, polls the container until it has real
dimensions before fitting + writing (avoids xterm's "Cannot read
properties of undefined (reading 'dimensions')" race in
Viewport.syncScrollArea), then schedules `term.write` calls at the
recorded timestamps via rAF. End of transcript → "↻ Replay again"
button.
Recorder (`ui/src/demo/recorder/recorder.ts`) — dev-mode helper that
monkey-patches `window.WebSocket` to tap server→client frames on URLs
matching `/api/workspaces/pty`, captures `{atMs, bytesB64}` per frame,
and on `__demoRecord.stop(label)` packages them as a JSON download.
Main.tsx auto-loads it in dev mode (NOT demo mode, NOT prod) so the
console-driven recorder is available alongside the real backend.
Placeholder transcript (`fixtures/transcripts/welcome.ts`) — hand-
crafted Claude-Code-style intro with banner, typed query, tool calls,
and a multi-paragraph response describing OpenAlice's architecture.
PR-3 will replace this with a real recorded session captured via the
recorder tool.
Verified:
- pnpm -F open-alice-ui exec tsc -b clean
- pnpm -F open-alice-ui build:demo: 277KB demo chunk + 2.83MB main chunk
- pnpm -F open-alice-ui build (prod): zero __demoRecord/
DemoTerminalReplay/transcriptsByWorkspace/welcomeTranscript/
startRecording/stopRecording/VITE_DEMO_MODE refs in dist
- pnpm dev:demo + Playwright walk: transcript plays back end-to-end,
ANSI colors + bold + dim render correctly, line wrapping respects
container width, "Replay again" button re-runs from start
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The demo terminal now plays back a recorded PTY transcript through xterm.js instead of showing a static "terminal disabled" stub. Workspaces without a registered transcript still fall back to the stub from PR-1.
Stacks on top of PR #221 (Vercel + skeleton).
What changed
Transcript format
bytesB64is the raw bytes the PTY WebSocket sent down. ANSI escapes, cursor positioning, colors all replay 1:1 because they ride along inside the bytes — no re-parsing needed.Replay component
ui/src/demo/DemoTerminalReplay.tsx— instantiated byTerminal.tsxwhenVITE_DEMO_MODEis on. Looks up a transcript bywsId; renders the stub from PR-1 if none registered. For sessions WITH a transcript:Viewport.syncScrollAreathat fires when fit() or term.write() runs before the renderer is fully laid out, AND avoids the "default 80×24 stuck at narrow width" failure mode where writes go to a 0-cell terminalterm.writeat recorded timestamps viarequestAnimationFrameRecorder tool
ui/src/demo/recorder/recorder.ts— dev-mode helper that monkey-patcheswindow.WebSocketto tap PTY frames. Usage:main.tsxauto-loads it in dev (not demo, not prod) so the recorder is always available alongside the real backend. URLs not matching/api/workspaces/ptypass through unchanged — limited blast radius.See
ui/src/demo/recorder/README.mdfor the full record-and-register flow.Placeholder transcript
ui/src/demo/fixtures/transcripts/welcome.ts— hand-crafted Claude-Code-style intro: banner, typed user query, tool calls, multi-paragraph response describing OpenAlice's architecture. Placeholder only — PR-3 swaps this out for a real recorded session captured via the recorder tool.Registered in
fixtures/transcripts/index.tskeyed toDEMO_WORKSPACE_ID. Adding more workspaces with their own transcripts is a one-line addition there.Verification
pnpm -F open-alice-ui exec tsc -bpnpm -F open-alice-ui build:demopnpm -F open-alice-ui build(prod)__demoRecord/DemoTerminalReplay/transcriptsByWorkspace/welcomeTranscript/startRecording/stopRecording/VITE_DEMO_MODErefs in dist (the singlerecordersubstring match is an unrelatedaudio_get_recorder_countstring inside a transitive dep)pnpm dev:demo+ PlaywrightScreenshot at end of replay attached in the PR thread (final-frame state shows the agent's full response with proper tool-call formatting and bullet styling).
Test plan
pnpm dev, exercises__demoRecord.start()/stop()on a real terminal session, replaceswelcome.tsplaceholder with the real transcript (= PR-3's job to land)Out of scope (deferred)
Boundary touch
None — no trading / auth / broker / migrations code modified.
🤖 Generated with Claude Code