Repository navigation
fix(terminal): load a shell from tmux's rendered history, not the byte recording - #601
Conversation
…e recording
A shell's automatic loads (tab select, dropped-output recovery, clear
terminal, a split/grid tile's load) replayed `session.terminalBuffer`,
the raw PTY recording, ahead of the visible frame. That recording holds
full-screen repaints (an ssh'd tmux, less, any TUI: cursor home, then the
rows joined by newlines, prompt on the last row) drawn for the pane
height of the moment. Replayed into a terminal with fewer rows, each
repaint's last newline scrolls and pushes its top row into scrollback,
so one line tmux holds once shows up dozens of times (26 copies in the
case that found this, after the pane went from 24 to 23 rows). Remote
SSH shell sessions hit it constantly: their inner tmux repaints often.
Shells now load a bounded window of tmux's rendered history instead
(`SHELL_LOAD_QUERY`: full=1&tail=1MiB&lines=10000), the same source the
scroll-to-top pull already used. `lines=10000` bounds the synchronous
capture-pane, measured at ~8 ms per 1000 lines (~0.1 s here vs ~0.8 s
for a 100k-line history). Because that bound is shallower than xterm's
scrollback, the server now reports a cut: the capture's existing
display-message query also reads `#{history_size}`
(`PaneCaptureOptions.capturedHistorySize`), and the route marks a full
capture `truncated` / 'tail' when tmux holds more than it read, so the
banner keeps offering Load full history and scroll-to-top keeps pulling
older lines. The byte cap still reports 'capped'. The banner no longer
names an amount it cannot know ("less than 1 KB more") for such a cut.
Agent sessions are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks a lot for this, @timkjr, and welcome to Codeman. The PR moves a shell's automatic loads (tab select, dropped-output recovery, clear, a tile's load) off the raw PTY byte recording and onto a bounded window of tmux's rendered history, and has the server report when that line bound left older lines behind. The diagnosis of the repaint duplication is spot on, the measurements hold up (I got about 98 ms for a 10,000-line capture and 463 ms for 60,000 lines on tmux 3.4), and the full suite plus the static checks are green. A few things need changing before it can go in. 1. Nested-tmux shells lose their scrollback on every automatic load ( Remote SSH cases and Docker cases run the shell inside a second tmux ( The route returns a full capture on its own ( Ask: add 2. An unbounded full capture is reported as a
3. Two docs still describe the old shell load ( Both say shell selection and drop recovery load "a bounded 1 MiB Smaller things (fine to fold in, or I can do them at merge time):
Once 1 to 3 are addressed I will take another look and merge. Thanks again for digging into this one, the write-up made the bug easy to follow. |
…ate screen, report a lines cut only below the ceiling
Review follow-ups for loading a shell from tmux's rendered history.
tmux adds no history while a pane is in the alternate screen, and a
nested tmux client (remote SSH and Docker cases, or tmux run by hand)
holds its pane there, so a full capture of it is one screen. The
display-message query that reads #{history_size} now also reads
#{alternate_on} (PaneCaptureOptions.capturedAlternateOn), and a request
carrying `alt=recording` (only SHELL_LOAD_QUERY sends it) is answered
the old way on such a pane: the visible frame over the tailed byte
recording. Agents, scroll pulls and Load full history are unchanged.
alternate_on cannot tell a nested tmux from vim or less, so a local
shell with one of those open takes the same fallback while it is open.
A full capture without `lines=` was reported as a 'tail' cut whenever
the pane held more than the configured ceiling (possible on tmux before
3.7), offering a Load full history that could never return more. The
cut is now reported only for a client bound below the ceiling.
The response carries `historyLinesCut` when that bound left lines in
tmux, and the banner then says earlier output may be retained instead
of naming the bytes a later tail cut, which undercount what tmux holds.
A bounded grid tile's shell load sends its own `lines=` (via
SHELL_LOAD_BASE_QUERY). CLAUDE.md, docs/architecture-invariants.md and
docs/api-reference.md describe the shell load, the cut report and the
fallback.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for the review. Everything is in 063e6fc, on top of the original commit.
Both smaller ones are in too: a bounded tile's shell load sends its own On #602: with both in, a remote shell's local pane is in the alternate screen too (it's attached to the remote tmux). As written, this fallback would discard #602's remote splice on every remote shell tab switch. The adjustment is to skip the fallback when the remote scrollback override was used, and to read the cut from the remote Typecheck, lint, format, frontend syntax, browser-excludes and the full |
…-visible (#601 merge-time fix) The geometry-retry comment in selectSession and the #435 invariant said the route never answers full=1 with mux-visible; a shell load's alt=recording on an alternate-screen pane now is. The conclusion holds (a useFullHistory pass never sends it), so only the stated reason changes. Also lists SHELL_LOAD_BASE_QUERY in terminal-tile.js's @dependency header. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ack from the remote tmux A full-history load of a remote SSH session reads the scrollback from the remote tmux in one bounded ssh round trip and splices it above the local frame. A transport failure (ssh 255, timeout, unparseable output) starts a 30 s per-host back-off; a missing remote session is per-session only. Conflict resolved in session-routes.ts: #602's remote scrollback read runs before #601's capture helper and its alternate-screen fallback. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…602 integration) A remote session's local pane hosts the remote tmux client, so it is always in the alternate screen. #601's alt=recording fallback therefore threw away the scrollback #602 had just spliced in from the remote tmux on every remote shell load. The fallback now applies only when no remote scrollback was read (the read failed, or the session is not remote). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…in a remote history load Conflict resolved in tmux-manager.ts: #609's screen-aware joinScrollbackAndFrame keeps #602's merge-time SGR reset before the frame, and #609's join tests expect it. The #601/#602 route test now mocks the remote read in #609's { scrollback, screen } shape. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…t with PageUp/PageDown from the first wheel event Conflict resolved in terminal-tile.js's @dependency header: #601's SHELL_LOAD_BASE_QUERY/SHELL_LOAD_QUERY and #527's pageKeysForGesture and app.tabAlerts are all listed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… open .md from the terminal in the rendered viewer Conflicts resolved against #604, #606 and #601: - constants.js: the rooted link pattern takes html-md's `markdown` and `html?` with #606's m4v/ogv; #604's relative pattern gets the same list (link-provider-regex pins the two equal); #604's findFilePathLinks / folder helpers and html-md's rendersAsPage() are both kept, and window.CodemanFilePaths exports all of them. - terminal-ui.js: #604's resolved `target` (relative, ~/ and folder links) now also goes through rendersAsPage(), so every link kind routes .html to the page preview; .md routes there through FILE_PREVIEW_EXTENSIONS. - CLAUDE.md, api-reference, architecture-invariants: both sides' text kept. - test/folder-grid-ui.test.ts (#604) stubs rendersAsPage() like the other panels-ui harnesses. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A shell's automatic loads (tab select, dropped-output recovery, clear terminal, a split/grid tile's load) replayed
session.terminalBuffer, the raw PTY recording. That recording holds full-screen repaints (an ssh'd tmux,less, any TUI) drawn for the pane height of the moment. Replayed into a terminal with fewer rows, each repaint's last newline pushes its top row into scrollback, so one line tmux holds once shows up dozens of times (26 copies in the case that found this, after the pane went from 24 to 23 rows). Remote SSH shells hit it constantly, since their inner tmux repaints often.SHELL_LOAD_QUERY:full=1&tail=1MiB&lines=10000), the same source the scroll-to-top pull from fix(terminal): let a Shell pane's scroll-up reach tmux history #494 already uses. Agent sessions are unchanged.lines=10000bounds the synchronouscapture-pane: measured at ~8 ms per 1000 lines, so ~0.1 s here vs ~0.8 s for a 100k-line history.display-messagequery also reads#{history_size}(PaneCaptureOptions.capturedHistorySize), and the route marks a full capturetruncated/'tail'when tmux holds more than it read. The banner keeps offering Load full history and scroll-to-top keeps pulling older lines. The byte cap still reports'capped', and keeps precedence.Testing: route, capture, banner, flush-budget and tile tests updated/added; typecheck, lint, frontend syntax, format check and the full suite green on this branch. Running on my own deployment.