mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-05 15:09:42 +02:00
fix: terminal flicker regression — move viewport clear inside dimension guard
Three fixes from the WIP flicker branch that were lost during master merges: 1. Move viewport clear (\x1b[3J\x1b[H\x1b[2J) inside the dimension-change guard so it only fires when cols/rows actually change. Previously every resize event cleared the screen even at identical dimensions, causing visible flicker with no subsequent Ink redraw to repaint. 2. Sync _lastResizeDims in sendResize() so restoreTerminalSize() doesn't trigger a redundant viewport clear on the next throttledResize tick. 3. Add didScroll tracking to touch events — tap (no scroll) now refocuses xterm's hidden textarea, fixing mobile keyboard input routing after tapping the terminal area.
This commit is contained in:
@@ -179,6 +179,7 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
// Accumulate sub-line pixel deltas so slow swipes still scroll
|
// Accumulate sub-line pixel deltas so slow swipes still scroll
|
||||||
let pixelAccum = 0;
|
let pixelAccum = 0;
|
||||||
|
|
||||||
|
let didScroll = false; // track whether touchmove fired (tap vs scroll)
|
||||||
container.addEventListener(
|
container.addEventListener(
|
||||||
'touchstart',
|
'touchstart',
|
||||||
(ev) => {
|
(ev) => {
|
||||||
@@ -187,6 +188,7 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
velocity = 0;
|
velocity = 0;
|
||||||
pixelAccum = 0;
|
pixelAccum = 0;
|
||||||
isTouching = true;
|
isTouching = true;
|
||||||
|
didScroll = false;
|
||||||
lastTime = 0;
|
lastTime = 0;
|
||||||
if (scrollFrame) {
|
if (scrollFrame) {
|
||||||
cancelAnimationFrame(scrollFrame);
|
cancelAnimationFrame(scrollFrame);
|
||||||
@@ -201,6 +203,7 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
'touchmove',
|
'touchmove',
|
||||||
(ev) => {
|
(ev) => {
|
||||||
if (ev.touches.length === 1 && isTouching) {
|
if (ev.touches.length === 1 && isTouching) {
|
||||||
|
didScroll = true;
|
||||||
const touchY = ev.touches[0].clientY;
|
const touchY = ev.touches[0].clientY;
|
||||||
const delta = touchLastY - touchY; // positive = scroll down
|
const delta = touchLastY - touchY; // positive = scroll down
|
||||||
pixelAccum += delta;
|
pixelAccum += delta;
|
||||||
@@ -225,6 +228,12 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
if (!scrollFrame && Math.abs(velocity) > 0.3) {
|
if (!scrollFrame && Math.abs(velocity) > 0.3) {
|
||||||
scrollFrame = requestAnimationFrame(scrollLoop);
|
scrollFrame = requestAnimationFrame(scrollLoop);
|
||||||
}
|
}
|
||||||
|
// Tap (no scroll): refocus xterm's hidden textarea so keyboard input
|
||||||
|
// routes back to the terminal. Without this, a tap on the terminal area
|
||||||
|
// consumes the touch event but xterm's textarea never regains focus.
|
||||||
|
if (!didScroll && this.terminal) {
|
||||||
|
this.terminal.focus();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ passive: true }
|
{ passive: true }
|
||||||
);
|
);
|
||||||
@@ -284,22 +293,6 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
}
|
}
|
||||||
this.flushFlickerBuffer();
|
this.flushFlickerBuffer();
|
||||||
}
|
}
|
||||||
// Clear viewport + scrollback for Ink-based sessions before sending SIGWINCH.
|
|
||||||
// fitAddon.fit() reflows content: lines at old width may wrap to more rows,
|
|
||||||
// pushing overflow into scrollback. Ink's cursor-up count is based on the
|
|
||||||
// pre-reflow line count, so ghost renders accumulate in scrollback.
|
|
||||||
// Fix: \x1b[3J (Erase Saved Lines) clears scrollback reflow debris,
|
|
||||||
// then \x1b[H\x1b[2J clears the viewport for a clean Ink redraw.
|
|
||||||
const activeResizeSession = this.activeSessionId ? this.sessions.get(this.activeSessionId) : null;
|
|
||||||
if (
|
|
||||||
activeResizeSession &&
|
|
||||||
activeResizeSession.mode !== 'shell' &&
|
|
||||||
!activeResizeSession._ended &&
|
|
||||||
this.terminal &&
|
|
||||||
this.isTerminalAtBottom()
|
|
||||||
) {
|
|
||||||
this.terminal.write('\x1b[3J\x1b[H\x1b[2J');
|
|
||||||
}
|
|
||||||
// Skip server resize while mobile keyboard is visible — sending SIGWINCH
|
// Skip server resize while mobile keyboard is visible — sending SIGWINCH
|
||||||
// causes Ink to re-render at the new row count, garbling terminal output.
|
// causes Ink to re-render at the new row count, garbling terminal output.
|
||||||
// Local fit() still runs so xterm knows the viewport size for scrolling.
|
// Local fit() still runs so xterm knows the viewport size for scrolling.
|
||||||
@@ -311,6 +304,24 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
const rows = dims ? Math.max(dims.rows, MIN_ROWS) : MIN_ROWS;
|
const rows = dims ? Math.max(dims.rows, MIN_ROWS) : MIN_ROWS;
|
||||||
// Only send resize if dimensions actually changed
|
// Only send resize if dimensions actually changed
|
||||||
if (!this._lastResizeDims || cols !== this._lastResizeDims.cols || rows !== this._lastResizeDims.rows) {
|
if (!this._lastResizeDims || cols !== this._lastResizeDims.cols || rows !== this._lastResizeDims.rows) {
|
||||||
|
// Clear viewport + scrollback ONLY when dimensions actually change.
|
||||||
|
// fitAddon.fit() reflows content: lines at old width may wrap to more rows,
|
||||||
|
// pushing overflow into scrollback. Ink's cursor-up count is based on the
|
||||||
|
// pre-reflow line count, so ghost renders accumulate in scrollback.
|
||||||
|
// Fix: \x1b[3J (Erase Saved Lines) clears scrollback reflow debris,
|
||||||
|
// then \x1b[H\x1b[2J clears the viewport for a clean Ink redraw.
|
||||||
|
// IMPORTANT: Only clear when we're actually sending SIGWINCH (dims changed).
|
||||||
|
// Clearing without a subsequent Ink redraw leaves the terminal blank.
|
||||||
|
const activeResizeSession = this.activeSessionId ? this.sessions.get(this.activeSessionId) : null;
|
||||||
|
if (
|
||||||
|
activeResizeSession &&
|
||||||
|
activeResizeSession.mode !== 'shell' &&
|
||||||
|
!activeResizeSession._ended &&
|
||||||
|
this.terminal &&
|
||||||
|
this.isTerminalAtBottom()
|
||||||
|
) {
|
||||||
|
this.terminal.write('\x1b[3J\x1b[H\x1b[2J');
|
||||||
|
}
|
||||||
this._lastResizeDims = { cols, rows };
|
this._lastResizeDims = { cols, rows };
|
||||||
fetch(`/api/sessions/${this.activeSessionId}/resize`, {
|
fetch(`/api/sessions/${this.activeSessionId}/resize`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -1348,6 +1359,10 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
if (this.fitAddon) this.fitAddon.fit();
|
if (this.fitAddon) this.fitAddon.fit();
|
||||||
const dims = this.getTerminalDimensions();
|
const dims = this.getTerminalDimensions();
|
||||||
if (!dims) return;
|
if (!dims) return;
|
||||||
|
// Update _lastResizeDims so the throttledResize handler won't redundantly
|
||||||
|
// clear the terminal for the same dimensions (which would blank the screen
|
||||||
|
// without a subsequent Ink redraw to repaint it).
|
||||||
|
this._lastResizeDims = { cols: dims.cols, rows: dims.rows };
|
||||||
// Fast path: WebSocket resize
|
// Fast path: WebSocket resize
|
||||||
if (this._wsReady && this._wsSessionId === sessionId) {
|
if (this._wsReady && this._wsSessionId === sessionId) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user