koneck 2.128.17 → 2.128.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -39,6 +39,38 @@ export declare function synchronizedFrame(chunk: string): string;
39
39
  * `write` is read from — and every method bound to — the real stream, so `on('resize')` registers
40
40
  * on the stream the terminal actually resizes rather than on a copy that never hears about it.
41
41
  */
42
+ /** True when a chunk only moves the cursor and erases: it paints nothing at all. */
43
+ export declare function isErasureOnly(chunk: string): boolean;
44
+ /**
45
+ * The same stream, with each frame presented atomically.
46
+ *
47
+ * A Proxy rather than a subclass or a prototype clone because Ink both writes to this object and
48
+ * listens on it, and an EventEmitter keeps its listeners on the instance. Every member other than
49
+ * `write` is read from — and every method bound to — the real stream, so `on('resize')` registers
50
+ * on the stream the terminal actually resizes rather than on a copy that never hears about it.
51
+ *
52
+ * ── Why a render is sometimes more than one write ──
53
+ *
54
+ * Ink usually paints in a single write: `eraseLines(n) + output`. Bracketing that is enough, and
55
+ * it stays synchronous here.
56
+ *
57
+ * But when <Static> has new rows to commit — which is every time a reply or a tool result is
58
+ * finished, so constantly while the agent works — it takes a different path:
59
+ *
60
+ * this.log.clear(); // write #1: erase the live region, print nothing
61
+ * this.options.stdout.write(staticOutput); // write #2: the newly finished rows
62
+ * this.log(output); // write #3: the live region again
63
+ *
64
+ * Bracketing those separately is worse than not bracketing at all: write #1 contains no printable
65
+ * output, so it instructs the terminal to present, as a completed frame, the state in which the
66
+ * composer and the status bar have been erased and nothing has replaced them. A guaranteed blank
67
+ * flash, once per committed row. Reported as blinking "when the model is working".
68
+ *
69
+ * So a write that only erases starts a batch instead of being sent. Everything written for the
70
+ * rest of the tick joins it, and the whole render leaves as one frame — erase, new rows and live
71
+ * region together, which is the picture that was wanted in the first place. A render that is a
72
+ * single write never enters the batch and is sent immediately, so ordinary typing is unchanged.
73
+ */
42
74
  export declare function withSynchronizedFrames<T extends NodeJS.WriteStream>(stream: T): T;
43
75
  /**
44
76
  * Whether this terminal really implements synchronized output, asked rather than assumed.
@@ -1 +1 @@
1
- {"version":3,"file":"frame-sync.d.ts","sourceRoot":"","sources":["../src/frame-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAOH,4EAA4E;AAC5E,wBAAgB,uBAAuB,CACrC,MAAM,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAmB,EAC5C,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACpD,OAAO,CAMT;AAED,kFAAkF;AAClF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKvD;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAmBjF;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAA;CAAmB,EAC5E,KAAK,GAAE,MAAM,CAAC,UAA0B,EACxC,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,EACrD,SAAS,SAAM,GACd,OAAO,CAAC,OAAO,CAAC,CAmClB"}
1
+ {"version":3,"file":"frame-sync.d.ts","sourceRoot":"","sources":["../src/frame-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAOH,4EAA4E;AAC5E,wBAAgB,uBAAuB,CACrC,MAAM,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAmB,EAC5C,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACpD,OAAO,CAMT;AAED,kFAAkF;AAClF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKvD;AAED;;;;;;;GAOG;AACH,oFAAoF;AACpF,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAGpD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAkDjF;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAA;CAAmB,EAC5E,KAAK,GAAE,MAAM,CAAC,UAA0B,EACxC,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,EACrD,SAAS,SAAM,GACd,OAAO,CAAC,OAAO,CAAC,CAmClB"}
@@ -56,20 +56,88 @@ export function synchronizedFrame(chunk) {
56
56
  * `write` is read from — and every method bound to — the real stream, so `on('resize')` registers
57
57
  * on the stream the terminal actually resizes rather than on a copy that never hears about it.
58
58
  */
59
+ /** True when a chunk only moves the cursor and erases: it paints nothing at all. */
60
+ export function isErasureOnly(chunk) {
61
+ if (chunk === '')
62
+ return false;
63
+ return chunk.replace(/\u001b\[[0-9;]*[A-Za-z]/g, '') === '';
64
+ }
65
+ /**
66
+ * The same stream, with each frame presented atomically.
67
+ *
68
+ * A Proxy rather than a subclass or a prototype clone because Ink both writes to this object and
69
+ * listens on it, and an EventEmitter keeps its listeners on the instance. Every member other than
70
+ * `write` is read from — and every method bound to — the real stream, so `on('resize')` registers
71
+ * on the stream the terminal actually resizes rather than on a copy that never hears about it.
72
+ *
73
+ * ── Why a render is sometimes more than one write ──
74
+ *
75
+ * Ink usually paints in a single write: `eraseLines(n) + output`. Bracketing that is enough, and
76
+ * it stays synchronous here.
77
+ *
78
+ * But when <Static> has new rows to commit — which is every time a reply or a tool result is
79
+ * finished, so constantly while the agent works — it takes a different path:
80
+ *
81
+ * this.log.clear(); // write #1: erase the live region, print nothing
82
+ * this.options.stdout.write(staticOutput); // write #2: the newly finished rows
83
+ * this.log(output); // write #3: the live region again
84
+ *
85
+ * Bracketing those separately is worse than not bracketing at all: write #1 contains no printable
86
+ * output, so it instructs the terminal to present, as a completed frame, the state in which the
87
+ * composer and the status bar have been erased and nothing has replaced them. A guaranteed blank
88
+ * flash, once per committed row. Reported as blinking "when the model is working".
89
+ *
90
+ * So a write that only erases starts a batch instead of being sent. Everything written for the
91
+ * rest of the tick joins it, and the whole render leaves as one frame — erase, new rows and live
92
+ * region together, which is the picture that was wanted in the first place. A render that is a
93
+ * single write never enters the batch and is sent immediately, so ordinary typing is unchanged.
94
+ */
59
95
  export function withSynchronizedFrames(stream) {
60
96
  if (!wantsSynchronizedFrames(stream))
61
97
  return stream;
98
+ // Called on `target`, not lifted off it: a stream's write reaches into `this._writableState`,
99
+ // so a detached reference throws "Cannot read properties of undefined". Found by running the
100
+ // real binary on a pty, which a fake stream in a unit test had not needed to expose.
101
+ const raw = (chunk, ...rest) => {
102
+ const write = stream.write;
103
+ return write.call(stream, chunk, ...rest);
104
+ };
105
+ let batch = '';
106
+ let batching = false;
107
+ const flush = () => {
108
+ batching = false;
109
+ if (batch === '')
110
+ return;
111
+ const frame = batch;
112
+ batch = '';
113
+ raw(synchronizedFrame(frame));
114
+ };
115
+ // A batch lives for one tick, so an ordinary exit drains it first. This is for the abrupt ones.
116
+ process.once('exit', flush);
62
117
  return new Proxy(stream, {
63
118
  get(target, prop) {
64
119
  if (prop === 'write') {
65
- // Called on `target`, not lifted off it: a stream's write reaches into `this._writableState`,
66
- // so a detached reference throws "Cannot read properties of undefined". Found by running
67
- // the real binary on a pty, which a fake stream in a unit test had not needed to expose.
68
120
  return (chunk, ...rest) => {
69
- const write = target.write;
70
- return typeof chunk === 'string'
71
- ? write.call(target, synchronizedFrame(chunk), ...rest)
72
- : write.call(target, chunk, ...rest);
121
+ // Anything that is not a plain string — a Buffer, or a write with a callback that must
122
+ // actually be called — is sent as it is, after whatever is queued, so nothing is
123
+ // reordered around it.
124
+ if (typeof chunk !== 'string' || rest.some(a => typeof a === 'function')) {
125
+ flush();
126
+ return raw(chunk, ...rest);
127
+ }
128
+ if (batching) {
129
+ batch += chunk;
130
+ return true;
131
+ }
132
+ if (isErasureOnly(chunk)) {
133
+ batch = chunk;
134
+ batching = true;
135
+ // A microtask, so it runs the moment Ink's synchronous render returns and never
136
+ // leaves a frame waiting on I/O.
137
+ queueMicrotask(flush);
138
+ return true;
139
+ }
140
+ return raw(synchronizedFrame(chunk));
73
141
  };
74
142
  }
75
143
  const value = Reflect.get(target, prop, target);
@@ -1 +1 @@
1
- {"version":3,"file":"frame-sync.js","sourceRoot":"","sources":["../src/frame-sync.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"frame-sync.js","sourceRoot":"","sources":["../src/frame-sync.ts"],"names":[],"mappings":"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"}
@@ -67,6 +67,27 @@ export declare function pasteForDisplay(text: string, maxRows?: number): string;
67
67
  * only ever holds the fragment of the current block that has not been committed yet — the text a
68
68
  * reader is watching appear, rather than the whole reply waiting for the turn to end.
69
69
  */
70
+ /** At most this many files are previewed while a tool is still running. */
71
+ export declare const LIVE_DIFF_FILES = 2;
72
+ /**
73
+ * How many lines of each file's diff may be drawn while the tool that is writing it still runs.
74
+ *
75
+ * There has to be a ceiling, and it has to come from the window. Ink paints by erasing the lines
76
+ * it last wrote, but it cannot erase lines that have scrolled off — so when the live region is at
77
+ * least as tall as the terminal it gives up and writes `clearTerminal + everything` instead, on
78
+ * every render. That wipes the screen and the scrollback and repaints the entire session, per
79
+ * keystroke, getting heavier as the session grows.
80
+ *
81
+ * A running tool's diff was the only part of the live region with no natural ceiling: a large
82
+ * edit is a large diff, and off the alternate screen it is drawn inline rather than in the side
83
+ * panel. So a big write guaranteed the wipe-and-repaint path for as long as it took.
84
+ *
85
+ * The subtraction leaves room for everything else sharing the region — the reply tail, the step
86
+ * heading, the queued list, the composer and the status bar — and the floor keeps a preview
87
+ * worth looking at on a very short terminal, where a couple of scrolled lines are a far smaller
88
+ * problem than clearing the screen sixty times a minute.
89
+ */
90
+ export declare function liveDiffLineBudget(termRows: number | undefined, files?: number): number;
70
91
  export declare function liveReplyLines(termRows: number | undefined): number;
71
92
  /**
72
93
  * How long one animation frame lasts.
@@ -1 +1 @@
1
- {"version":3,"file":"ink-chat.d.ts","sourceRoot":"","sources":["../src/ink-chat.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ink-chat.d.ts","sourceRoot":"","sources":["../src/ink-chat.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AA2B3D,OAAO,EAAyB,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAuCtE,OAAO,KAAK,EAAY,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAiB5D,OAAO,EAA6C,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3F,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,YAAY,CAAC;AAoD7D,KAAK,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC;AAGhD,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAI9D;AAyED,6FAA6F;AAC7F,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAM7C;AA0BD,gGAAgG;AAChG,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA+BpE;AAED,qFAAqF;AACrF;;;;;GAKG;AASH,eAAO,MAAM,YAAY,mBAAuB,CAAC;AAEjD,+CAA+C;AAC/C,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,SAAS,CAAC;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAchG;AAED,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,SAAiB,GAAG,MAAM,CAM9E;AA6CD;;;;;;;;;;;GAWG;AACH,2EAA2E;AAC3E,eAAO,MAAM,eAAe,IAAI,CAAC;AAEjC;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,SAAkB,GAAG,MAAM,CAGhG;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAGnE;AAoBD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,QAAQ,KAAK,CAAC;AAE3B;;;;;GAKG;AACH,eAAO,MAAM,YAAY,QAAS,CAAC;AAEnC;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAGtG;AAyBD,OAAO,EACL,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EACzC,KAAK,QAAQ,EAAE,KAAK,UAAU,GAC/B,MAAM,iBAAiB,CAAC;AA8EzB,0FAA0F;AAC1F,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CASpD;AAYD,8CAA8C;AAC9C,KAAK,UAAU,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;AAClF,UAAU,UAAU;IAAG,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE;AAEtG,qGAAqG;AACrG,wBAAgB,mBAAmB,CACjC,QAAQ,GAAE,SAAS;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAa,GAC5D,UAAU,EAAE,CAId;AAUD,qDAAqD;AACrD,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAK7E;AAwBD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,MAAM,EACjB,YAAY,CAAC,EAAE,MAAM,EACrB,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,CAYR;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAElF;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,EAAE,CAWlF;AAyBD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,WAAiC,GAAG,MAAM,GAAG,SAAS,CAgBhH;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,EAAE,CAM/F;AAED,mGAAmG;AACnG,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,SAAM,GAAG,MAAM,EAAE,CAInG;AAGD,iGAAiG;AACjG,UAAU,SAAS;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,CAiBpE;AAQD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAMjF;AAGD;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,UAAU,GAAG,MAAM,CAAC;AAE7D,wBAAgB,WAAW,CAAC,IAAI,EAAE;IAChC,SAAS,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAC;CACtD,GAAG,WAAW,CAMd;AAGD,2DAA2D;AAC3D,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIhD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAKvF;AAGD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAKtF;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,KAAK,EAAE,MAAM,GACZ,MAAM,CAMR;AAiCD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,UAAQ,GAAG,MAAM,CAEvD;AAED,KAAK,eAAe,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE,CAAC;AAElG,sFAAsF;AACtF,wBAAgB,aAAa,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,EAAE,SAAS,EAAE,SAAS,eAAe,EAAE,GAAG,MAAM,CAa1G;AAED,kGAAkG;AAClG,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,GAAG,UAAQ,GAAG,MAAM,CAOzE;AAED,0FAA0F;AAC1F,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,CAEjF;AAED,iGAAiG;AACjG,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE9D;AAID,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAa7C,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAQxE;AAED,kGAAkG;AAClG,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,6EAA6E;AAC7E,wBAAgB,4BAA4B,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAO9E;AAED,8FAA8F;AAC9F,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,SAAI,GAAG,MAAM,CAGjF;AAED,oFAAoF;AACpF,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED,2FAA2F;AAC3F,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAsBpE;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CASrF;AAED,iGAAiG;AACjG,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrF,GAAG,OAAO,CAEV;AAED,8EAA8E;AAC9E,wBAAgB,mBAAmB,CACjC,eAAe,EAAE,OAAO,EACxB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,sBAAsB,UAAQ,GAC7B,OAAO,GAAG,MAAM,GAAG,KAAK,CAiB1B;AAED,MAAM,MAAM,iBAAiB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElE,sGAAsG;AACtG,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,mBAAmB,EAAE,MAAM,EAC3B,eAAe,CAAC,EAAE,MAAM,EACxB,QAAQ,CAAC,EAAE,iBAAiB,GAAG,IAAI,GAClC,MAAM,CAcR;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,SAAK,GAAG,OAAO,CAMnE;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEzD;AA4ED;;;;;GAKG;AACH;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAU3D;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,GAAG,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9D,CAAC,CAAC,GAAG,CAAC,GAAG,SAAS,CAKpB;AAED,wBAAgB,gBAAgB,CAC9B,MAAM,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAmB,EAC7E,GAAG,GAAE,MAAM,CAAC,UAAwB;AACpC;;;;;;;;;;;;;;;;;;GAkBG;AACH,iBAAiB,UAAQ,GACxB,OAAO,CAQT;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,EACjD,iBAAiB,EAAE,OAAO,EAC1B,MAAM,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAmB,EAC7E,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,OAAO,CAMT;AA2BD;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CACvB,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAgB,EAAE,EAAE;IACpD,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAC1D,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GACA,KAAK,CAAC,GAAG,CAAC,OAAO,CA2GnB;AAED;;;;;;;GAOG;AACH,wBAAgB,GAAG,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,kBAA0B,EAAE,EACxE;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7C,oEAAoE;IACpE,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAg5KhE;AAED,wBAAsB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAoCvE"}
package/dist/ink-chat.js CHANGED
@@ -383,6 +383,30 @@ function tailLines(text, maxLines, width) {
383
383
  * only ever holds the fragment of the current block that has not been committed yet — the text a
384
384
  * reader is watching appear, rather than the whole reply waiting for the turn to end.
385
385
  */
386
+ /** At most this many files are previewed while a tool is still running. */
387
+ export const LIVE_DIFF_FILES = 2;
388
+ /**
389
+ * How many lines of each file's diff may be drawn while the tool that is writing it still runs.
390
+ *
391
+ * There has to be a ceiling, and it has to come from the window. Ink paints by erasing the lines
392
+ * it last wrote, but it cannot erase lines that have scrolled off — so when the live region is at
393
+ * least as tall as the terminal it gives up and writes `clearTerminal + everything` instead, on
394
+ * every render. That wipes the screen and the scrollback and repaints the entire session, per
395
+ * keystroke, getting heavier as the session grows.
396
+ *
397
+ * A running tool's diff was the only part of the live region with no natural ceiling: a large
398
+ * edit is a large diff, and off the alternate screen it is drawn inline rather than in the side
399
+ * panel. So a big write guaranteed the wipe-and-repaint path for as long as it took.
400
+ *
401
+ * The subtraction leaves room for everything else sharing the region — the reply tail, the step
402
+ * heading, the queued list, the composer and the status bar — and the floor keeps a preview
403
+ * worth looking at on a very short terminal, where a couple of scrolled lines are a far smaller
404
+ * problem than clearing the screen sixty times a minute.
405
+ */
406
+ export function liveDiffLineBudget(termRows, files = LIVE_DIFF_FILES) {
407
+ const rows = termRows && termRows > 0 ? termRows : 24;
408
+ return Math.max(3, Math.floor((rows - 14) / Math.max(1, files)));
409
+ }
386
410
  export function liveReplyLines(termRows) {
387
411
  const rows = termRows && termRows > 0 ? termRows : 24;
388
412
  return rows < 12 ? 1 : 2;
@@ -1375,6 +1399,8 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
1375
1399
  const providerFramesRef = useRef(0);
1376
1400
  /** Last time an incoming provider event was allowed to refresh a still screen. */
1377
1401
  const lastIncomingPaintRef = useRef(0);
1402
+ /** A deferred paint for content that arrived inside the throttle window; see below. */
1403
+ const paintTimerRef = useRef(null);
1378
1404
  /**
1379
1405
  * Reasoning characters this turn, and when the last one arrived.
1380
1406
  *
@@ -1481,7 +1507,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
1481
1507
  sayRef.current += text;
1482
1508
  streamedRef.current += text;
1483
1509
  charsRef.current += text.length;
1484
- requestIncomingPaint();
1510
+ requestIncomingPaint(true);
1485
1511
  const cut = safeCommitPoint(sayRef.current);
1486
1512
  if (cut <= 0)
1487
1513
  return;
@@ -1741,7 +1767,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
1741
1767
  // produce tens of thousands of characters per turn, and what anyone watching wants is what
1742
1768
  // it is thinking now.
1743
1769
  thinkingTextRef.current = (thinkingTextRef.current + text).slice(-THINKING_TAIL_CHARS);
1744
- requestIncomingPaint();
1770
+ requestIncomingPaint(true);
1745
1771
  },
1746
1772
  onAgentProgress: (list) => { agentsRef.current = list; setAgents(list); },
1747
1773
  onWindowDetected: (limit, source) => setDetectedContext({ limit, source }),
@@ -1963,18 +1989,56 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
1963
1989
  * `animate: false` means no idle animation frames, not "hide real incoming work until the
1964
1990
  * turn completes". The old ticker was the only thing that painted ref-backed stream text, so
1965
1991
  * a provider could stream an entire reply while the screen stayed still, then reveal it in one
1966
- * burst. Refresh only when the provider actually sends progress, and cap it at one small frame
1967
- * per 1.2 seconds: that makes thinking and output visible without recreating the flicker that
1968
- * the still-screen setting exists to avoid.
1992
+ * burst. Refresh only when the provider actually sends progress: nothing arriving is still a
1993
+ * still screen, which is the whole point of the setting.
1994
+ *
1995
+ * The rate was a flat 1.2 seconds, and that was too slow to read as streaming at all. Reported
1996
+ * as "what it's typing/responding i only see the complete response, not even typing it in real
1997
+ * time" — accurate, because at 0.83 frames a second a reply that takes two seconds to arrive
1998
+ * gets one or two paints, which is indistinguishable from appearing all at once.
1999
+ *
2000
+ * That cap was defending against a repaint that cost 168ms on a Raspberry Pi 5, when the whole
2001
+ * transcript was re-rendered every frame. It no longer is — finished rows are cached and, off
2002
+ * the alternate screen, live in <Static> — so a repaint is one to two milliseconds and there is
2003
+ * nothing left to ration. The rate is now the terminal's own: 80ms where a redraw is composited,
2004
+ * 250ms where it is not, the same floor the animation uses, so a terminal that would flash is
2005
+ * still not asked to flash.
1969
2006
  */
1970
- function requestIncomingPaint() {
2007
+ function requestIncomingPaint(trailing = false) {
1971
2008
  if (animate || ask)
1972
2009
  return;
2010
+ const floor = floorFor(syncedFramesRef.current);
1973
2011
  const now = Date.now();
1974
- if (now - lastIncomingPaintRef.current < 1_200)
2012
+ const since = now - lastIncomingPaintRef.current;
2013
+ if (since >= floor) {
2014
+ lastIncomingPaintRef.current = now;
2015
+ setRepaint(n => n + 1);
1975
2016
  return;
1976
- lastIncomingPaintRef.current = now;
1977
- setRepaint(n => n + 1);
2017
+ }
2018
+ /*
2019
+ * Inside the window, with something new to show: paint when the window expires.
2020
+ *
2021
+ * The throttle used to have a leading edge only — a request arriving too soon after the last
2022
+ * paint was dropped, not deferred. So whether a chunk of the reply was ever displayed came
2023
+ * down to whether the next chunk happened to land after the window had expired, and text that
2024
+ * arrived in the wrong millisecond was never painted at all. At the old 1.2-second window that
2025
+ * was most of the reply, which is why it read as no streaming whatsoever: "what it's
2026
+ * typing/responding i only see the complete response, not even typing it in real time".
2027
+ *
2028
+ * One timer, not one per chunk: whatever has accumulated by the time it fires is what gets
2029
+ * painted, so a fast stream still costs one frame per window rather than one per token.
2030
+ *
2031
+ * Only for callers that actually change what is on screen. A content-free keep-alive frame
2032
+ * from the provider has nothing new to paint, and scheduling one for it would repaint a
2033
+ * screen that is meant to stay still while nothing is arriving.
2034
+ */
2035
+ if (!trailing || paintTimerRef.current)
2036
+ return;
2037
+ paintTimerRef.current = setTimeout(() => {
2038
+ paintTimerRef.current = null;
2039
+ lastIncomingPaintRef.current = Date.now();
2040
+ setRepaint(n => n + 1);
2041
+ }, Math.max(1, floor - since));
1978
2042
  }
1979
2043
  const [ask, setAsk] = useState(null);
1980
2044
  /*
@@ -2324,7 +2388,6 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
2324
2388
  const spinFrame = anim.spin;
2325
2389
  const shimmerFrame = anim.shimmer;
2326
2390
  const [spinWord, setSpinWord] = useState(0);
2327
- const elapsedMs = anim.elapsed;
2328
2391
  /**
2329
2392
  * Extra cursor movements from a single input chunk.
2330
2393
  *
@@ -2376,6 +2439,27 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
2376
2439
  /** Tools this turn has run, which is what separates work from a plain answer. */
2377
2440
  const turnToolsRef = useRef(0); // a tool failed and was worked around
2378
2441
  const busyStart = useRef(0);
2442
+ /**
2443
+ * How long this turn has been running, read from the clock at render rather than from the
2444
+ * animation.
2445
+ *
2446
+ * It used to be `anim.elapsed`, which is only ever written by the animation ticker — and that
2447
+ * ticker does not run when `animate` is off. So with the animation disabled the status line
2448
+ * reported "(0ms)" for the entire turn while the token count beside it climbed, because the
2449
+ * tokens are read from a ref at render and the time was not. Reported exactly that way, with a
2450
+ * screenshot: "(0ms | 184 tokens)".
2451
+ *
2452
+ * It was not only cosmetic. This figure decides which tier of work word is shown, and whether
2453
+ * the line says "no response from the provider yet" after eight seconds. Frozen at zero, the
2454
+ * word never advanced and the stall warning could never appear — so a turn that had genuinely
2455
+ * hung looked identical to one that had just started.
2456
+ *
2457
+ * Computed here instead, so every repaint shows the truth whatever caused it, and no repaint is
2458
+ * needed merely to keep a clock honest.
2459
+ */
2460
+ const elapsedMs = busy && busyStart.current > 0
2461
+ ? Date.now() - busyStart.current
2462
+ : anim.elapsed;
2379
2463
  /*
2380
2464
  * Whether this terminal composites a redraw, decided once before Ink started.
2381
2465
  *
@@ -2447,9 +2531,21 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
2447
2531
  // Drive the animated working indicator while busy. With animation off, no timer is permitted:
2448
2532
  // in an alternate-screen terminal every tick erases and redraws the complete frame, which is
2449
2533
  // the visible black flash people configured this setting to eliminate.
2534
+ // A deferred paint belongs to the turn that asked for it. Left to fire after the turn ended it
2535
+ // would repaint a finished screen, and after unmount it would set state on a gone component.
2536
+ useEffect(() => () => {
2537
+ if (paintTimerRef.current) {
2538
+ clearTimeout(paintTimerRef.current);
2539
+ paintTimerRef.current = null;
2540
+ }
2541
+ }, []);
2450
2542
  useEffect(() => {
2451
2543
  if (!busy) {
2452
2544
  busyStart.current = 0;
2545
+ if (paintTimerRef.current) {
2546
+ clearTimeout(paintTimerRef.current);
2547
+ paintTimerRef.current = null;
2548
+ }
2453
2549
  setAnim({ spin: 0, shimmer: 0, elapsed: 0 });
2454
2550
  return;
2455
2551
  }
@@ -5715,23 +5811,61 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
5715
5811
  * itself on a tinted band. Every rendered line is padded to the same width so the tint forms a
5716
5812
  * solid block rather than a ragged edge, which is what makes an added run legible at a glance.
5717
5813
  */
5718
- const renderDiff = (d, key) => {
5814
+ const liveDiffBudget = liveDiffLineBudget(termRows);
5815
+ /**
5816
+ * One file's diff, with a ceiling on how many lines it may draw.
5817
+ *
5818
+ * The ceiling exists because of how Ink puts a frame on screen. It normally erases the lines it
5819
+ * last wrote and prints the new ones, but when the live region is at least as tall as the
5820
+ * terminal it cannot do that — lines that scrolled off cannot be erased — so it falls back to
5821
+ *
5822
+ * stdout.write(ansiEscapes.clearTerminal + fullStaticOutput + output) // ink/build/ink.js
5823
+ *
5824
+ * `clearTerminal` is ESC[2J ESC[3J ESC[H: wipe the screen, wipe the scrollback, home the cursor.
5825
+ * Then the entire session is written again. That happens on *every* render — so once the live
5826
+ * region outgrows the window, every keystroke wipes and repaints the whole terminal, and it gets
5827
+ * heavier as the session grows because the static output it rewrites only ever accumulates.
5828
+ * Measured with a recording stdout: a six-line live region gave three 466-byte frames for three
5829
+ * keystrokes; a forty-five-line one in a forty-row terminal gave three full clear-and-rewrites.
5830
+ *
5831
+ * That is the blinking. Not the animation, not the terminal, not the machine.
5832
+ *
5833
+ * A running tool's diff was the thing that made the region that tall: it is drawn in full, and
5834
+ * with the side panel shut — which it always is off the alternate screen — it is drawn inline.
5835
+ * A two-hundred-line edit therefore guaranteed the clear-and-rewrite path for as long as the
5836
+ * write took. Nothing is lost by capping it: this is the live preview of a diff that is written
5837
+ * to scrollback in full the moment the step finishes.
5838
+ */
5839
+ const renderDiff = (d, key, maxLines = Infinity) => {
5719
5840
  const numbers = d.hunks.flatMap(h => h.lines.map(l => l.after ?? l.before ?? 0));
5720
5841
  const gutter = Math.max(2, String(Math.max(0, ...numbers)).length);
5721
5842
  const width = Math.max(24, barWidth - gutter - 8);
5722
5843
  const header = d.created ? 'new file' : d.deleted ? 'deleted' : diffSummary(d);
5844
+ const all = [];
5845
+ for (const [hi, hunk] of d.hunks.entries()) {
5846
+ if (hi > 0)
5847
+ all.push({ key: `g${hi}`, gap: true });
5848
+ for (const [li, l] of hunk.lines.entries()) {
5849
+ all.push({ key: `${hi}-${li}`, gap: false, kind: l.kind,
5850
+ shown: (l.kind === 'del' ? l.before : l.after) ?? undefined,
5851
+ sign: l.kind === 'add' ? '+' : l.kind === 'del' ? '-' : ' ',
5852
+ text: l.text });
5853
+ }
5854
+ }
5855
+ const shownLines = all.slice(0, Math.max(1, maxLines));
5856
+ const clipped = all.length - shownLines.length;
5723
5857
  return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: DIM, children: "\u2514" }), _jsx(Text, { color: CYAN, children: d.path }), _jsx(Text, { color: d.created ? DIFF_ADD : d.deleted ? DIFF_DEL : MUTED, children: header })] }), d.binary
5724
5858
  ? _jsx(Text, { color: DIM, children: ' binary file - not shown' })
5725
- : d.hunks.map((hunk, hi) => (_jsxs(Box, { flexDirection: "column", children: [hi > 0 && _jsxs(Text, { color: DIM, children: [' '.repeat(gutter), " ..."] }), hunk.lines.map((l, li) => {
5726
- const shown = l.kind === 'del' ? l.before : l.after;
5727
- const sign = l.kind === 'add' ? '+' : l.kind === 'del' ? '-' : ' ';
5728
- const fg = l.kind === 'add' ? DIFF_ADD : l.kind === 'del' ? DIFF_DEL : MUTED;
5729
- const bg = l.kind === 'add' ? DIFF_ADD_BG : l.kind === 'del' ? DIFF_DEL_BG : undefined;
5730
- // Tabs would make the tinted block a ragged width, so they are expanded.
5731
- const body = `${sign} ${l.text}`.replace(/\t/g, ' ');
5732
- const cell = fitCells(body, width, true);
5733
- return (_jsxs(Box, { children: [_jsxs(Text, { color: DIM, children: [String(shown ?? '').padStart(gutter), " "] }), _jsx(Text, { color: fg, ...(bg ? { backgroundColor: bg } : {}), children: cell })] }, li));
5734
- })] }, hi))), d.hiddenLines > 0 && (_jsxs(Text, { color: DIM, children: [' '.repeat(gutter), " ... ", d.hiddenLines, " more changed lines"] }))] }, key));
5859
+ : shownLines.map(l => l.gap
5860
+ // A gap between hunks stands for the untouched lines that were skipped.
5861
+ ? _jsxs(Text, { color: DIM, children: [' '.repeat(gutter), " ..."] }, l.key)
5862
+ : (() => {
5863
+ const fg = l.kind === 'add' ? DIFF_ADD : l.kind === 'del' ? DIFF_DEL : MUTED;
5864
+ const bg = l.kind === 'add' ? DIFF_ADD_BG : l.kind === 'del' ? DIFF_DEL_BG : undefined;
5865
+ // Tabs would make the tinted block a ragged width, so they are expanded.
5866
+ const body = `${l.sign} ${l.text}`.replace(/\t/g, ' ');
5867
+ return (_jsxs(Box, { children: [_jsxs(Text, { color: DIM, children: [String(l.shown ?? '').padStart(gutter), " "] }), _jsx(Text, { color: fg, ...(bg ? { backgroundColor: bg } : {}), children: fitCells(body, width, true) })] }, l.key));
5868
+ })()), clipped > 0 && (_jsxs(Text, { color: DIM, children: [' '.repeat(gutter), " ... ", clipped, " more lines, shown in full when this finishes"] })), d.hiddenLines > 0 && (_jsxs(Text, { color: DIM, children: [' '.repeat(gutter), " ... ", d.hiddenLines, " more changed lines"] }))] }, key));
5735
5869
  };
5736
5870
  const renderStep = (step, key) => {
5737
5871
  if (step.kind === 'say') {
@@ -5741,7 +5875,20 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
5741
5875
  const ms = (step.endedAt ?? Date.now()) - step.startedAt;
5742
5876
  const glyph = running ? SPINNER[spinFrame] : step.ok === false ? G.fail : G.ok;
5743
5877
  const color = running ? AMBER : step.ok === false ? CRIMSON : GREEN;
5744
- return (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: color, children: glyph }), _jsx(Text, { color: AMBER, bold: true, children: step.name }), _jsx(Text, { color: DIM, children: "\u00B7" }), _jsx(Text, { color: MUTED, children: fmtElapsed(ms) })] }), step.detail !== '' && (step.diffs ?? []).length === 0 && (_jsxs(Box, { paddingLeft: 2, gap: 1, children: [_jsx(Text, { color: DIM, children: "\u2514" }), _jsx(Text, { color: MUTED, children: step.detail })] })), step.ok === false && step.error && (_jsxs(Box, { paddingLeft: 2, gap: 1, children: [_jsx(Text, { color: DIM, children: "\u2514" }), _jsx(Text, { color: CRIMSON, children: fitCells(step.error, Math.max(20, barWidth - 6)) })] })), running && step.output && (_jsx(Box, { paddingLeft: 4, children: _jsx(Text, { color: DIM, children: fitCells(step.output, Math.max(20, barWidth - 6)) }) })), !diffPanelOpenRef.current && (step.diffs ?? []).map((d, i) => renderDiff(d, i))] }, key));
5878
+ return (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: color, children: glyph }), _jsx(Text, { color: AMBER, bold: true, children: step.name }), _jsx(Text, { color: DIM, children: "\u00B7" }), _jsx(Text, { color: MUTED, children: fmtElapsed(ms) })] }), step.detail !== '' && (step.diffs ?? []).length === 0 && (_jsxs(Box, { paddingLeft: 2, gap: 1, children: [_jsx(Text, { color: DIM, children: "\u2514" }), _jsx(Text, { color: MUTED, children: step.detail })] })), step.ok === false && step.error && (_jsxs(Box, { paddingLeft: 2, gap: 1, children: [_jsx(Text, { color: DIM, children: "\u2514" }), _jsx(Text, { color: CRIMSON, children: fitCells(step.error, Math.max(20, barWidth - 6)) })] })), running && step.output && (_jsx(Box, { paddingLeft: 4, children: _jsx(Text, { color: DIM, children: fitCells(step.output, Math.max(20, barWidth - 6)) }) })), !diffPanelOpenRef.current && (running
5879
+ /*
5880
+ * While the tool is still working this is a preview, and it has to stay small.
5881
+ *
5882
+ * It is the one part of the live region with no natural ceiling — a large edit is a
5883
+ * large diff — and the live region going past the window height is what makes Ink wipe
5884
+ * and repaint the whole terminal on every render. See renderDiff for the mechanism.
5885
+ *
5886
+ * Nothing is hidden by this. The step is committed to scrollback the moment it
5887
+ * finishes, and the branch below draws it there in full.
5888
+ */
5889
+ ? (step.diffs ?? []).slice(0, LIVE_DIFF_FILES)
5890
+ .map((d, i) => renderDiff(d, i, liveDiffBudget))
5891
+ : (step.diffs ?? []).map((d, i) => renderDiff(d, i)))] }, key));
5745
5892
  };
5746
5893
  /**
5747
5894
  * One transcript row.