fix(tui): raster figure through the renderer's own output channel [spec 10] - #115
Merged
Conversation
…ec 10] The whisper-figure raster (PR #114) wrote kitty-graphics escapes straight to process.stdout — which OpenTUI intercepts in capture-stdout mode, so the payload was re-rendered as text (the on-screen base64 garbage) and panicked bun's native layer within seconds (SIGTRAP), stranding the terminal in raw mode with dead input. Reproduced A/B in a 200x50 pty: image mode crashed every run, sprite mode never did. - app.tsx: every figure escape now goes through renderer.writeOut — the one channel serialized with the render thread. 25s acceptance run in the same pty harness: alive, 70 APC sequences on the wire, no crash report. - figure-image.ts: the bun:ffi ioctl (which returned all-zero winsize and fed the path fallback guesses) is gone; cell pixel size now derives from the renderer's own resolution report (cellSizeFrom, unit-tested). - ipc-host.ts: when the front-end exits abnormally, the engine writes the terminal restore itself (leave alt screen, show cursor, raw mode off) — a dead face can no longer freeze the terminal. Unit-tested via an injectable tty; verified live by SIGKILLing the client mid-run. Peer review (codex gpt-5.5 xhigh): 0 findings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
wine-fall
added a commit
that referenced
this pull request
Aug 10, 2026
…[spec 10] (#116) Two by-eye reports from the first real Ghostty session after #115: - The spectrum was pinned bin 0 -> left arm, so the ever-breathing bass made the wave read as marching left-to-right. waveBinAt now mirrors the bins from the arc's middle outward — bass at the center, treble fraying toward both arms (unit-tested for symmetry and for a bass-only frame lighting the center alone). - The raster figure blanked for over half a second at the first spoken line: the placement effect depended on the pose, so every state change ran deleteFigures and waited out the 600ms resettle. The pose now rides a ref and swaps the next transmitted frame in place under the same image id; only a real relayout tears the placement down. Verified in the pty harness: 73 in-place retransmissions across a run spanning the greeting->talk transition, zero deletes before shutdown. Peer review (codex gpt-5.5 xhigh): 0 findings. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.
Implements specs/spec10/10-tui.md (§3.7 raster figure, §8 opentui#92 note).
What broke
PR #114's raster whisper-figure wrote kitty-graphics escapes straight to
process.stdout. OpenTUI interceptsprocess.stdout.writein itscapture-stdout mode, so the payload was fed back into the renderer as text
(the on-screen base64 garbage) and panicked bun's native layer within
seconds of entering the sky layout — SIGTRAP, front-end dead with OpenTUI's
restore skipped, terminal stranded in raw mode with dead input and a dead
Ctrl-C. Reproduced A/B in a 200x50 pty harness with a ghostty env: image
mode crashed every run (
bun-*.ipscrash reports), sprite mode never did.The fix
tui/src/app.tsx— every figure escape now goes through therenderer's
writeOut, the one channel serialized with the render thread(the same channel OpenTUI's own escape emissions use). Typed private
upstream pending a public graphics API (opentui#92); reached via one
documented cast.
tui/src/figure-image.ts— thebun:ffiioctl is deleted (itreturned an all-zero winsize, so the path always ran on fallback
guesses); cell pixel size now derives from the renderer's own
resolutionreport viacellSizeFrom(unit-tested).src/ipc-host.ts— engine-side backstop: when the front-end exitsabnormally, the engine writes the terminal restore itself (leave the
alternate screen, show the cursor, raw mode off), so a crashed face can
never freeze the terminal again. Unit-tested via an injectable tty.
specs/spec10/10-tui.md— the opentui#92 note now names thewriteOutrequirement so the direct-stdout mistake cannot come back.Acceptance
25-second run in the same pty harness that reproduced the crash: TUI alive
(old crash window was under 5s), 70 kitty APC sequences on the wire through
the new channel, no new crash report; then a deliberate SIGKILL of the
client showed the engine's restore bytes arriving on the pty.
Peer review (codex gpt-5.5 xhigh): 0 findings.
AI coding brief
with dead input after the latest PR; diagnose from the latest PR and the
running instance's logs, then root-cause it rather than disabling the
feature.
(default the figure back to sprite) toward solving the actual
writer-vs-renderer conflict, and clarified the symptom was an
unresponsive TUI, not a visible crash.
writeOutand the stdout interception in minutes; when a library ownsthe terminal, look for its sanctioned output channel before reaching for
process.stdoutor FFI.🤖 Generated with Claude Code