mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-09 16:59:43 +02:00
fix(terminal): restore shell scrollback, recover history lost to tmux repaints
Four fixes for the scrollback reports in #205 (plus its follow-up comment). 1. tmux-backed shell/opencode/antigravity sessions were parked in xterm's ALTERNATE buffer for their whole life. The tmux CLIENT emits smcup (\x1b[?1049h) as its first bytes on attach, and the existing strip is gated to claude/codex/gemini, so it reached the browser verbatim. In the alternate buffer baseY is pinned at 0 (no scrollback, so touch scrolling is a no-op) and xterm's own wheel handler translates the wheel into \x1bOA cursor keys, which readline receives as shell history navigation. Both reported symptoms, one sequence. isMuxAltScreenOnlyStripMode() now strips that toggle for those modes, but ONLY under tmux (the direct-PTY fallback still needs a program's own alt screen) and ONLY the alt-screen toggle: 3J from a user's `clear` and the mouse DECSETs a pane's htop/vim rely on are left alone. Safe because tmux never forwards a pane's alt-screen toggles to its client, it repaints; captured from a real attach, vim/less/htop emit zero. 2. "Load more history" on scroll-to-top. xterm's buffer is only ever a window onto tmux's history, and tmux repaints the pane rectangle instead of emitting linefeeds whenever output outpaces its flush, OVERWRITING already-rendered scrollback. Measured: a 60-line burst added 1 row and destroyed 34, while the same 60 lines emitted slowly added all 60. Scrolling up at the top now re-pulls the full tmux scrollback and holds the user's place. Verified end to end: 42 rendered rows -> 213, recovering all 150+60 printed lines. 3. The full-scrollback replay was gated on a single "first load after page load" flag, which whichever session auto-selected consumed, so every other tab started with one visible frame. Now tracked per session. 4. _wheelScrollLines ignored ev.deltaMode, so Firefox (DOM_DELTA_LINE, deltaY 3 per notch) scrolled one line where Chrome scrolls four or five, and capped the forwarded SGR report at one tick. Line and page deltas are now converted, and a pure horizontal swipe no longer falls through to a phantom -1. Analysis and measurements: docs/scrollback-issues-analysis.md
This commit is contained in:
@@ -429,6 +429,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
}
|
||||
this._noteTerminalUserScroll(lines);
|
||||
this.terminal.scrollLines(lines);
|
||||
this._maybeLoadMoreHistoryOnScroll(lines);
|
||||
},
|
||||
{ passive: false }
|
||||
);
|
||||
@@ -453,7 +454,10 @@ Object.assign(CodemanApp.prototype, {
|
||||
if (!isTouching && Math.abs(velocity) > 0.3) {
|
||||
// Momentum phase — convert pixel velocity to lines
|
||||
const lines = Math.round(velocity / cellHeight());
|
||||
if (lines !== 0) this.terminal.scrollLines(lines);
|
||||
if (lines !== 0) {
|
||||
this.terminal.scrollLines(lines);
|
||||
this._maybeLoadMoreHistoryOnScroll(lines);
|
||||
}
|
||||
velocity *= 0.92;
|
||||
scrollFrame = requestAnimationFrame(scrollLoop);
|
||||
} else if (!isTouching) {
|
||||
@@ -516,6 +520,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
if (lines !== 0) {
|
||||
this._noteTerminalUserScroll(lines);
|
||||
this.terminal.scrollLines(lines);
|
||||
this._maybeLoadMoreHistoryOnScroll(lines);
|
||||
pixelAccum -= lines * ch;
|
||||
}
|
||||
}
|
||||
@@ -2009,6 +2014,20 @@ Object.assign(CodemanApp.prototype, {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Post-scroll companion to _noteTerminalUserScroll: hitting the TOP of the
|
||||
* buffer while scrolling up is the user reaching for history the browser does
|
||||
* not have, so pull the rest of tmux's scrollback (issue #205, see
|
||||
* _maybeRefetchFullHistory). Must be called AFTER scrollLines(), since the
|
||||
* check is on the resulting position, and it is deliberately not folded into
|
||||
* _noteTerminalUserScroll for exactly that reason. Cheap: one integer compare
|
||||
* per scroll event, and the pull itself is cooldown-guarded.
|
||||
*/
|
||||
_maybeLoadMoreHistoryOnScroll(lines) {
|
||||
if (lines >= 0) return;
|
||||
if (this.terminal?.buffer?.active?.viewportY === 0) this._maybeRefetchFullHistory?.();
|
||||
},
|
||||
|
||||
_hasRecentUserScrollUp() {
|
||||
if (typeof this._lastUserScrollUpAt !== 'number') return false;
|
||||
return performance.now() - this._lastUserScrollUpAt < window.CodemanTerminalInput.USER_SCROLL_STICKY_SUPPRESS_MS;
|
||||
@@ -2815,9 +2834,22 @@ Object.assign(CodemanApp.prototype, {
|
||||
// deltaY≈0 collapses to a fixed ±1 line/tick and the gesture can't page through
|
||||
// history on a trackpad (issue #154). Non-Shift and mouse-wheel paths are
|
||||
// unchanged (they carry deltaY). The `|| ±1` keeps sub-25px deltas moving.
|
||||
//
|
||||
// `deltaMode` says what UNIT the delta is in, and ignoring it made every
|
||||
// non-pixel browser scroll ~4x too slowly: Firefox reports DOM_DELTA_LINE (1)
|
||||
// with deltaY≈3 per notch, so the pixel math rounded to 0 and fell through to
|
||||
// the ±1 fallback — one line per notch, versus 4-5 for Chrome's ~110px. In
|
||||
// Claude mode the same value also capped the forwarded SGR report at one tick.
|
||||
_wheelScrollLines(ev) {
|
||||
const delta = ev.shiftKey && Math.abs(ev.deltaX) > Math.abs(ev.deltaY) ? ev.deltaX : ev.deltaY;
|
||||
return Math.round(delta / 25) || (delta > 0 ? 1 : -1);
|
||||
if (!delta) return 0; // pure horizontal swipe: don't fall through to -1
|
||||
const lines =
|
||||
ev.deltaMode === 1 // DOM_DELTA_LINE (Firefox mouse wheel)
|
||||
? delta
|
||||
: ev.deltaMode === 2 // DOM_DELTA_PAGE
|
||||
? delta * (this.terminal?.rows || 24)
|
||||
: delta / 25; // DOM_DELTA_PIXEL (Chrome/WebKit, and every trackpad)
|
||||
return Math.round(lines) || (delta > 0 ? 1 : -1);
|
||||
},
|
||||
|
||||
_shouldForwardWheelToApp(ev) {
|
||||
|
||||
Reference in New Issue
Block a user