Skip to content

fix(web): clearing a running terminal keeps its screen and input modes - #36380

Merged
juliusmarminge merged 4 commits into
mainfrom
factory/issue-36006-a1794597635
Nov 13, 2026
Merged

juliusmarminge merged 4 commits into
mainfrom
factory/issue-36006-a1794597635

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Nov 13, 2026 •

Copy link
Copy Markdown
Member

No code change in this round; the head is unchanged at 95ae7c8. Watched pull request #36380 until its checks and bot reviews finished: all 36 checks passed on 95ae7c8 (mergeable state clean), Macroscope approved with no findings (Correctness Check and Approvability Check both success), and CodeRabbit reported 'No actionable comments were generated' with all four of its pre-merge checks passed and no line comments. No finding rated high or above is open, so the watch was stopped.

CodeRabbit's description check, though passed, noted two things about the pull request description that are for the pipeline, not the code: the Evidence section is still a placeholder, and the 'Not in this change' bullets contain a duplicate and a contradictory line about whether the 9-byte overage after Clear is bounded (it is: at most the 9-byte erase sequence over budget until the next output, snapshot or restart). The description also says only clearOutput changed, while the diff also adds textStartOffset, terminalOutputReplay and the mobile call-site changes.

The fix as it stands: a cleared event appends an erase of screen and scrollback (ESC[H ESC[2J ESC[3J) to the client's retained output instead of resetting it, so attached emulators keep the running program's alternate screen, Kitty flags, cursor-key mode and mouse tracking; displayed text for the mobile text fallback starts after the clear; older native binaries get the full replay as initialBuffer; and Clear never trims retained output.

Fixes #36006

Reproduction on main

On origin/main 9efd960: started T3CODE_DEV_SEED=0 vp run dev in a fresh worktree, paired a headless Chromium (Playwright) web client, and opened the terminal drawer ('Toggle terminal drawer') on a new thread. In the terminal I ran a probe script that prints ESC[?1049h ESC[>7u ESC[?1;1000;1006h ESC= (alternate screen, Kitty flags 7, application cursor keys, SGR mouse), then stty raw -echo; exec cat > log, so every byte the viewer sends is logged. Clicked in the terminal and pressed ArrowUp and 'a': the program received ESC[<0;5;3M/ESC[<0;5;3m, ESC[1;1:1A/ESC[1;1:3A, and a plus ESC[97;1:3u. Then sent terminal.clear (the mobile Clear toolbar's RPC) for that thread/term-1 over the app's own /ws URL from the page; the server replied Exit Success. Repeated the click, ArrowUp and 'a' with the same program still running: the click produced nothing, ArrowUp sent ESC[A, and 'a' sent a with no release event. Also added a unit test in apps/web/src/terminal/ghostty/core.test.ts that applies a cleared event through applyTerminalAttachStreamEvent, readTerminalOutputUpdate and writeTerminalOutputUpdate on the real Ghostty WASM core; on main it fails, with alternateScreen, applicationCursorKeys and mouseTracking going from true to false and the Kitty flags from ESC[?7u to ESC[?0u.

Evidence

Evidence for #36006: real Chromium web client, 1280×800, light theme, fresh synthetic data on each commit. Enable the reported terminal modes once; click, press ArrowUp and a; invoke terminal.clear; repeat with the same program still running. The probe displays actual received bytes. Both sides work before Clear; only base loses mouse and Kitty events afterward. scenarios:prove exited 0.

Capture Before fix After fix
Commit 9efd960925 95ae7c8316
Before Clear Base before Clear Head before Clear
After Clear Base: mouse and Kitty events lost Head: mouse and Kitty events preserved
Interaction Before recording After recording

Scenario source and probe.

Not shown:

  • Native iOS/Android rendering and viewer reattachment with a full retained buffer were not exercised; this proof covers the reported attached web viewer.

Tests

None run locally this round (no code change). On the pull request at 95ae7c8: all 36 CI checks passed, including Test, Test Web 1 and 2, Test Server 1-8, Lint, and Build and Typecheck; Macroscope Correctness Check and Approvability Check succeeded; CodeRabbit completed with no actionable comments.

Not in this change

  • TerminalManager.clear on the server still wipes its retained history, so a viewer that attaches or resubscribes (socket reconnect) after a Clear gets an empty snapshot and starts without the running program's modes; fixing that means changing server history/persistence semantics and is a separate change.
  • Clear no longer resets the emulator's SGR pen or other non-text state (scroll region, cursor style); that matches the still-running program's own state and was left as is.
  • Clear no longer frees the client's retained pre-clear output; it stays bounded by the existing max buffer bytes and is dropped on the next snapshot or restart.
  • Older mobile native binaries that take initialBuffer from the materialized text still behave as on main (buffer becomes empty on Clear).
  • TerminalManager.clear on the server still wipes its retained history, so a viewer that attaches or resubscribes after a Clear starts from an empty snapshot without the running program's modes.
  • On older native binaries a history rollover (the buffer head trimmed at the retention limit) still stops the new buffer extending the old one and recreates the emulator; that is unchanged from main and unrelated to Clear.
  • In the text fallback both the replay string and the displayed text are now materialized per update; the fallback is the rare no-native-view path and both are bounded by the max buffer bytes.
  • Clear does not reset the emulator's SGR pen or other non-text state, and does not free the client's retained pre-clear output until the next snapshot or restart.
  • On older native binaries with a full buffer, the first ordinary output after Clear (like any output on a full buffer, as on main) trims the head, so the buffer stops extending the previous one and the emulator is recreated; ordinary rollover is unchanged by this fix.
  • After Clear the client buffer can exceed its byte budget by the 9-byte erase sequence until the next output, snapshot or restart.
  • Clear does not reset the emulator's SGR pen or other non-text state, and does not free retained pre-clear output until it is trimmed or replaced.
  • The pull request description needs tidying (placeholder Evidence section, duplicated and contradictory 'Not in this change' bullets, and it understates what the diff changes); I do not write to the forge.
  • TerminalManager.clear on the server still wipes its retained history, so a viewer that attaches or reconnects after a Clear starts from an empty snapshot without the running program's modes.
  • On older native binaries with a full buffer, ordinary output rollover still recreates the emulator, as on main.

Who did the work

  • Written by Claude Fable 5.2, in Claude Code
  • Reviewed by GPT-6.1 Sol, in Codex, and by Claude Opus 5.5, each on its own
  • Proof captured by GPT-6.1 Sol and checked by Claude Opus 5.5

Run by the t3code factory in T3 Code.

juliusmarminge and others added 4 commits November 13, 2026 19:39
Fails on main: a cleared event fully resets the Ghostty core, dropping
the alternate screen, application cursor keys, mouse tracking and Kitty
keyboard flags the live program negotiated.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
A cleared event became an empty reset update, which fully resets the
emulator and drops the alternate screen, Kitty keyboard flags, cursor-key
mode and mouse tracking the still-running program negotiated.

Clear now appends an erase of the screen and scrollback instead, so
attached emulators keep their modes and a replay from the start restores
them. The retained output records where displayed text begins, so the
mobile text fallback still shows an empty buffer after Clear.

Fixes #36006

Co-Authored-By: Claude Fable 5.2 <[email protected]>
…rminal binaries

Older native binaries are fed the whole history as initialBuffer and
recreate their emulator when it stops extending the last one. They now
get the full replay, so Clear appends its erase. The text fallback takes
the displayed text from the output state instead.

Co-Authored-By: Claude Fable 5.2 <[email protected]>
The erase was appended within the retention budget, so on a full buffer
it trimmed the oldest output. A replay then no longer extended what older
native binaries were fed, and could lose the program's mode setup.

Co-Authored-By: Claude Fable 5.2 <[email protected]>
@github-actions github-actions Bot added vouch:trusted Author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Nov 13, 2026
@macroscopeapp

macroscopeapp Bot commented Nov 13, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at 95ae7c8

Macroscope's review found this PR approvable — Clearing a terminal now erases its visible content without resetting the running program’s screen and input modes. The change is limited to client-side display and replay handling, with separate handling for mobile text fallbacks.

You can add or adjust custom eligibility rules. Learn more.

@coderabbitai

coderabbitai Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

Terminal clear now appends an erase sequence while retaining terminal replay history. Displayed text excludes content before the clear. Mobile terminal buffers use displayed text or retained replay data based on their purpose.

Changes: Terminal clear and replay

Layer / File(s) Summary
Clear semantics and terminal updates
packages/client-runtime/src/state/terminalOutput.ts, packages/client-runtime/src/state/terminalSession.ts, packages/client-runtime/src/state/terminalSession.test.ts, apps/web/src/components/ThreadTerminalDrawer.test.ts, apps/web/src/terminal/ghostty/core.test.ts
Output state tracks a display offset separately from retained replay data. Clear events append an erase sequence instead of resetting output. Tests check visible output, replay updates, and preservation of screen and input modes.
Mobile display and replay buffers
apps/mobile/src/features/terminal/NativeTerminalSurface.tsx, apps/mobile/src/state/use-terminal-session.ts, apps/mobile/src/features/terminal/NativeTerminalSurface.test.tsx
The native view receives displayed text. Buffers materialized for older binaries use retained replay data, including prior output and clear sequences.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant TerminalSession
  participant TerminalOutputState
  participant Ghostty
  TerminalSession->>TerminalOutputState: Apply clear event
  TerminalOutputState->>Ghostty: Append erase sequence
  Note over Ghostty: Preserve running screen and input modes
Loading

Suggested reviewers: t3dotgg

Merge Risk: Minimal · up to 95ae7

Clearing a running terminal now preserves its screen and input modes instead of resetting the emulator, which resolves the reported web and desktop behavior. Older native mobile binaries still receive the retained replay plus the erase sequence. The remaining uncertainty about how those binaries treat later output trimming is narrow and unconfirmed. Merge-readiness risk is minimal.

Pre-merge checks: Passed 4

Check name Status Explanation
Title check Passed The title follows the conventional commit format and names the main change: clearing a running terminal now keeps its screen and input modes. The changeset matches this: clearOutput now appends the er…
Linked Issues check Passed [#36006] Clear no longer does a full emulator reset on the web/desktop path. In packages/client-runtime/src/state/terminalSession.ts, the "cleared" attach event now calls clearOutput instead of resetO…
Out of Scope Changes check Passed All changed areas connect to the Clear behavior in #36006. terminalOutput.ts: clearOutput, textStartOffset, and terminalOutputReplay/terminalOutputText split are needed so Clear keeps the replay and c…
Description check Passed The description follows the repository template. It has Problem, Change, Scope, Verification, and agent attribution sections. The problem, the fix, and the linked issue (#36006) are clear. The Verific…
Full details: Title check

Explanation

The title follows the conventional commit format and names the main change: clearing a running terminal now keeps its screen and input modes. The changeset matches this: clearOutput now appends the erase sequence without evicting retained output, and the Ghostty core test checks that alternate-screen, application-cursor-key, mouse-tracking, and Kitty keyboard modes survive a clear. The title is 70 characters, slightly above a short ideal but still concise and specific.

Full details: Linked Issues check

Explanation

[#36006] Clear no longer does a full emulator reset on the web/desktop path. In packages/client-runtime/src/state/terminalSession.ts, the "cleared" attach event now calls clearOutput instead of resetOutput(output, ""). clearOutput appends CLEAR_TERMINAL_TEXT (ESC[H ESC[2J ESC[3J) in packages/client-runtime/src/state/terminalOutput.ts. This erases the screen and scrollback but does not change alternate screen, application cursor keys, mouse tracking, or Kitty flags, so the running program's negotiated modes survive. The retained text is cleared: textStartOffset advances to nextOffset, and terminalOutputText excludes pre-clear text. Earlier output stays in the replay, so a replay from the start keeps the mode-setting sequences. The clearOutput budget is max(maxBufferBytes, retainedBytes + erase length), so Clear does not evict retained output. Ghostty core test (apps/web/src/terminal/ghostty/core.test.ts) and ThreadTerminalDrawer test cover the mode-preservation behavior. Mobile legacy-binary test covers initialBuffer after Clear on a full buffer. Remaining gap, stated by the PR: a viewer that reattaches or reconnects after Clear gets an empty server snapshot and loses the modes. This occurs on reconnect, not across Clear for an attached viewer, so it is not a failing requirement here.

Full details: Out of Scope Changes check

Explanation

All changed areas connect to the Clear behavior in #36006. terminalOutput.ts: clearOutput, textStartOffset, and terminalOutputReplay/terminalOutputText split are needed so Clear keeps the replay and clears displayed text. terminalSession.ts: switches the cleared event to clearOutput. Mobile files (NativeTerminalSurface.tsx, use-terminal-session.ts) use terminalOutputReplay for initialBuffer and terminalOutputText for the text fallback, which keeps older native binaries consistent after Clear. Tests (terminalSession.test.ts, core.test.ts, ThreadTerminalDrawer.test.ts, NativeTerminalSurface.test.tsx) verify these paths. The PR does not change server TerminalManager.clear, ordinary rollover, or non-text emulator state, as stated. Note: the PR description says only clearOutput changed, but the diff also adds textStartOffset, terminalOutputReplay, and the mobile call-site changes; these are supporting changes for the same Clear behavior and do not make the PR out of scope.

Full details: Description check

Explanation

The description follows the repository template. It has Problem, Change, Scope, Verification, and agent attribution sections. The problem, the fix, and the linked issue (#36006) are clear. The Verification section gives a detailed reproduction on main and a passing CI run. Several gaps remain. The Evidence section still contains a placeholder-style structure that needs confirmation. The 'Not in this change' section repeats several bullets, and two bullets conflict on whether the 9-byte overage after Clear is bounded. The Change section also understates the diff. It omits the textStartOffset addition, the terminalOutputReplay addition, and the mobile call-site changes. Because the core content is complete, the check passes.

Finishing Touches

Generate unit tests (beta)

  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Recent review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between eb81855 and 95ae7c8.

Files selected for processing: 8

  • apps/mobile/src/features/terminal/NativeTerminalSurface.test.tsx
  • apps/mobile/src/features/terminal/NativeTerminalSurface.tsx
  • apps/mobile/src/state/use-terminal-session.ts
  • apps/web/src/components/ThreadTerminalDrawer.test.ts
  • apps/web/src/terminal/ghostty/core.test.ts
  • packages/client-runtime/src/state/terminalOutput.ts
  • packages/client-runtime/src/state/terminalSession.test.ts
  • packages/client-runtime/src/state/terminalSession.ts

Limit details: You’ve used all 10 included reviews currently available.

@juliusmarminge
juliusmarminge merged commit 1721f18 into main Nov 13, 2026
37 checks passed
@juliusmarminge
juliusmarminge deleted the factory/issue-36006-a1794597635 branch November 13, 2026 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:trusted Author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Clearing terminal history resets the live program's screen and input modes

1 participant