From 6a276390837315bf7cde798b1a0954ac0b5ce7a0 Mon Sep 17 00:00:00 2001 From: arkon Date: Sun, 22 Mar 2026 03:38:09 +0100 Subject: [PATCH] fix: increase Ink frame search window from 4KB to 64KB to prevent partial frames Single Ink frames with response content can be 10-20KB, so the 4KB tail was too small and caused blank gaps. Now searches the last 64KB for VPA row drops to find the last complete frame boundary. Co-Authored-By: Claude Opus 4.6 --- src/web/routes/session-routes.ts | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/web/routes/session-routes.ts b/src/web/routes/session-routes.ts index f07e1a5f..d4d59dc0 100644 --- a/src/web/routes/session-routes.ts +++ b/src/web/routes/session-routes.ts @@ -73,15 +73,28 @@ function stripInkRedrawBloat(buffer: string): string { // If the redraw section is small (<16KB), not worth stripping if (redrawPart.length < 16384) return buffer; - // Keep only the last 4KB of redraw frames — this preserves the final visual state - // (spinner position, status bar text, token count, etc.) - const tail = redrawPart.slice(-4096); - // Avoid starting mid-escape: find first complete frame boundary - // eslint-disable-next-line no-control-regex - const frameStart = tail.search(/\x1b\(B\x1b\[m|\x1b\[\d+d|\x1b\[\d+;\d+H/); - const cleanTail = frameStart > 0 ? tail.slice(frameStart) : tail; + // Find the last complete Ink frame by searching for where the VPA row + // number drops (cursor jumps back to viewport top for a new render cycle). + // Search the last 64KB — a single Ink frame with response content can be + // 10-20KB, so 4KB was too small and caused partial frames (blank gap). + const searchLen = Math.min(redrawPart.length, 65536); + const searchWindow = redrawPart.slice(-searchLen); - return contentPart + cleanTail; + // eslint-disable-next-line no-control-regex + const vpaRe = /\x1b\[(\d+)d/g; + let lastFrameStart = 0; + let prevRow = -1; + let match; + while ((match = vpaRe.exec(searchWindow)) !== null) { + const row = parseInt(match[1], 10); + // Row number dropped significantly — Ink started a new frame + if (prevRow > 0 && row < prevRow - 5) { + lastFrameStart = match.index; + } + prevRow = row; + } + + return contentPart + searchWindow.slice(lastFrameStart); } export function registerSessionRoutes(