fix(terminal): replay restores the DEC modes the capped buffer evicted (#843) - #1041
Merged
Merged
Conversation
Real OpenCode 1.18.31 startup bytes (recorded under node-pty and sanitized) show the one-time preamble: ?1049h, ?2004h, ?1000h ?1002h ?1003h and ?1006h. Every case runs through a real headless xterm and compares the replayed terminal with one that saw the live stream: - the recording, with the preamble evicted, still replays onto the alternate screen with any-event mouse tracking. This is checked for the buffer on its own and through SessionManager.attachAgentPty at the real 512 KiB cap; - normal-screen output before a retained ?1049h stays on the normal screen, which is the case the reverted "current modes" fix broke; - mouse protocols follow xterm's exclusive state (1000 then 1003, 1003 then 1000, reset, ESC c); - a sequence cut by an eviction boundary still counts; - a session that set no mode replays byte for byte. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#843) A full-screen TUI writes its alternate-screen and mouse modes once at startup, and 512 KiB of 60 fps repaint evicts them within minutes. A remounted xterm was fed only the tail. It sat on the normal buffer with mouse tracking off, so the wheel did nothing, and the pane looked fine because a full-frame repaint renders the same on either buffer. CappedTextBuffer now reports what it evicts. A DecModeTracker, which models xterm's exclusive mouse protocols and encodings, the 47/1047/1049 alternate-screen aliases, ESC c, and cursor keys, cursor visibility, focus and bracketed paste, folds those evicted bytes into the state as of the replay's first byte. TerminalReplayBuffer.replay() prefixes that state, and both attach paths use it. read() stays byte-exact for the paged raw reads. This is the state at the replay START, not the current state: the first fix prefixed current modes and was reverted, because it moved retained normal-screen output onto the alternate buffer (see the 2026-09-08 regression audit). Synchronized output (2026) and terminal queries are deliberately not replayed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
From the #1041 review (APPROVE, low findings, all reproduced there): - ?2031 (theme-change notifications) is tracked. OpenCode sets it once, and without it a remounted pane stopped following app theme switches. - A sequence straddling the eviction boundary is replayed whole: the held fragment goes between the prefix and the tail, instead of xterm printing "49h". - An ESC that cuts a private-mode sequence short now starts the next sequence, as xterm parses it. - ESC c keeps a hidden cursor hidden, matching the shipped xterm. Soft reset (DECSTR) is documented as not modelled. - The shell attach path has a test at the real 256 KiB cap. Reverting attachTerminal to read() had passed every test. - The fixture README states exactly what was edited: the user name. The wrapped temporary path could not be replaced. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Owner
Author
|
Review (APPROVE) findings folded in
Tests: |
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.
Part 1 of #843 (the mouse wheel is dead in an OpenCode terminal pane after it remounts). Part 2, routing Jump to Latest through the TUI server, follows in its own PR.
Why
attachAgentPty/attachTerminalreplay the last 512 KiB of PTY output to a newly mounted xterm.?1049h ?2004h ?1000h ?1002h ?1003h ?1006h.Change
CappedTextBuffergets anonEvicthook. It reports evicted text in stream order, including an oversized chunk's discarded head.DecModeTrackerfolds the evicted bytes into the terminal state as of the replay's first byte. It follows xterm.js semantics, checked against the shipped@xterm/xtermbundle:ESC cresets everything;TerminalReplayBuffer.replay()= the mode prefix + the tail. Both attach paths use it.read()stays byte-exact forsessions.terminalReadpaging.?2026synchronized output (per-frame; a prefix could freeze painting) and terminal queries (they would send stale answers to the program).Why the state at the replay start, not the current state: the first fix prefixed the current modes and was reverted. It moved retained normal-screen output onto the alternate buffer, where the retained
?1049hwiped it (docs/superpowers/research/2026-09-08-post-merge-regression-audit.md).Tests (real xterm headless, real OpenCode bytes)
testing/fixtures/terminal-replay-modes/: the real OpenCode 1.18.31 startup, recorded under node-pty and sanitized. The README gives provenance.terminalReplayModes.test.tscompares the replayed terminal with one fed the live stream:anymouse);?1049h, which fails the reverted "current modes" design (mutation-checked);ESC c;sessionManager.screenGate.test.ts: through the realSessionManagerat the real 512 KiB cap,attachAgentPtygives a remounted xterm the alternate screen andanymouse tracking. This fails withread()in the attach path.src/main/sessionsandsessionManagerpass (196), andnpx tsc -bis clean.🤖 Generated with Claude Code