Skip to content

feat(terminal): render Claude/Codex transcripts beside the terminal; keep it mounted across swaps - #1489

Merged
xiaolai merged 2 commits into
mainfrom
feat/terminal-transcript
Sep 30, 2026
Merged

xiaolai merged 2 commits into
mainfrom
feat/terminal-transcript

Conversation

@xiaolai

@xiaolai xiaolai commented Sep 30, 2026

Copy link
Copy Markdown
Owner

Summary

Two changes, one commit each.

fix(shell): keep the terminal mounted when it swaps sides

Swapping the terminal between top and bottom (or left and right) killed every running shell, including programs like a Claude session. EditorArea rendered the panel in one of two child slots depending on position, so a swap made React unmount and remount TerminalPanel, whose unmount disposes all PTYs. The panel now stays in one slot; top and left reverse the flex axis instead. This bug had been present since the swap control shipped (47228ef).

  • Regression test: a stateful panel swapped through every position mounts once (failed before the fix).
  • WebKit test: every position still draws the panel on its side of the editor.
  • Verified live: a shell PID survived repeated top↔bottom and left↔right swaps.

feat(terminal): render Claude/Codex transcripts beside the terminal

New opt-in setting Settings → Terminal → Automatic transcript rendering (off by default). It renders the Markdown tables and Mermaid diagrams that a terminal cannot draw, beside the CLI.

  • Binding: each shell gets an opaque VMARK_TRANSCRIPT_TOKEN. An additive SessionStart hook in ~/.claude/settings.json and ~/.codex/hooks.json records that CLI session's transcript under the token, so each terminal follows its own session. Config files are written only when the hook is missing, and invalid config is refused, never replaced. Bindings are deleted when a shell closes or restarts, and all of them when the setting is turned off.
  • Reading: bounded to the last 2 MiB and confined to canonical paths under the CLI session directories. Unchanged files are skipped, and a transcript announced but not yet written reads as waiting.
  • Rendering: Markdown becomes React elements (no raw HTML, links or remote images). Mermaid renders with strict security and SVG labels into a scriptless sandboxed iframe, and the editor's config is restored afterwards.
  • UI: the transcript sits right of the CLI in a top/bottom panel and below it in a side panel. It starts collapsed and opens once for each new reply containing a table or diagram. A pressed-state chart toggle in the tab bar shows or hides it; hidden, it takes no space.
  • Docs: terminal.md and settings.md in all ten locales.

Verification

  • pnpm check:predelta: all 46 gates passed. pnpm check:all: exit 0.
  • WebKit tier: layout geometry, panel placement, strict Mermaid rendering (flowchart and sequence).
  • Rust: transcript tests (binding confinement, tail bounds, config left untouched when already configured, binding cleanup) and the managed-state identity test.
  • Two audit→fix→verify rounds: all findings fixed and independently verified, or rejected with written evidence.

Swapping the terminal between top and bottom (or left and right) killed
every running shell, taking programs like a Claude session with it.

EditorArea rendered the panel in one of two child slots depending on
panelPosition, so a top<->bottom or left<->right swap moved it to the
other slot and React unmounted and remounted TerminalPanel. The panel's
unmount disposes all sessions, which kills their PTYs; the remount then
started fresh shells. Auto mode rarely hit it because bottom and right
share a slot.

The panel now always occupies one slot after the editor column; top and
left reverse the flex axis instead, so no node moves and nothing
remounts. Reordering keyed children instead would have made React move
the editor column, which reloads its iframes and drops focus. The cost
is that for top/left the Tab order reaches the panel after the editor
while it is drawn before; the two are independent landmarks.

A regression test swaps a stateful panel through every position and
asserts one mount; a WebKit test measures that each position still
draws the panel on its side of the editor.
A terminal cannot draw a Markdown table or a Mermaid diagram, so the
replies where Claude Code or Codex answer with one arrive as pipes and
source. With Settings > Terminal > "Automatic transcript rendering" on
(off by default), VMark renders those replies beside the CLI.

Binding: each shell gets an opaque token (VMARK_TRANSCRIPT_TOKEN). A
SessionStart hook installed additively into ~/.claude/settings.json and
~/.codex/hooks.json records that CLI session's transcript path under the
token, so every terminal follows its own session, never "the most
recently modified transcript". Config is written only when the hook is
missing, and invalid user config is refused rather than replaced.
Disabling removes the enabled marker and every binding; closing or
restarting a shell deletes its binding.

Reading: terminal_transcript_read is bounded to the last 2 MiB, confined
to canonical paths under the CLIs' session directories, returns only
when the file changed, and treats a transcript announced but not yet
written as waiting. Parsing keeps complete JSONL lines and the last 100
assistant messages.

Rendering: Markdown becomes React elements (no raw HTML, links or remote
images); Mermaid renders with strict security and SVG labels into a
scriptless sandboxed iframe, then restores the editor's config. The
region sits right of the CLI in a top/bottom panel and below it in a
side panel, starts collapsed, and opens once per new reply that holds a
table or diagram. A pressed-state chart toggle in the tab bar shows or
hides it; hidden, it leaves the layout.

Documented in terminal.md and settings.md in all ten locales.
@xiaolai
xiaolai merged commit 53a04d2 into main Sep 30, 2026
18 checks passed
@xiaolai
xiaolai deleted the feat/terminal-transcript branch September 30, 2026 17:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant