/** * Atomic frames, so a fast animation does not flash. * * Ink repaints its live region by erasing the lines it last wrote and printing the new ones: * * stream.write(ansiEscapes.eraseLines(previousLineCount) + output) // ink/build/log-update.js * * That is one `write`, but a terminal does not treat it as one picture. It erases the region, * displays the gap, then draws the replacement — and the gap is a visible flash. The taller the * live region, the more obvious it is, which is why it showed up here as flicker rather than as * tearing. It was previously worked around by repainting less often: the spinner advanced once a * second, the shimmer once every two, and the tick that reveals streamed text ran at 500-1000ms. * That hid the flash by making the interface look broken — reported as "even 10x slower than a * tortoise" — and it did not remove the cause, so any faster animation brought the flash back. * * DEC private mode 2026 removes the cause. Between "begin synchronized update" and "end", a * terminal that implements it buffers what it receives and presents the result in one frame, so * the erase and the redraw are never on screen separately. There is nothing to flash. Every * frame Ink writes is wrapped inside a single `write` call, so the begin and the end cannot be * separated by a crash, and terminals bound the block with their own timeout regardless. * * Terminals that do not implement it ignore it: setting and resetting an unrecognised DEC private * mode is defined as a no-op, which is why this needs no capability query and prints nothing on an * older terminal. It is still limited to a TTY — a pipe or a file should receive the frame bytes * and nothing else — and can be turned off with KONECK_NO_SYNC_OUTPUT=1 if some terminal is found * that handles it badly. */ /** Whether frames written to this stream should be presented atomically. */ export declare function wantsSynchronizedFrames(stream?: { isTTY?: boolean; }, env?: Record): boolean; /** One frame, bracketed so the terminal shows all of it at once or none of it. */ export declare function synchronizedFrame(chunk: string): string; /** * The same stream, with each write presented as one frame. * * A Proxy rather than a subclass or a prototype clone because Ink both writes to this object and * listens on it, and an EventEmitter keeps its listeners on the instance. Every member other than * `write` is read from — and every method bound to — the real stream, so `on('resize')` registers * on the stream the terminal actually resizes rather than on a copy that never hears about it. */ /** True when a chunk only moves the cursor and erases: it paints nothing at all. */ export declare function isErasureOnly(chunk: string): boolean; /** * The same stream, with each frame presented atomically. * * A Proxy rather than a subclass or a prototype clone because Ink both writes to this object and * listens on it, and an EventEmitter keeps its listeners on the instance. Every member other than * `write` is read from — and every method bound to — the real stream, so `on('resize')` registers * on the stream the terminal actually resizes rather than on a copy that never hears about it. * * ── Why a render is sometimes more than one write ── * * Ink usually paints in a single write: `eraseLines(n) + output`. Bracketing that is enough, and * it stays synchronous here. * * But when has new rows to commit — which is every time a reply or a tool result is * finished, so constantly while the agent works — it takes a different path: * * this.log.clear(); // write #1: erase the live region, print nothing * this.options.stdout.write(staticOutput); // write #2: the newly finished rows * this.log(output); // write #3: the live region again * * Bracketing those separately is worse than not bracketing at all: write #1 contains no printable * output, so it instructs the terminal to present, as a completed frame, the state in which the * composer and the status bar have been erased and nothing has replaced them. A guaranteed blank * flash, once per committed row. Reported as blinking "when the model is working". * * So a write that only erases starts a batch instead of being sent. Everything written for the * rest of the tick joins it, and the whole render leaves as one frame — erase, new rows and live * region together, which is the picture that was wanted in the first place. A render that is a * single write never enters the batch and is sent immediately, so ordinary typing is unchanged. */ export declare function withSynchronizedFrames(stream: T): T; /** * Whether this terminal really implements synchronized output, asked rather than assumed. * * Setting mode 2026 is safe everywhere — an unrecognised private mode is a defined no-op — so * nothing had to be known in advance to start using it. But whether it *works* decides something * else entirely: how often the whole screen may be redrawn. * * In alt-screen mode the transcript is re-rendered every frame, so each animation tick rewrites the * entire screen. On a terminal that composites the update that is invisible and the animation can * run fast. On one that does not, every tick is an erase the eye can see, twelve times a second — * reported as "the whole cli blinks when it is working, it blinks a lot ... otherwise people won't * use it". Those two terminals want opposite frame rates, and there is no number that suits both. * * So the terminal is asked. DECRQM (CSI ? 2026 $p) makes it report the mode: a reply of 1 or 2 means * it knows the mode (set or reset), 0 means it does not recognise it, and no reply at all means the * same thing in practice. Silence is treated as unsupported, which is both the safe default and * self-correcting: a terminal that implements the mode answers in microseconds, so the only thing a * timeout costs is a slower animation on a terminal that was going to flash anyway. * * Asked once, before Ink starts, because it reads a reply from stdin and Ink owns stdin afterwards. */ export declare function probeSynchronizedOutput(stdout?: { isTTY?: boolean; write?: (s: string) => unknown; }, stdin?: NodeJS.ReadStream, env?: Record, timeoutMs?: number): Promise; /** * What this terminal can do, in a form somebody can paste into a bug report. * * Exists because the answer decides which of two entirely different fixes a flicker report needs, * and it cannot be guessed from the outside. A terminal that composites a redraw can be repainted * freely; one that does not shows every erase, and the only remedy is to repaint less of the * screen. Two sessions were spent fixing the wrong one of those before anybody asked the terminal. * * It is a flag on the binary rather than a script to copy around because the machine that needs * asking is usually not the machine the investigating is happening on — the report that prompted * this was "still blinking, i am testing it in windows computer". */ export declare function reportTerminal(version: string): Promise; /** * The same stream, reporting a size it is safe to reason about. * * Ink decides how to paint by comparing what it is about to draw against `stdout.rows`: * * if (outputHeight >= this.options.stdout.rows) { ... clearTerminal ... } // ink/build/ink.js * * A terminal that does not report its size gives 0, and every height is greater than or equal to * zero — so that branch is taken on every single render, and every render wipes the screen, wipes * the scrollback, and rewrites the whole session. The worst path in the renderer, reached not by * a tall interface but by an unknown one. * * It is not a rare corner. `script` reports 0x0, and so do several CI runners, some editors' * integrated terminals, and any pty whose size was never set. Found while checking that KONECK * clears the screen when it opens: the check kept reporting a scrollback wipe that nothing in * KONECK had written. * * 80x24 is the fallback for the same reason it is everywhere else: it is the size a terminal is * assumed to be when it will not say, and being wrong about it costs a little wrapping. Being * wrong about zero costs the screen. */ export declare function withUsableSize(stream: T): T; //# sourceMappingURL=frame-sync.d.ts.map