From 92ade69cd62acfbfeb4a8cbea327cebfa8c93b9c Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Sat, 24 Oct 2026 20:16:32 +0000 Subject: [PATCH] test(app): reproduce cursor separation on unequal restore sizes Replay a 41-row shell restore into both 41-row and 35-row native emulators. The smaller replay leaves the cursor six rows below the prompt after the viewport grows. This intentionally failing reproduction preserves the unresolved restore sizing defect for investigation. --- .../headless-terminal-output-drain.test.ts | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/app/src/terminal/native-renderer/headless-terminal-output-drain.test.ts b/packages/app/src/terminal/native-renderer/headless-terminal-output-drain.test.ts index fb94588a01..c19fb4e0b0 100644 --- a/packages/app/src/terminal/native-renderer/headless-terminal-output-drain.test.ts +++ b/packages/app/src/terminal/native-renderer/headless-terminal-output-drain.test.ts @@ -107,7 +107,7 @@ function createScreenRestorer( return { async restore(snapshot: Awaited>) { - drain.restoreSnapshot(snapshot); + drain.restoreText(renderTerminalSnapshotToAnsi(snapshot)); await drain.flush(); frameRecorder.frames[0]?.(); return screen; @@ -116,6 +116,38 @@ function createScreenRestorer( } describe("native terminal output drain", () => { + test.each([41, 35])( + "keeps a restored shell cursor beside its prompt after replay at %i rows", + async (rows) => { + const source = createNativeHeadlessTerminal({ rows: 41, cols: 49 }); + await source.write(Array.from({ length: 16 }, (_, i) => `probe ${i}\r\n`).join("") + "$ "); + const snapshot = source.getState(); + const terminal = createNativeHeadlessTerminal({ rows, cols: 49 }); + const frameRecorder = createFrameRecorder(); + const drain = createNativeTerminalOutputDrain({ + write: (chunk) => terminal.write(chunk), + reset: () => terminal.reset(), + getViewportState: () => terminal.getViewportState(), + onPaint: () => {}, + scheduleFrame: frameRecorder.scheduleFrame, + cancelFrame: frameRecorder.cancelFrame, + }); + try { + drain.restoreText(renderTerminalSnapshotToAnsi(snapshot)); + await drain.flush(); + terminal.resize({ rows: 41, cols: 49 }); + const restored = terminal.getState(); + const promptRow = restored.grid.findIndex((row) => rowText(row) === "$ ".trimEnd()); + expect(promptRow).toBeGreaterThanOrEqual(0); + expect(restored.cursor).toEqual({ row: promptRow, col: 2 }); + } finally { + drain.dispose(); + terminal.dispose(); + source.dispose(); + } + }, + ); + test("drains large bursts without waiting one frame per 2KB parse slice", async () => { const writes: string[] = []; const parseSamples: Array<{ parsedChars: number; queuedChars: number }> = [];