@vincemakes/kiso-tui 0.1.32 → 0.1.34
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.
- package/dist/components.js +5 -2
- package/dist/compositor.d.ts +8 -7
- package/dist/compositor.js +73 -28
- package/dist/render.d.ts +10 -7
- package/dist/render.js +15 -10
- package/package.json +1 -1
package/dist/components.js
CHANGED
|
@@ -165,7 +165,10 @@ class UserMessage {
|
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
/** The thinking fold — one dim line, width-capped so the /think suffix
|
|
168
|
-
* rides the fold's own row (the #17 fix's slice, componentized).
|
|
168
|
+
* rides the fold's own row (the #17 fix's slice, componentized). The
|
|
169
|
+
* slice is DISPLAY-WIDTH-based (the char-based slice overflowed with
|
|
170
|
+
* CJK — 2 cells per char — and tripped invariant ① on a real
|
|
171
|
+
* Chinese session). */
|
|
169
172
|
class ThinkingFold {
|
|
170
173
|
cell;
|
|
171
174
|
constructor(cell) {
|
|
@@ -178,7 +181,7 @@ class ThinkingFold {
|
|
|
178
181
|
return [`${palette().dim}…${trimmed}${palette().reset}`];
|
|
179
182
|
const suffix = ` (${block.length} chars · /think)`;
|
|
180
183
|
const slice = Math.max(1, W - 1 - suffix.length);
|
|
181
|
-
return [`${palette().dim}…${trimmed
|
|
184
|
+
return [`${palette().dim}…${widthCut(trimmed, slice)}${suffix}${palette().reset}`];
|
|
182
185
|
}
|
|
183
186
|
}
|
|
184
187
|
/** The tool execution line + the approval mini-diff — every state is
|
package/dist/compositor.d.ts
CHANGED
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
* re-emitted — the native scrollback gets them, reflow-safe, and
|
|
15
15
|
* the user's shell history is never touched (zero \x1b[3J, zero
|
|
16
16
|
* replay);
|
|
17
|
-
* - the live region (content +
|
|
18
|
-
*
|
|
19
|
-
* line regardless of done-ness — the one
|
|
20
|
-
*
|
|
17
|
+
* - the live region (content + chrome + menu) is hard-capped: the
|
|
18
|
+
* content at H−4 (V6-3 — the four-row chrome); overflow FORCE-
|
|
19
|
+
* commits the oldest live line regardless of done-ness — the one
|
|
20
|
+
* sharp edge (asserted by the VT-emulator gate);
|
|
21
21
|
* - two crash invariants: ① every emitted line's visible width ≤ W
|
|
22
22
|
* (components fold; a violation THROWS with diagnostics — pi
|
|
23
23
|
* tui-main-screen.ts:447-473, no silent truncate); ② within the
|
|
@@ -32,9 +32,10 @@
|
|
|
32
32
|
* scheduler — a one-shot setTimeout re-armed only while a running
|
|
33
33
|
* tool exists (the #14/#15 zero-output contract is structural).
|
|
34
34
|
*
|
|
35
|
-
* Layout at H rows
|
|
36
|
-
* H−
|
|
37
|
-
*
|
|
35
|
+
* Layout at H rows (V6-3 — the design §03 chrome): content rows
|
|
36
|
+
* 1..H−4, upper ╌ H−3, editor (the slot) H−2, lower ╌ H−1, status H.
|
|
37
|
+
* Pipes / NO_COLOR: the passthrough branches below keep the v2a/v2b
|
|
38
|
+
* line-mode bytes byte-for-byte (the e2e guards them).
|
|
38
39
|
*/
|
|
39
40
|
import { type MenuItem } from "./editor.js";
|
|
40
41
|
/** The cursor marker — an APC private sequence the focus component
|
package/dist/compositor.js
CHANGED
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
* re-emitted — the native scrollback gets them, reflow-safe, and
|
|
15
15
|
* the user's shell history is never touched (zero \x1b[3J, zero
|
|
16
16
|
* replay);
|
|
17
|
-
* - the live region (content +
|
|
18
|
-
*
|
|
19
|
-
* line regardless of done-ness — the one
|
|
20
|
-
*
|
|
17
|
+
* - the live region (content + chrome + menu) is hard-capped: the
|
|
18
|
+
* content at H−4 (V6-3 — the four-row chrome); overflow FORCE-
|
|
19
|
+
* commits the oldest live line regardless of done-ness — the one
|
|
20
|
+
* sharp edge (asserted by the VT-emulator gate);
|
|
21
21
|
* - two crash invariants: ① every emitted line's visible width ≤ W
|
|
22
22
|
* (components fold; a violation THROWS with diagnostics — pi
|
|
23
23
|
* tui-main-screen.ts:447-473, no silent truncate); ② within the
|
|
@@ -32,9 +32,10 @@
|
|
|
32
32
|
* scheduler — a one-shot setTimeout re-armed only while a running
|
|
33
33
|
* tool exists (the #14/#15 zero-output contract is structural).
|
|
34
34
|
*
|
|
35
|
-
* Layout at H rows
|
|
36
|
-
* H−
|
|
37
|
-
*
|
|
35
|
+
* Layout at H rows (V6-3 — the design §03 chrome): content rows
|
|
36
|
+
* 1..H−4, upper ╌ H−3, editor (the slot) H−2, lower ╌ H−1, status H.
|
|
37
|
+
* Pipes / NO_COLOR: the passthrough branches below keep the v2a/v2b
|
|
38
|
+
* line-mode bytes byte-for-byte (the e2e guards them).
|
|
38
39
|
*/
|
|
39
40
|
import { truncateDiff } from "./diff.js";
|
|
40
41
|
import { displayWidth } from "./editor.js";
|
|
@@ -46,7 +47,7 @@ import { escapeTerminal, foldResult, foldThinking, palette, renderTerminalGap, r
|
|
|
46
47
|
export const CURSOR_MARKER = "\x1b_[kiso-cur]\x1b\\";
|
|
47
48
|
const FRAME_MS = 16; // state changes coalesce to ≥16ms frames
|
|
48
49
|
const SPINNER_MS = 200; // the spinner cadence — a ONE-SHOT re-armed on demand
|
|
49
|
-
const CHROME_ROWS =
|
|
50
|
+
const CHROME_ROWS = 4; // upper ╌ + input + lower ╌ + status — the design §03 chrome (V6-3)
|
|
50
51
|
/** The one compositor — implements the Body façade AND the Dock chrome
|
|
51
52
|
* API (see the class comments on each method group). */
|
|
52
53
|
export class Body {
|
|
@@ -332,7 +333,7 @@ export class Body {
|
|
|
332
333
|
const H = this.#lastH > 0 ? this.#lastH : process.stdout.rows ?? 24;
|
|
333
334
|
const out = [];
|
|
334
335
|
out.push("\x1b[r");
|
|
335
|
-
for (let row = H -
|
|
336
|
+
for (let row = H - 3; row <= H; row += 1) { // V6-3: the four chrome rows
|
|
336
337
|
out.push(`\x1b[${row};1H\x1b[0K`); // clear the three chrome rows
|
|
337
338
|
}
|
|
338
339
|
out.push(`\x1b[${Math.max(1, H - 1)};1H`);
|
|
@@ -470,6 +471,21 @@ export class Body {
|
|
|
470
471
|
return;
|
|
471
472
|
this.#lastH = H;
|
|
472
473
|
const ctx = { spinnerI: this.#spinnerI, now: Date.now() };
|
|
474
|
+
// V6-1 (the screen-state == frame-state rule): the resize's first
|
|
475
|
+
// frame — the terminal's reflow re-wrapped the committed content at
|
|
476
|
+
// the NEW width, so the cached folds are stale. Re-fold the
|
|
477
|
+
// committed cells so the every-row draw below re-paints them at the
|
|
478
|
+
// current geometry — the frame's model and the screen agree.
|
|
479
|
+
if (this.#fullRedraw) {
|
|
480
|
+
this.#lineCache = this.#lineCache.map(() => null);
|
|
481
|
+
this.#committedLines = 0;
|
|
482
|
+
for (let i = 0; i < this.#committed; i += 1) {
|
|
483
|
+
const cell = this.#cells[i];
|
|
484
|
+
const lines = cellComponent(cell).render(W, ctx);
|
|
485
|
+
this.#lineCache[i] = lines;
|
|
486
|
+
this.#committedLines += lines.length;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
473
489
|
// 1. the natural commits — the leading DONE cells freeze: their
|
|
474
490
|
// lines leave the live region, the scrolls + the committed
|
|
475
491
|
// writes below place them (the #17 "freeze as a real line",
|
|
@@ -490,7 +506,7 @@ export class Body {
|
|
|
490
506
|
// 3. the FORCE commits — the live region's hard cap H−1: overflow
|
|
491
507
|
// commits the oldest live cell UNCONDITIONALLY (the one sharp
|
|
492
508
|
// edge — the cap scalar is asserted by the gates).
|
|
493
|
-
while (liveLines.length
|
|
509
|
+
while (liveLines.length > H - 4 && this.#committed < this.#cells.length) { // V6-3: the content cap H−4
|
|
494
510
|
this.#commitCell(this.#committed, W, ctx);
|
|
495
511
|
liveLines = [];
|
|
496
512
|
for (const cell of this.#cells.slice(this.#committed)) {
|
|
@@ -601,28 +617,53 @@ export class Body {
|
|
|
601
617
|
}
|
|
602
618
|
/** The full-redraw path (the first frame, the resize repaint) — CUP
|
|
603
619
|
* allowed here; zero LF; zero \x1b[3J; zero replay. The committed
|
|
604
|
-
* lines (this frame's) write at [liveTop−N .. liveTop−1].
|
|
620
|
+
* lines (this frame's) write at [liveTop−N .. liveTop−1].
|
|
621
|
+
*
|
|
622
|
+
* V6-1 (the screen-state == frame-state rule): every row 1..H is
|
|
623
|
+
* covered — the committed/live/chrome writes AND the EL-only rows
|
|
624
|
+
* (above the committed section, the gap). The terminal's reflow
|
|
625
|
+
* re-wraps the old content at the new size — its shifted copies
|
|
626
|
+
* survive anywhere the draw does not touch; a draw that covers
|
|
627
|
+
* EVERY row is idempotent: N consecutive resizes end with the same
|
|
628
|
+
* screen as a single jump to the same size. */
|
|
605
629
|
#drawFull(out, W, H, liveTop, liveLines, menuRows, ctx) {
|
|
606
630
|
const committed = this.#committedLinesThisFrame;
|
|
607
|
-
|
|
631
|
+
// 0. the FROZEN rows — the re-folded committed content (re-flowed
|
|
632
|
+
// at the new width by the terminal): re-painted at [1..frozen],
|
|
633
|
+
// so the reflow's shifted copies can never ghost.
|
|
634
|
+
const frozen = this.#lineCache.slice(0, this.#committed - committed.length).flat().filter((l) => l !== null);
|
|
635
|
+
let r = 1;
|
|
636
|
+
for (const line of frozen) {
|
|
637
|
+
out.push(`\x1b[${r};1H\x1b[0K${this.#checked(line, W)}`);
|
|
638
|
+
r += 1;
|
|
639
|
+
}
|
|
640
|
+
// 1. the committed lines (this frame's).
|
|
608
641
|
for (const line of committed) {
|
|
609
642
|
out.push(`\x1b[${r};1H\x1b[0K${this.#checked(line, W)}`);
|
|
610
643
|
r += 1;
|
|
611
644
|
}
|
|
645
|
+
// 2. the live lines.
|
|
612
646
|
for (const line of liveLines) {
|
|
613
647
|
out.push(`\x1b[${r};1H\x1b[0K${this.#checked(line, W)}`);
|
|
614
648
|
r += 1;
|
|
615
649
|
}
|
|
616
|
-
|
|
650
|
+
// 3. the GAP rows (between the live content and the chrome) — EL.
|
|
651
|
+
for (let rr = r; rr <= H - 4; rr += 1) {
|
|
652
|
+
out.push(`\x1b[${rr};1H\x1b[0K`);
|
|
653
|
+
}
|
|
654
|
+
const menuTop = H - 3 - menuRows.length;
|
|
617
655
|
for (let i = 0; i < menuRows.length; i += 1) {
|
|
618
656
|
out.push(`\x1b[${menuTop + i};1H\x1b[0K${this.#checked(menuRows[i], W)}`);
|
|
619
657
|
}
|
|
620
|
-
|
|
658
|
+
// V6-3: the design §03 chrome — upper ╌ (H−3), input (H−2),
|
|
659
|
+
// lower ╌ (H−1), status (H).
|
|
660
|
+
out.push(`\x1b[${H - 3};1H\x1b[0K${footerLine(W)}`);
|
|
621
661
|
const editor = this.#inputRow(W, ctx);
|
|
622
|
-
out.push(`\x1b[${H -
|
|
623
|
-
out.push(`\x1b[${H};1H\x1b[0K${footerLine(W)}`);
|
|
624
|
-
|
|
625
|
-
|
|
662
|
+
out.push(`\x1b[${H - 2};1H\x1b[0K${this.#checked(editor.stripped, W)}`);
|
|
663
|
+
out.push(`\x1b[${H - 1};1H\x1b[0K${footerLine(W)}`);
|
|
664
|
+
out.push(`\x1b[${H};1H\x1b[0K${this.#checked(statusLine(this.#status, this.#tail, this.#question !== null, W), W)}`);
|
|
665
|
+
// the cursor: up two (the input row at H−2) + left to the marker
|
|
666
|
+
out.push("\x1b[2A");
|
|
626
667
|
if (editor.afterW > 0)
|
|
627
668
|
out.push(`\x1b[${editor.afterW}D`);
|
|
628
669
|
}
|
|
@@ -640,12 +681,16 @@ export class Body {
|
|
|
640
681
|
out.push("\n");
|
|
641
682
|
}
|
|
642
683
|
else {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
684
|
+
// no commits — jump from the anchor (H−2) straight to the
|
|
685
|
+
// bottom row H (the commit path's LFs left the cursor there)
|
|
686
|
+
out.push("\x1b[2B");
|
|
687
|
+
}
|
|
688
|
+
// the bottom-up repaint, from the last row up — V6-3: the design
|
|
689
|
+
// §03 chrome: status (H), lower ╌ (H−1), input (H−2), upper ╌ (H−3)
|
|
690
|
+
out.push(`\x1b[1G\x1b[0K${this.#checked(statusLine(this.#status, this.#tail, this.#question !== null, W), W)}`); // H — the status
|
|
691
|
+
out.push(`\x1b[1A\x1b[1G\x1b[0K${footerLine(W)}`); // H−1 — the lower ╌
|
|
692
|
+
out.push(`\x1b[1A\x1b[1G\x1b[0K${this.#checked(editor.stripped, W)}`); // H−2 — the input
|
|
693
|
+
out.push(`\x1b[1A\x1b[1G\x1b[0K${footerLine(W)}`); // H−3 — the upper ╌
|
|
649
694
|
for (let i = menuRows.length - 1; i >= 0; i -= 1) {
|
|
650
695
|
out.push(`\x1b[1A\x1b[1G\x1b[0K${this.#checked(menuRows[i], W)}`);
|
|
651
696
|
}
|
|
@@ -659,7 +704,7 @@ export class Body {
|
|
|
659
704
|
// CRLF) cannot misplace them the way a relative march could).
|
|
660
705
|
// 1. the GAP rows (between the live content and the chrome) — EL'd
|
|
661
706
|
// so the old content there cannot ghost.
|
|
662
|
-
for (let r = liveTop + liveLines.length; r <= H -
|
|
707
|
+
for (let r = liveTop + liveLines.length; r <= H - 4; r += 1) {
|
|
663
708
|
out.push(`\x1b[${r};1H\x1b[0K`);
|
|
664
709
|
}
|
|
665
710
|
// 2. the STALE rows above the committed section — the scrolled old
|
|
@@ -675,11 +720,11 @@ export class Body {
|
|
|
675
720
|
for (let i = 0; i < committed.length; i += 1) {
|
|
676
721
|
out.push(`\x1b[${Math.max(1, liveTop - committed.length + i)};1H\x1b[0K${this.#checked(committed[i], W)}`);
|
|
677
722
|
}
|
|
678
|
-
// the cursor: down to the anchor (H−
|
|
723
|
+
// the cursor: down to the anchor (H−2, the input row) + left to the marker —
|
|
679
724
|
// the down-distance from the LAST written row (the committed bottom,
|
|
680
725
|
// the stale bottom, or the gap bottom when nothing else wrote).
|
|
681
|
-
const lastRow = committed.length > 0 ? liveTop - 1 : staleFrom < liveTop ? liveTop - 1 : H -
|
|
682
|
-
const down = H -
|
|
726
|
+
const lastRow = committed.length > 0 ? liveTop - 1 : staleFrom < liveTop ? liveTop - 1 : H - 4;
|
|
727
|
+
const down = H - 2 - lastRow; // the anchor: the input row (H−2)
|
|
683
728
|
if (down > 0)
|
|
684
729
|
out.push(`\x1b[${down}B`);
|
|
685
730
|
if (editor.afterW > 0)
|
package/dist/render.d.ts
CHANGED
|
@@ -202,17 +202,20 @@ export declare function renderStatusLine(turn: number, usage: RunUsage, ctxRatio
|
|
|
202
202
|
*/
|
|
203
203
|
export declare function renderTerminalGap(statusLine: string | null): string;
|
|
204
204
|
/**
|
|
205
|
-
* v3 §01 — the banner, block-split. The logo is
|
|
206
|
-
* (
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
205
|
+
* v3 §01 (V6-2) — the banner, block-split. The logo is THREE BRICK rows
|
|
206
|
+
* (the logo.svg pixel form — K I S O), then a BLANK, then the info rows:
|
|
207
|
+
* "kiso vX — tagline" + extensions. The tagline rides the version line
|
|
208
|
+
* (the old logo MIDDLE row was the tagline — a text row masquerading as
|
|
209
|
+
* the logo's centre). Every row truncates at the terminal width with a
|
|
210
|
+
* " (+N)" marker (N = the hidden display width); a window narrower than
|
|
211
|
+
* 40 columns skips the logo + the blank entirely — only the info rows.
|
|
212
|
+
* Pure.
|
|
210
213
|
*/
|
|
211
214
|
export declare const TAGLINE = "the coding agent that survives kill -9";
|
|
212
215
|
/** v3 §01: truncate a row at `width`, marking the hidden span " (+N)". */
|
|
213
216
|
export declare function truncateRow(row: string, width: number): string;
|
|
214
|
-
/** v3 §01: the banner lines for a width W — logo (skipped under
|
|
215
|
-
* columns) +
|
|
217
|
+
/** v3 §01 (V6-2): the banner lines for a width W — logo (skipped under
|
|
218
|
+
* 40 columns) + blank + "kiso vX — tagline" + extensions. */
|
|
216
219
|
export declare function bannerLines(W: number, version: string, extensionsText: string): string[];
|
|
217
220
|
/** v3 §02 — the recap line that ends a run, replacing the "done" label +
|
|
218
221
|
* the old status line. All fields derive LOCALLY from the event stream
|
package/dist/render.js
CHANGED
|
@@ -283,14 +283,17 @@ export function renderTerminalGap(statusLine) {
|
|
|
283
283
|
return `${statusLine === null ? "" : `${statusLine}\n`}\n`;
|
|
284
284
|
}
|
|
285
285
|
/**
|
|
286
|
-
* v3 §01 — the banner, block-split. The logo is
|
|
287
|
-
* (
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
286
|
+
* v3 §01 (V6-2) — the banner, block-split. The logo is THREE BRICK rows
|
|
287
|
+
* (the logo.svg pixel form — K I S O), then a BLANK, then the info rows:
|
|
288
|
+
* "kiso vX — tagline" + extensions. The tagline rides the version line
|
|
289
|
+
* (the old logo MIDDLE row was the tagline — a text row masquerading as
|
|
290
|
+
* the logo's centre). Every row truncates at the terminal width with a
|
|
291
|
+
* " (+N)" marker (N = the hidden display width); a window narrower than
|
|
292
|
+
* 40 columns skips the logo + the blank entirely — only the info rows.
|
|
293
|
+
* Pure.
|
|
291
294
|
*/
|
|
292
295
|
export const TAGLINE = "the coding agent that survives kill -9";
|
|
293
|
-
const LOGO_ROWS = ["█ █ ▀█▀ █▀▀ █▀█",
|
|
296
|
+
const LOGO_ROWS = ["█ █ ▀█▀ █▀▀ █▀█", "█▀▄ █ ▀▀█ █ █", "▀ ▀ ▀▀▀ ▀▀▀ ▀▀▀"];
|
|
294
297
|
/** Display width of a row (1-cell ASCII; 2-cell CJK/wide). */
|
|
295
298
|
function displayW(row) {
|
|
296
299
|
let w = 0;
|
|
@@ -315,14 +318,16 @@ export function truncateRow(row, width) {
|
|
|
315
318
|
}
|
|
316
319
|
return `${row.slice(0, i)} (+${displayW(row) - w})`;
|
|
317
320
|
}
|
|
318
|
-
/** v3 §01: the banner lines for a width W — logo (skipped under
|
|
319
|
-
* columns) +
|
|
321
|
+
/** v3 §01 (V6-2): the banner lines for a width W — logo (skipped under
|
|
322
|
+
* 40 columns) + blank + "kiso vX — tagline" + extensions. */
|
|
320
323
|
export function bannerLines(W, version, extensionsText) {
|
|
321
324
|
const rows = [];
|
|
322
|
-
if (W >= 40)
|
|
325
|
+
if (W >= 40) {
|
|
323
326
|
for (const r of LOGO_ROWS)
|
|
324
327
|
rows.push(truncateRow(r, W));
|
|
325
|
-
|
|
328
|
+
rows.push("");
|
|
329
|
+
}
|
|
330
|
+
rows.push(truncateRow(`kiso v${version} — ${TAGLINE}`, W));
|
|
326
331
|
if (extensionsText !== "")
|
|
327
332
|
rows.push(truncateRow(extensionsText, W));
|
|
328
333
|
return rows;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-tui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.34",
|
|
4
4
|
"description": "kiso tui — the pure terminal layer (cell renderer, dock, raw editor, diff, palette). Zero runtime dependencies: input is data, output is bytes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|