fix(terminal): a remote shell load keeps the remote scrollback (#601/#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>
This commit is contained in:
Codeman maintainer committed 2026-10-13 18:02:36 +02:00
1 parent d571c41fb1
commit f78b820302
2 files changed
+32 -2

No files matched your search

+10 -2
View File
@@ -3153,8 +3153,16 @@ export function registerSessionRoutes(
// `alt=recording`) gets the old visible-frame load over it, tailed, rather
// than losing everything above the screen. `alternate_on` cannot tell a
// nested tmux from vim or less, so those take this path too while open:
// the load the shell had before tmux-rendered loads existed.
if (isFullReload && query.alt === 'recording' && captureOpts.capturedAlternateOn === true) {
// the load the shell had before tmux-rendered loads existed. Not when the
// history came from the remote tmux (`scrollbackOverride`): a remote pane is
// in the alternate screen only because it hosts that tmux's client, and
// the fallback would throw the remote scrollback away.
if (
isFullReload &&
query.alt === 'recording' &&
captureOpts.scrollbackOverride === undefined &&
captureOpts.capturedAlternateOn === true
) {
isFullReload = false;
captureOpts = {};
liveMuxBuffer = capture(captureOpts);
+22
View File
@@ -1349,6 +1349,28 @@ describe('session-routes', () => {
}
});
it("keeps a remote session's spliced remote scrollback, and falls back only when that read failed", async () => {
// The remote session's local pane hosts the remote tmux client, so it is
// always in the alternate screen; the history comes from the remote tmux.
const spy = install(true);
const session = harness.ctx._session as unknown as { remote?: unknown };
session.remote = { hostId: 'h1', label: 'proxx', host: 'proxx.lan', username: 'tim', remotePath: '/home/tim' };
try {
remoteScrollback.mockResolvedValueOnce('remote line 1\n');
const spliced = await load(`full=1&tail=${1024 * 1024}&alt=recording&lines=10000`);
expect(spy).toHaveBeenCalledTimes(1);
expect(spliced.source).toBe('mux-full-history');
spy.mockClear();
remoteScrollback.mockResolvedValueOnce(null);
const fallback = await load(`full=1&tail=${1024 * 1024}&alt=recording&lines=10000`);
expect(spy).toHaveBeenCalledTimes(2);
expect(fallback.source).toBe('mux-visible');
} finally {
session.remote = undefined;
}
});
it('keeps the full capture without the opt-in (agents, Load full history, scroll pulls)', async () => {
const spy = install(true);
const data = await load(`full=1&tail=${1024 * 1024}&lines=10000`);