Skip to content

Commit 4cd15ff

Browse files
committed
fix(ui): fall back to plain write when block exceeds terminal height
The cursor-up escape that repositions the header only works when the block fits on screen without scrolling. With a tall next-steps body (~27 lines for deploy), a short terminal scrolls the buffer and the cursor-up no longer lands on the header, corrupting the output. Guard against this by comparing rowsBelow against process.stderr.rows before entering the animation path.
1 parent 4b4eba7 commit 4cd15ff

2 files changed

Lines changed: 29 additions & 0 deletions

File tree

packages/cli-core/src/lib/gradient.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,4 +199,25 @@ describe("animateHeader (interactive gating)", () => {
199199
await run();
200200
expect(captured.err).toContain("\r");
201201
});
202+
203+
test("falls back to a plain write when the block is taller than the terminal", async () => {
204+
const savedRows = process.stderr.rows;
205+
try {
206+
Object.defineProperty(process.stderr, "rows", { value: 5, configurable: true });
207+
await animateHeader({
208+
prefix: "",
209+
label: "Hi",
210+
fallback: bold,
211+
// 5 newlines → rowsBelow = 6, which exceeds rows = 5
212+
body: "a\nb\nc\nd\ne\n",
213+
frames: 2,
214+
intervalMs: 1,
215+
});
216+
expect(captured.err).not.toContain("\r");
217+
expect(captured.err).not.toContain("\x1b[?25l");
218+
expect(captured.err).toContain("a");
219+
} finally {
220+
Object.defineProperty(process.stderr, "rows", { value: savedRows, configurable: true });
221+
}
222+
});
202223
});

packages/cli-core/src/lib/gradient.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,14 @@ export async function animateHeader(options: AnimateHeaderOptions): Promise<void
149149
// header's own newline plus every newline inside the body.
150150
const rowsBelow = 1 + (body.match(/\n/g)?.length ?? 0);
151151

152+
// The cursor-up escape only lands on the header when the block fits on screen
153+
// without scrolling. Fall back to a plain write on short terminals where the
154+
// scroll would push the header out of reach.
155+
if (process.stderr.rows != null && rowsBelow >= process.stderr.rows) {
156+
write(`${prefix}${fallback(label)}\n${body}`);
157+
return;
158+
}
159+
152160
hideCursor();
153161
try {
154162
// Print the whole block first so the body is visible immediately, then step

0 commit comments

Comments
 (0)