askeroo
Version:
A modern CLI prompt library with flow control, history navigation, and conditional prompts
77 lines • 3.36 kB
JavaScript
/**
* WORKAROUND: Fix for Ink rendering timing bug
*
* PROBLEM: When going back from fields (especially from multi fields or groups),
* React state updates happen too quickly, causing Ink to render with inconsistent state.
* This results in visual duplication where nodes appear twice or fields appear in both
* the completed fields section and the current active field section.
*
* When terminal height is limited, Ink has to redraw the entire screen, which exacerbates
* the issue and causes the first prompt to be duplicated in the output.
*
* ROOT CAUSE:
* 1. Tree state is updated (nodes removed, active node changed)
* 2. React state update is triggered (setTreeRevision)
* 3. Ink tries to render before React has fully processed the tree changes
* The rapid state updates cause React to batch updates incorrectly, and Ink can render
* with a partial/inconsistent view of the tree state, showing both old and new nodes.
*
* SOLUTION: Introduce multiple micro-delays to allow React to fully process state updates:
* 1. First delay using console.log() to allow initial React processing
* 2. Second delay using setImmediate() to push to next event loop tick
* We suppress stdout to avoid visual artifacts.
*
* WHY THIS WORKS: The combination of synchronous console.log() and asynchronous
* setImmediate() ensures React has enough time to fully process and reconcile the
* tree state before Ink attempts to render it, preventing rendering inconsistencies.
*/
export function applyInkRenderingFix() {
const originalStdout = process.stdout.write;
// Apply multiple micro-delays through console.log to ensure React has enough time
// to fully process and reconcile tree state changes before Ink renders
// More delays = more time for React reconciliation, preventing duplicate rendering
for (let i = 0; i < 10; i++) {
process.stdout.write = () => true;
console.log();
process.stdout.write = originalStdout;
}
}
/**
* Async version of the rendering fix that waits for the next event loop tick
* Use this when you need to ensure complete React reconciliation before proceeding
*/
export async function applyInkRenderingFixAsync() {
// First apply the synchronous fix
applyInkRenderingFix();
// Then wait for the next event loop tick to ensure all React updates are processed
await new Promise((resolve) => {
setImmediate(() => {
// Wait one more tick to be absolutely sure
setImmediate(() => {
resolve();
});
});
});
}
/**
* Execute a callback while temporarily suppressing stdout to prevent
* intermediate rendering artifacts during state transitions
*/
export function withOutputSuppression(callback) {
const originalStdoutWrite = process.stdout.write;
const originalStderrWrite = process.stderr.write;
try {
// Temporarily suppress all output
process.stdout.write = () => true;
process.stderr.write = () => true;
// Execute the callback
const result = callback();
return result;
}
finally {
// Restore output after a brief delay to ensure state is settled
process.stdout.write = originalStdoutWrite;
process.stderr.write = originalStderrWrite;
}
}
//# sourceMappingURL=ink-rendering-fix.js.map