Skip to content

Commit 5e8d522

Browse files
nickwesselmanclaude
andcommitted
Fix loading bar persisting on screen after task completion
Ink 6's unmount flow renders a final frame before the React tree clears, leaving stale loading bar output on screen. The component's null render from setIsDone(true) hasn't committed yet due to throttled rendering. Add an eraseOnExit option to the render() wrapper that intercepts stdout writes to track the maximum output height, then erases that many lines after the Ink instance exits. Applied to renderTasks and renderSingleTask. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0b41bff commit 5e8d522

2 files changed

Lines changed: 44 additions & 3 deletions

File tree

packages/cli-kit/src/private/node/ui.tsx

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {treeKill} from '../../public/node/tree-kill.js'
55

66
import {ReactElement} from 'react'
77
import {Key, render as inkRender, RenderOptions} from 'ink'
8+
import ansiEscapes from 'ansi-escapes'
89

910
import {EventEmitter} from 'events'
1011

@@ -26,9 +27,47 @@ export function renderOnce(element: JSX.Element, {logLevel = 'info', renderOptio
2627
return renderedString
2728
}
2829

29-
export async function render(element: JSX.Element, options?: RenderOptions) {
30-
const {waitUntilExit} = inkRender(element, options)
31-
await waitUntilExit()
30+
interface ExtendedRenderOptions extends RenderOptions {
31+
// When true, erase the final output after the Ink instance exits.
32+
// Use for transient UI (loading bars, progress indicators) that should
33+
// not persist on screen after completion.
34+
eraseOnExit?: boolean
35+
}
36+
37+
export async function render(element: JSX.Element, options?: ExtendedRenderOptions) {
38+
const {eraseOnExit, ...inkOptions} = options ?? {}
39+
const stdout = inkOptions.stdout ?? process.stdout
40+
// Track the height of Ink's last write so we can erase it after exit.
41+
// Ink 6's unmount performs a final onRender() before clearing the React tree,
42+
// which can leave stale output (e.g. a loading bar) on screen because the
43+
// component's null render hasn't committed yet due to throttled rendering.
44+
let lastOutputHeight = 0
45+
if (eraseOnExit && 'write' in stdout && typeof stdout.write === 'function') {
46+
const origWrite = stdout.write.bind(stdout) as typeof stdout.write
47+
stdout.write = ((...args: Parameters<typeof stdout.write>) => {
48+
const data = args[0]
49+
if (typeof data === 'string' && data.length > 0) {
50+
const lineCount = data.split('\n').length
51+
// Track the maximum height to avoid being clobbered by small
52+
// writes (e.g. cursor escape sequences) after the final render.
53+
if (lineCount > lastOutputHeight) {
54+
lastOutputHeight = lineCount
55+
}
56+
}
57+
return origWrite(...args)
58+
}) as typeof stdout.write
59+
60+
const {waitUntilExit} = inkRender(element, inkOptions)
61+
await waitUntilExit()
62+
63+
stdout.write = origWrite
64+
if (lastOutputHeight > 0) {
65+
stdout.write(ansiEscapes.eraseLines(lastOutputHeight))
66+
}
67+
} else {
68+
const {waitUntilExit} = inkRender(element, inkOptions)
69+
await waitUntilExit()
70+
}
3271
// We need to wait for other pending tasks -- unmounting of the ink component -- to complete
3372
return new Promise((resolve) => setImmediate(resolve))
3473
}

packages/cli-kit/src/public/node/ui.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,7 @@ export async function renderTasks<TContext>(
491491
render(<Tasks tasks={tasks} onComplete={resolve} noProgressBar={noProgressBar} />, {
492492
...renderOptions,
493493
exitOnCtrlC: false,
494+
eraseOnExit: true,
494495
})
495496
.then(() => {})
496497
.catch(reject)
@@ -525,6 +526,7 @@ export async function renderSingleTask<T>({
525526
render(<SingleTask title={title} task={task} onComplete={resolve} onAbort={onAbort} />, {
526527
...renderOptions,
527528
exitOnCtrlC: false,
529+
eraseOnExit: true,
528530
}).catch(reject)
529531
})
530532
}

0 commit comments

Comments
 (0)