Repository navigation
fix(terminal): let Ctrl+Shift+R leave a dead program's alternate screen again - #359
Merged
amirlehmam merged 9 commits intoOct 25, 2026
Conversation
… panes that report no shell state (amirlehmam#357) Since amirlehmam#347 the manual reset left the alternate screen only when the shell had reported idle or interrupted, and only wmux's PowerShell integration reports that. In cmd, Git Bash, WSL, SSH, nested shells and PowerShell without the integration, a pane stuck on a dead program's alternate screen could no longer be recovered (amirlehmam#175), and nothing said why. The decision now reads the pane's own output first (utils/alt-screen-owner): an OSC 133;A drawn on the alternate screen means a shell is prompting there, so the reset leaves it even under a stale `running` from an outer shell; a DECSET 47/1047/1049 since then (a TUI started from that prompt) means a program may be drawing, so it keeps, even when PowerShell without PSReadLine reads `idle`. The prompt mark comes from the prompt log's existing OSC 133 parse. The amirlehmam#347 shell-state rule decides only when the stream said neither. A reset that keeps the alternate screen shows a non-modal notice, and a second press within 5 s forces it. The command palette gets "Reset terminal state (leave alternate screen)". Strings in en and fr. Fixes amirlehmam#357
jpstratman
marked this pull request as ready for review
October 24, 2026 21:04
A reset that keeps the alternate screen arms a second press to force it, and Windows starts repeating a held key well inside that 5 s window. Holding Ctrl+Shift+R therefore forced a live program off its screen, which is amirlehmam#347 again. Repeat keydowns of the reset binding are still claimed, so the chord reaches nothing else, but no longer run the reset. No minimum gap between presses: a deliberate quick double press is the "leave it anyway" the notice invites, and the repeat flag already covers the held chord. Refs amirlehmam#357
… changes A reset that kept the alternate screen armed a second press for 5 s, and nothing disarmed it when that screen went away. Resetting in vim, quitting it and reopening it within the window let the next press force the live vim off its screen, and the notice stayed up after the program had exited. Returning to the normal buffer and a new DECSET 47/1047/1049 now both clear the arm and hide the notice: each means the screen the reset was about is no longer the one on display. Refs amirlehmam#357
…t runs A prompt mark on the alternate screen meant "the shell owns it" until the next DECSET, so it outranked a fresh `running` report. On a local PowerShell pane stuck on a dead program's screen, running an inline program from that prompt (Claude Code's inline mode, a dev server) and then resetting left the screen under it, where origin/master kept it. An OSC 133;C seen on the alternate buffer now records that a command is running from that prompt, and the reset keeps the screen until the next 133;A or 133;D. C and D come from the prompt log's existing OSC 133 parse, like A. The PowerShell and bash integrations and Starship emit C; cmd does not, so a cmd pane behaves as before. In ssh, wsl and nested shells the outer shell's C lands on the normal buffer and is ignored, so its stale `running` still cannot block a leave once the inner prompt is drawn. Refs amirlehmam#357
…entry A terminal rebuilt from a remount snapshot replays `?1049h`, which the reset's CSI hook read as a program entering the screen, so a screen the shell owned asked for an extra press. DECSETs are now ignored while the snapshot is being written: a per-surface flag is set before the write and cleared in its callback, and also when the tracker is disposed, since a terminal disposed before the snapshot parsed never runs the callback. The record itself is module state and survives the remount. Refs amirlehmam#357
…eal xterm The `entersAlternateScreen([[1049, 1]])` case asserted a parameter shape xterm never produces: for `?1049:1h` it hands the hook `[1049, [1]]` and switches buffers. Replace it with real-xterm writes of ?47h, ?1047h, ?1049h, ?1049;1006h, ?1006;1049h and ?1049:1h, the private modes that must not count, and RIS (ESC c) clearing the record, the force window and the notice. Refs amirlehmam#357
ResetNotice mounted its role="status" node already holding its text, which screen readers often do not announce. The region now stays mounted and only its text changes; empty, it draws nothing, without display:none, which would take it out of the accessibility tree. The notice also sat at z-index 25, under the prompt outline (27), which is up to half the pane wide and could cover part of it. It is now 28, below the find bar, and on the z-index ladder in prompt-marks.css. It is never hit-testable, so the outline stays usable underneath. Refs amirlehmam#357
# Conflicts: # src/renderer/hooks/useTerminal.ts
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.
Fixes #357. Since #347 the reset leaves the alternate screen only when the shell has reported it's back at its prompt, and only wmux's PowerShell integration reports that, so cmd, Git Bash, WSL, SSH and nested shells lost the #175 recovery. The reset now also reads the pane's own output, and can be forced.
How the reset decides
runningreport, which in SSH, WSL and nested shells belongs to the outer shell.idle.Design choices
handlePromptMarkparse, recorded even when the prompt log is off, so there's no second OSC handler.ResetNotice.tsx). A notification raises an OS toast and a pane ring, which is too much here, and a[wmux]line written into xterm would land inside the running program's frame.role="status 8000 "region mounted and toggles only its text, so screen readers announce it. It sits above the prompt outline and below the find bar.?1049hthat a remount snapshot replays is ignored, so a rebuilt terminal doesn't read as a program entering the alternate screen.Manual verification
In a
wmux-dev.ps1 -Previewinstance: a Git Bash pane stuck afterprintf '\e[?1049h\e[H'stays on the alternate screen after one press with the notice shown, and returns to the normal screen with its scrollback on the second. Holding the chord resets once and keeps the screen. A cmd pane leaves on one press. The #347 script under PowerShell keeps its screen while it runs, the notice disappears when it exits, and the prompt doesn't overprint. AStart-Sleep 10run from a stuck PowerShell prompt keeps the screen while it runs, and one press leaves after it finishes.