@vincemakes/kiso-tui 0.1.35 → 0.1.37
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.d.ts +26 -3
- package/dist/components.js +396 -44
- package/dist/compositor.d.ts +24 -7
- package/dist/compositor.js +111 -39
- package/dist/editor.d.ts +2 -6
- package/dist/editor.js +11 -53
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/render.d.ts +35 -4
- package/dist/render.js +115 -29
- package/dist/width.d.ts +15 -0
- package/dist/width.js +59 -0
- package/package.json +1 -1
package/dist/compositor.d.ts
CHANGED
|
@@ -20,10 +20,13 @@
|
|
|
20
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
|
-
* tui-main-screen.ts:447-473, no silent truncate); ②
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
23
|
+
* tui-main-screen.ts:447-473, no silent truncate); ② every steady-
|
|
24
|
+
* frame CUP lands in the CONTENT area (rows ≤ H−4−menu — the
|
|
25
|
+
* committed band, the stale/gap ELs, and the LIVE lines at their
|
|
26
|
+
* model rows; fix C's sanctioned reinterpretation, ADR-0046) — the
|
|
27
|
+
* CHROME rows (H−3..H) are RELATIVE-only (vertical A/B, horizontal
|
|
28
|
+
* G/D); CUP over the whole screen exists only in the full-redraw
|
|
29
|
+
* path (the first frame, the resize repaint);
|
|
27
30
|
* - the cursor DERIVES from the frame: the focus component embeds the
|
|
28
31
|
* APC marker in its rendered line; the compositor locates, strips,
|
|
29
32
|
* and relatively positions from the frame — no side-channel cursor
|
|
@@ -38,6 +41,7 @@
|
|
|
38
41
|
* line-mode bytes byte-for-byte (the e2e guards them).
|
|
39
42
|
*/
|
|
40
43
|
import { type MenuItem } from "./editor.js";
|
|
44
|
+
import { type ResumeMeta } from "./render.js";
|
|
41
45
|
/** The cursor marker — an APC private sequence the focus component
|
|
42
46
|
* embeds at the edit position; the compositor strips it and moves
|
|
43
47
|
* relatively (it never reaches the terminal). */
|
|
@@ -83,6 +87,14 @@ export declare class Body {
|
|
|
83
87
|
text: string;
|
|
84
88
|
status: "pending" | "active" | "done";
|
|
85
89
|
}[]): void;
|
|
90
|
+
/** The startup banner (W1): a LIVE cell — the tier re-derives per
|
|
91
|
+
* frame (bannerLines with the CURRENT W and H), so a resize re-tiers
|
|
92
|
+
* the art instead of re-folding frozen rows (a window below 40 cols
|
|
93
|
+
* never paints the logo). W5: the resume metas ride the cell — the
|
|
94
|
+
* list re-gates with the tier (BIG only) and re-times with the
|
|
95
|
+
* frame. The inactive path keeps the historical bytes (no resume —
|
|
96
|
+
* the pipe contract). */
|
|
97
|
+
banner(version: string, extensionsText: string, resume?: ResumeMeta[]): void;
|
|
86
98
|
raw(lines: string[]): void;
|
|
87
99
|
/** The last COMPLETE thinking block, for /think. */
|
|
88
100
|
lastThinking(): string | null;
|
|
@@ -109,7 +121,10 @@ export declare class Body {
|
|
|
109
121
|
* top: external writes (the CLI's console.error CRLF) can shift the
|
|
110
122
|
* committed content down, and a formula-top ED0 would clear it. */
|
|
111
123
|
onResize(): void;
|
|
112
|
-
|
|
124
|
+
/** W18: the status row's right-aligned hint is part of the status
|
|
125
|
+
* state — the compacting row passes "esc to cancel" (the affordance
|
|
126
|
+
* must survive repaints). */
|
|
127
|
+
setStatus(text: string, hint?: string | null): void;
|
|
113
128
|
setTail(tail: string): void;
|
|
114
129
|
showQuestion(question: string): void;
|
|
115
130
|
clearQuestion(): void;
|
|
@@ -135,7 +150,9 @@ export declare class Body {
|
|
|
135
150
|
/** Teardown — flush a pending frame, stop the timers. */
|
|
136
151
|
close(): void;
|
|
137
152
|
/** The live region's scalar — the unit tests assert the cap directly
|
|
138
|
-
* (the e2e gate pins the screen consequence).
|
|
153
|
+
* (the e2e gate pins the screen consequence). W11: the formula's
|
|
154
|
+
* blanks are join artifacts — the count includes them (they are real
|
|
155
|
+
* screen rows), threaded against the previous sibling's OWN rows. */
|
|
139
156
|
liveCount(): number;
|
|
140
157
|
render(): void;
|
|
141
158
|
}
|
|
@@ -149,7 +166,7 @@ export declare class Dock {
|
|
|
149
166
|
enter(): void;
|
|
150
167
|
exit(): void;
|
|
151
168
|
onResize(): void;
|
|
152
|
-
setStatus(text: string): void;
|
|
169
|
+
setStatus(text: string, hint?: string | null): void;
|
|
153
170
|
setTail(tail: string): void;
|
|
154
171
|
showQuestion(question: string): void;
|
|
155
172
|
clearQuestion(): void;
|
package/dist/compositor.js
CHANGED
|
@@ -20,10 +20,13 @@
|
|
|
20
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
|
-
* tui-main-screen.ts:447-473, no silent truncate); ②
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
23
|
+
* tui-main-screen.ts:447-473, no silent truncate); ② every steady-
|
|
24
|
+
* frame CUP lands in the CONTENT area (rows ≤ H−4−menu — the
|
|
25
|
+
* committed band, the stale/gap ELs, and the LIVE lines at their
|
|
26
|
+
* model rows; fix C's sanctioned reinterpretation, ADR-0046) — the
|
|
27
|
+
* CHROME rows (H−3..H) are RELATIVE-only (vertical A/B, horizontal
|
|
28
|
+
* G/D); CUP over the whole screen exists only in the full-redraw
|
|
29
|
+
* path (the first frame, the resize repaint);
|
|
27
30
|
* - the cursor DERIVES from the frame: the focus component embeds the
|
|
28
31
|
* APC marker in its rendered line; the compositor locates, strips,
|
|
29
32
|
* and relatively positions from the frame — no side-channel cursor
|
|
@@ -39,8 +42,8 @@
|
|
|
39
42
|
*/
|
|
40
43
|
import { truncateDiff } from "./diff.js";
|
|
41
44
|
import { displayWidth } from "./editor.js";
|
|
42
|
-
import { Container, SPINNER, cellComponent, foldLine, footerLine, statusLine, visibleWidth, } from "./components.js";
|
|
43
|
-
import { escapeTerminal, foldResult, foldThinking, palette, renderTerminalGap, renderToolSummary } from "./render.js";
|
|
45
|
+
import { Container, SPINNER, bodySpacing, cellComponent, foldLine, footerLine, statusLine, visibleWidth, } from "./components.js";
|
|
46
|
+
import { bannerLines, escapeTerminal, foldResult, foldThinking, palette, renderTerminalGap, renderToolSummary } from "./render.js";
|
|
44
47
|
/** The cursor marker — an APC private sequence the focus component
|
|
45
48
|
* embeds at the edit position; the compositor strips it and moves
|
|
46
49
|
* relatively (it never reaches the terminal). */
|
|
@@ -75,6 +78,7 @@ export class Body {
|
|
|
75
78
|
#resizeHandler = null;
|
|
76
79
|
// the chrome state (the Dock façade)
|
|
77
80
|
#status = "";
|
|
81
|
+
#statusHint = null;
|
|
78
82
|
#tail = "";
|
|
79
83
|
#question = null;
|
|
80
84
|
#inputState = () => ({ line: "", cursor: 0 });
|
|
@@ -151,8 +155,18 @@ export class Body {
|
|
|
151
155
|
this.#write(`→ ${escapeTerminal(name)}(${escapeTerminal(JSON.stringify(input).slice(0, 200))})\n`);
|
|
152
156
|
return;
|
|
153
157
|
}
|
|
158
|
+
// W12: the cell carries the delegate's child roles from the FULL
|
|
159
|
+
// input — the display summary is sliced at 60 chars (unparseable);
|
|
160
|
+
// the roles are the only running-state data the parent holds (there
|
|
161
|
+
// is no live channel to a running child session).
|
|
162
|
+
const childRoles = [];
|
|
163
|
+
for (const t of Array.isArray(input.tasks) ? input.tasks : []) {
|
|
164
|
+
if (typeof t === "object" && t !== null && typeof t.role === "string") {
|
|
165
|
+
childRoles.push(t.role);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
154
168
|
this.#toolCells.set(callId, this.#cells.length);
|
|
155
|
-
this.#cells.push({ kind: "tool", name, input: summary, state: "pending", isError: false, resultText: "", diff: null, added: 0, removed: 0, startedAt: null, doneAt: null, done: false });
|
|
169
|
+
this.#cells.push({ kind: "tool", name, input: summary, childRoles, state: "pending", isError: false, resultText: "", diff: null, added: 0, removed: 0, startedAt: null, doneAt: null, done: false });
|
|
156
170
|
this.#mark();
|
|
157
171
|
}
|
|
158
172
|
toolApproval(callId, diff) {
|
|
@@ -283,6 +297,30 @@ export class Body {
|
|
|
283
297
|
this.#cells.push({ kind: "checklist", header, items, done: true });
|
|
284
298
|
this.#mark();
|
|
285
299
|
}
|
|
300
|
+
/** The startup banner (W1): a LIVE cell — the tier re-derives per
|
|
301
|
+
* frame (bannerLines with the CURRENT W and H), so a resize re-tiers
|
|
302
|
+
* the art instead of re-folding frozen rows (a window below 40 cols
|
|
303
|
+
* never paints the logo). W5: the resume metas ride the cell — the
|
|
304
|
+
* list re-gates with the tier (BIG only) and re-times with the
|
|
305
|
+
* frame. The inactive path keeps the historical bytes (no resume —
|
|
306
|
+
* the pipe contract). */
|
|
307
|
+
banner(version, extensionsText, resume = []) {
|
|
308
|
+
if (!this.#isActive()) {
|
|
309
|
+
this.#closeOpenThinking();
|
|
310
|
+
this.#closeOpenText();
|
|
311
|
+
const W = this.#opts.width() || 80; // a 0-size pty falls back
|
|
312
|
+
const H = this.#opts.height();
|
|
313
|
+
const p = palette();
|
|
314
|
+
for (const r of bannerLines(W, H, version, extensionsText))
|
|
315
|
+
this.#write(`${p.dim}${r}${p.reset}\n`);
|
|
316
|
+
this.#write("\n");
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
this.#closeOpenThinking();
|
|
320
|
+
this.#closeOpenText();
|
|
321
|
+
this.#cells.push({ kind: "banner", version, extensionsText, resume, done: true });
|
|
322
|
+
this.#mark();
|
|
323
|
+
}
|
|
286
324
|
raw(lines) {
|
|
287
325
|
if (!this.#isActive()) {
|
|
288
326
|
this.#closeOpenThinking();
|
|
@@ -357,8 +395,12 @@ export class Body {
|
|
|
357
395
|
this.#dirty = true;
|
|
358
396
|
this.render(); // the immediate redraw at the NEW geometry
|
|
359
397
|
}
|
|
360
|
-
|
|
398
|
+
/** W18: the status row's right-aligned hint is part of the status
|
|
399
|
+
* state — the compacting row passes "esc to cancel" (the affordance
|
|
400
|
+
* must survive repaints). */
|
|
401
|
+
setStatus(text, hint = null) {
|
|
361
402
|
this.#status = text;
|
|
403
|
+
this.#statusHint = hint;
|
|
362
404
|
this.redraw();
|
|
363
405
|
}
|
|
364
406
|
setTail(tail) {
|
|
@@ -449,19 +491,29 @@ export class Body {
|
|
|
449
491
|
}
|
|
450
492
|
// ---- the one writer ----
|
|
451
493
|
/** The live region's scalar — the unit tests assert the cap directly
|
|
452
|
-
* (the e2e gate pins the screen consequence).
|
|
494
|
+
* (the e2e gate pins the screen consequence). W11: the formula's
|
|
495
|
+
* blanks are join artifacts — the count includes them (they are real
|
|
496
|
+
* screen rows), threaded against the previous sibling's OWN rows. */
|
|
453
497
|
liveCount() {
|
|
454
498
|
const live = this.#cells.slice(this.#committed);
|
|
455
|
-
const ctx = { spinnerI: this.#spinnerI, now: Date.now() };
|
|
499
|
+
const ctx = { spinnerI: this.#spinnerI, now: Date.now(), height: this.#opts.height() };
|
|
456
500
|
const W = this.#opts.width();
|
|
457
501
|
let lines = 0;
|
|
458
|
-
|
|
459
|
-
|
|
502
|
+
let prev = this.#committed > 0 ? this.#lineCache[this.#committed - 1] : null;
|
|
503
|
+
for (const cell of live) {
|
|
504
|
+
const rows = cellComponent(cell).render(W, ctx);
|
|
505
|
+
lines += bodySpacing(prev, rows).length;
|
|
506
|
+
prev = rows;
|
|
507
|
+
}
|
|
460
508
|
return lines + CHROME_ROWS + this.#menuRows(W).length;
|
|
461
509
|
}
|
|
462
510
|
/** The lines committed THIS frame — the writes land in the frame's
|
|
463
511
|
* committed section (the rows just above the live region). */
|
|
464
512
|
#committedLinesThisFrame = [];
|
|
513
|
+
// the CELL count when the frame began — the drawFull frozen bound's
|
|
514
|
+
// unit (the cells committed BEFORE this frame; a force-commit frame's
|
|
515
|
+
// placed LINES outnumber its cells)
|
|
516
|
+
#committedAtFrameStart = 0;
|
|
465
517
|
render() {
|
|
466
518
|
if (!this.#isActive())
|
|
467
519
|
return;
|
|
@@ -470,7 +522,7 @@ export class Body {
|
|
|
470
522
|
if (H < 4)
|
|
471
523
|
return;
|
|
472
524
|
this.#lastH = H;
|
|
473
|
-
const ctx = { spinnerI: this.#spinnerI, now: Date.now() };
|
|
525
|
+
const ctx = { spinnerI: this.#spinnerI, now: Date.now(), height: H };
|
|
474
526
|
// V6-1 (the screen-state == frame-state rule): the resize's first
|
|
475
527
|
// frame — the terminal's reflow re-wrapped the committed content at
|
|
476
528
|
// the NEW width, so the cached folds are stale. Re-fold the
|
|
@@ -482,8 +534,8 @@ export class Body {
|
|
|
482
534
|
for (let i = 0; i < this.#committed; i += 1) {
|
|
483
535
|
const cell = this.#cells[i];
|
|
484
536
|
const lines = cellComponent(cell).render(W, ctx);
|
|
485
|
-
this.#lineCache[i] = lines;
|
|
486
|
-
this.#committedLines += lines.length;
|
|
537
|
+
this.#lineCache[i] = lines; // the cell's OWN rows — the cache stays raw
|
|
538
|
+
this.#committedLines += bodySpacing(i > 0 ? this.#lineCache[i - 1] : null, lines).length;
|
|
487
539
|
}
|
|
488
540
|
}
|
|
489
541
|
// 1. the natural commits — the leading DONE cells freeze: their
|
|
@@ -492,16 +544,24 @@ export class Body {
|
|
|
492
544
|
// short sessions included — the frame coalescing keeps a
|
|
493
545
|
// cell's first frame its freeze frame, so the frozen bytes
|
|
494
546
|
// emit exactly once).
|
|
547
|
+
this.#committedAtFrameStart = this.#committed;
|
|
495
548
|
this.#committedLinesThisFrame = [];
|
|
496
549
|
while (this.#committed < this.#cells.length && this.#cells[this.#committed].done) {
|
|
497
550
|
this.#commitCell(this.#committed, W, ctx);
|
|
498
551
|
}
|
|
499
552
|
// 2. the live lines — the unfinished cells (the tail) + the chrome.
|
|
553
|
+
// W11: the formula's blank above the first live cell hangs off
|
|
554
|
+
// the last COMMITTED sibling (the join spans the boundary).
|
|
500
555
|
const menuRows = this.#menuRows(W);
|
|
501
556
|
const chromeRows = CHROME_ROWS + menuRows.length;
|
|
502
557
|
let liveLines = [];
|
|
503
|
-
|
|
504
|
-
|
|
558
|
+
{
|
|
559
|
+
let prev = this.#committed > 0 ? this.#lineCache[this.#committed - 1] : null;
|
|
560
|
+
for (const cell of this.#cells.slice(this.#committed)) {
|
|
561
|
+
const rows = cellComponent(cell).render(W, ctx);
|
|
562
|
+
liveLines.push(...bodySpacing(prev, rows));
|
|
563
|
+
prev = rows;
|
|
564
|
+
}
|
|
505
565
|
}
|
|
506
566
|
// 3. the FORCE commits — the live region's hard cap H−1: overflow
|
|
507
567
|
// commits the oldest live cell UNCONDITIONALLY (the one sharp
|
|
@@ -509,8 +569,13 @@ export class Body {
|
|
|
509
569
|
while (liveLines.length > H - 4 && this.#committed < this.#cells.length) { // V6-3: the content cap H−4
|
|
510
570
|
this.#commitCell(this.#committed, W, ctx);
|
|
511
571
|
liveLines = [];
|
|
512
|
-
|
|
513
|
-
|
|
572
|
+
{
|
|
573
|
+
let prev = this.#committed > 0 ? this.#lineCache[this.#committed - 1] : null;
|
|
574
|
+
for (const cell of this.#cells.slice(this.#committed)) {
|
|
575
|
+
const rows = cellComponent(cell).render(W, ctx);
|
|
576
|
+
liveLines.push(...bodySpacing(prev, rows));
|
|
577
|
+
prev = rows;
|
|
578
|
+
}
|
|
514
579
|
}
|
|
515
580
|
}
|
|
516
581
|
// 4. the geometry — the live region's first row:
|
|
@@ -536,14 +601,17 @@ export class Body {
|
|
|
536
601
|
/** Commit the cell at index i: render + cache its lines (immutable —
|
|
537
602
|
* the force-committed form freezes at the current render), advance
|
|
538
603
|
* the bookkeeping — and collect the lines for this frame's writes.
|
|
539
|
-
* Pure accounting + the write list; the BYTES emit in the frame.
|
|
604
|
+
* Pure accounting + the write list; the BYTES emit in the frame.
|
|
605
|
+
* W11: the formula's blank above the cell (when one belongs) rides
|
|
606
|
+
* this cell's commit — the cache stays raw, the placed rows count. */
|
|
540
607
|
#commitCell(i, W, ctx) {
|
|
541
608
|
const cell = this.#cells[i];
|
|
542
609
|
const lines = cellComponent(cell).render(W, ctx);
|
|
543
610
|
this.#lineCache[i] = lines;
|
|
611
|
+
const placed = bodySpacing(i > 0 ? this.#lineCache[i - 1] : null, lines);
|
|
544
612
|
this.#committed += 1;
|
|
545
|
-
this.#committedLines +=
|
|
546
|
-
this.#committedLinesThisFrame.push(...
|
|
613
|
+
this.#committedLines += placed.length;
|
|
614
|
+
this.#committedLinesThisFrame.push(...placed);
|
|
547
615
|
}
|
|
548
616
|
/** The slot occupant's extra rows — the slash-command menu (above the
|
|
549
617
|
* status, in the rhythm gap + the content's spare rows — the old
|
|
@@ -630,20 +698,24 @@ export class Body {
|
|
|
630
698
|
const committed = this.#committedLinesThisFrame;
|
|
631
699
|
// 0. the FROZEN rows — the re-folded committed content (re-flowed
|
|
632
700
|
// at the new width by the terminal): re-painted at [1..frozen],
|
|
633
|
-
// so the reflow's shifted copies can never ghost.
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
//
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
701
|
+
// so the reflow's shifted copies can never ghost. W11: the
|
|
702
|
+
// formula's blanks between the cells are re-inserted here — the
|
|
703
|
+
// cache holds each cell's OWN rows; a missing blank would paint
|
|
704
|
+
// every row below it one row too high (the V6-1 idempotence
|
|
705
|
+
// rule: this draw must reproduce the screen, row by row).
|
|
706
|
+
// The bound is the CELL count at the frame's start — the force
|
|
707
|
+
// commit's placed LINES can exceed the cell count, and the old
|
|
708
|
+
// lines-bound went negative, skipping every previously-committed
|
|
709
|
+
// cell (the V6-1 frozen-loop finding — the banner vanished).
|
|
710
|
+
const frozen = [];
|
|
711
|
+
for (let i = 0; i < this.#committedAtFrameStart; i += 1) {
|
|
712
|
+
frozen.push(...bodySpacing(i > 0 ? this.#lineCache[i - 1] : null, this.#lineCache[i]));
|
|
644
713
|
}
|
|
645
|
-
|
|
646
|
-
|
|
714
|
+
let r = 1;
|
|
715
|
+
// one march — the frozen, the committed (this frame's), the live —
|
|
716
|
+
// in write order, r monotone (three byte-identical loops merged for
|
|
717
|
+
// the gate; the exact write sequence preserved)
|
|
718
|
+
for (const line of [...frozen, ...committed, ...liveLines]) {
|
|
647
719
|
out.push(`\x1b[${r};1H\x1b[0K${this.#checked(line, W)}`);
|
|
648
720
|
r += 1;
|
|
649
721
|
}
|
|
@@ -661,7 +733,7 @@ export class Body {
|
|
|
661
733
|
const editor = this.#inputRow(W, ctx);
|
|
662
734
|
out.push(`\x1b[${H - 2};1H\x1b[0K${this.#checked(editor.stripped, W)}`);
|
|
663
735
|
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)}`);
|
|
736
|
+
out.push(`\x1b[${H};1H\x1b[0K${this.#checked(statusLine(this.#status, this.#tail, this.#question !== null, W, this.#statusHint ?? undefined), W)}`);
|
|
665
737
|
// the cursor: up two (the input row at H−2) + left to the marker
|
|
666
738
|
out.push("\x1b[2A");
|
|
667
739
|
if (editor.afterW > 0)
|
|
@@ -686,7 +758,7 @@ export class Body {
|
|
|
686
758
|
out.push("\n");
|
|
687
759
|
// the bottom-up repaint, from the last row up — V6-3: the design
|
|
688
760
|
// §03 chrome: status (H), lower ╌ (H−1), input (H−2), upper ╌ (H−3)
|
|
689
|
-
out.push(`\x1b[1G\x1b[0K${this.#checked(statusLine(this.#status, this.#tail, this.#question !== null, W), W)}`); // H — the status
|
|
761
|
+
out.push(`\x1b[1G\x1b[0K${this.#checked(statusLine(this.#status, this.#tail, this.#question !== null, W, this.#statusHint ?? undefined), W)}`); // H — the status
|
|
690
762
|
out.push(`\x1b[1A\x1b[1G\x1b[0K${footerLine(W)}`); // H−1 — the lower ╌
|
|
691
763
|
out.push(`\x1b[1A\x1b[1G\x1b[0K${this.#checked(editor.stripped, W)}`); // H−2 — the input
|
|
692
764
|
out.push(`\x1b[1A\x1b[1G\x1b[0K${footerLine(W)}`); // H−3 — the upper ╌
|
|
@@ -803,8 +875,8 @@ export class Dock {
|
|
|
803
875
|
onResize() {
|
|
804
876
|
compositorRef?.onResize();
|
|
805
877
|
}
|
|
806
|
-
setStatus(text) {
|
|
807
|
-
compositorRef?.setStatus(text);
|
|
878
|
+
setStatus(text, hint) {
|
|
879
|
+
compositorRef?.setStatus(text, hint ?? null);
|
|
808
880
|
}
|
|
809
881
|
setTail(tail) {
|
|
810
882
|
compositorRef?.setTail(tail);
|
package/dist/editor.d.ts
CHANGED
|
@@ -16,12 +16,8 @@
|
|
|
16
16
|
* The editor is a SINGLE line: bracketed paste (?2004h) unwraps and
|
|
17
17
|
* inserts, internal newlines become spaces.
|
|
18
18
|
*/
|
|
19
|
-
|
|
20
|
-
export
|
|
21
|
-
/** Display width of a code-point array (cursor math, scrolling). */
|
|
22
|
-
export declare function widthOf(chars: readonly number[]): number;
|
|
23
|
-
/** Display width of a string. */
|
|
24
|
-
export declare function displayWidth(text: string): number;
|
|
19
|
+
import { charWidth, displayWidth, widthOf } from "./width.js";
|
|
20
|
+
export { charWidth, displayWidth, widthOf };
|
|
25
21
|
export declare const PROMPT = "\u258C ";
|
|
26
22
|
export declare const PROMPT_WIDTH: number;
|
|
27
23
|
/** v3 §04 — the slash-command menu's command table (English one-liners). */
|
package/dist/editor.js
CHANGED
|
@@ -16,56 +16,10 @@
|
|
|
16
16
|
* The editor is a SINGLE line: bracketed paste (?2004h) unwraps and
|
|
17
17
|
* inserts, internal newlines become spaces.
|
|
18
18
|
*/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (cp >= 0x2e80 && cp <= 0x303e)
|
|
24
|
-
return 2; // radicals .. CJK punctuation
|
|
25
|
-
if (cp >= 0x3041 && cp <= 0x33ff)
|
|
26
|
-
return 2; // kana, CJK compat
|
|
27
|
-
if (cp >= 0x3400 && cp <= 0x4dbf)
|
|
28
|
-
return 2; // CJK ext A
|
|
29
|
-
if (cp >= 0x4e00 && cp <= 0x9fff)
|
|
30
|
-
return 2; // CJK unified
|
|
31
|
-
if (cp >= 0xa000 && cp <= 0xa4cf)
|
|
32
|
-
return 2; // yi
|
|
33
|
-
if (cp >= 0xa960 && cp <= 0xa97f)
|
|
34
|
-
return 2; // hangul jamo ext
|
|
35
|
-
if (cp >= 0xac00 && cp <= 0xd7a3)
|
|
36
|
-
return 2; // hangul syllables
|
|
37
|
-
if (cp >= 0xf900 && cp <= 0xfaff)
|
|
38
|
-
return 2; // CJK compat ideographs
|
|
39
|
-
if (cp >= 0xfe10 && cp <= 0xfe19)
|
|
40
|
-
return 2; // vertical forms
|
|
41
|
-
if (cp >= 0xfe30 && cp <= 0xfe6f)
|
|
42
|
-
return 2; // CJK compat forms
|
|
43
|
-
if (cp >= 0xff00 && cp <= 0xff60)
|
|
44
|
-
return 2; // fullwidth forms
|
|
45
|
-
if (cp >= 0xffe0 && cp <= 0xffe6)
|
|
46
|
-
return 2; // fullwidth signs
|
|
47
|
-
if (cp >= 0x1f300 && cp <= 0x1f64f)
|
|
48
|
-
return 2; // emoji (misc + emoticons)
|
|
49
|
-
if (cp >= 0x1f900 && cp <= 0x1f9ff)
|
|
50
|
-
return 2; // supplemental emoji
|
|
51
|
-
if (cp >= 0x20000 && cp <= 0x3fffd)
|
|
52
|
-
return 2; // CJK ext B..G
|
|
53
|
-
return 1;
|
|
54
|
-
}
|
|
55
|
-
/** Display width of a code-point array (cursor math, scrolling). */
|
|
56
|
-
export function widthOf(chars) {
|
|
57
|
-
let w = 0;
|
|
58
|
-
for (const cp of chars)
|
|
59
|
-
w += charWidth(cp);
|
|
60
|
-
return w;
|
|
61
|
-
}
|
|
62
|
-
/** Display width of a string. */
|
|
63
|
-
export function displayWidth(text) {
|
|
64
|
-
let w = 0;
|
|
65
|
-
for (const ch of text)
|
|
66
|
-
w += charWidth(ch.codePointAt(0));
|
|
67
|
-
return w;
|
|
68
|
-
}
|
|
19
|
+
import { charWidth, displayWidth, widthOf } from "./width.js";
|
|
20
|
+
// the width primitives moved to width.ts (W1, the single width
|
|
21
|
+
// authority) — re-exported so the editor's public surface is unchanged.
|
|
22
|
+
export { charWidth, displayWidth, widthOf };
|
|
69
23
|
import { palette } from "./render.js";
|
|
70
24
|
// TUI v4 #16d: the input row is the blue brick + the edit area — the
|
|
71
25
|
// "you>" text is gone (the brick IS the prompt; the pipe path's readline
|
|
@@ -98,7 +52,10 @@ export class Editor {
|
|
|
98
52
|
#pendingLines = []; // submits before onLine is wired (startup) — never dropped
|
|
99
53
|
#sigintCb = null;
|
|
100
54
|
#eotCb = null;
|
|
101
|
-
|
|
55
|
+
// W18: the escape LIST — the run-abort (chat) and the /compact cancel
|
|
56
|
+
// (dispatch) coexist; a listener removes itself via an unarmed guard
|
|
57
|
+
// (the compact's handler no-ops after its abort has fired).
|
|
58
|
+
#escapeCbs = [];
|
|
102
59
|
#onRender;
|
|
103
60
|
#menuOpen = false; // v3 §04: the slash-command menu
|
|
104
61
|
#menuSel = 0;
|
|
@@ -136,7 +93,7 @@ export class Editor {
|
|
|
136
93
|
this.#eotCb = cb;
|
|
137
94
|
}
|
|
138
95
|
onEscape(cb) {
|
|
139
|
-
this.#
|
|
96
|
+
this.#escapeCbs.push(cb);
|
|
140
97
|
}
|
|
141
98
|
/** The whole buffer as text (the CLI's line()/clearLine()). */
|
|
142
99
|
line() {
|
|
@@ -253,7 +210,8 @@ export class Editor {
|
|
|
253
210
|
this.#onRender();
|
|
254
211
|
}
|
|
255
212
|
else {
|
|
256
|
-
this.#
|
|
213
|
+
for (const cb of [...this.#escapeCbs])
|
|
214
|
+
cb();
|
|
257
215
|
i += 1;
|
|
258
216
|
}
|
|
259
217
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
export { Body, Dock, CURSOR_MARKER, type BodyOptions } from "./compositor.js";
|
|
9
9
|
export { Container, foldLine, visibleWidth, SPINNER, type Component, type FrameCtx } from "./components.js";
|
|
10
10
|
export { Editor, MENU_ITEMS, PROMPT, PROMPT_WIDTH, displayWidth, charWidth, widthOf, type MenuItem, } from "./editor.js";
|
|
11
|
-
export { bannerLines, COLOR_OFF, COLOR_ON, escapeTerminal, foldResult, foldThinking, kUnit, palette, renderEvent, renderRecap, renderSessionLine, renderStatusLine, renderTerminalGap, renderToolSummary, TAGLINE, truncateRow, type Palette, type PathResolver, type RecapStats, type RenderInput, type RenderResult, type RunUsage, } from "./render.js";
|
|
11
|
+
export { bannerLines, COLOR_OFF, COLOR_ON, escapeTerminal, foldResult, foldThinking, kUnit, palette, renderEvent, renderRecap, renderResumeList, renderSessionLine, renderStatusLine, relativeTime, renderTerminalGap, renderToolSummary, TAGLINE, truncateRow, type Palette, type PathResolver, type RecapStats, type ResumeMeta, type RenderInput, type RenderResult, type RunUsage, } from "./render.js";
|
|
12
12
|
export { editFileDiff, truncateDiff, writeFileDiff, type DiffLine, type DiffResult } from "./diff.js";
|
package/dist/index.js
CHANGED
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
export { Body, Dock, CURSOR_MARKER } from "./compositor.js";
|
|
9
9
|
export { Container, foldLine, visibleWidth, SPINNER } from "./components.js";
|
|
10
10
|
export { Editor, MENU_ITEMS, PROMPT, PROMPT_WIDTH, displayWidth, charWidth, widthOf, } from "./editor.js";
|
|
11
|
-
export { bannerLines, COLOR_OFF, COLOR_ON, escapeTerminal, foldResult, foldThinking, kUnit, palette, renderEvent, renderRecap, renderSessionLine, renderStatusLine, renderTerminalGap, renderToolSummary, TAGLINE, truncateRow, } from "./render.js";
|
|
11
|
+
export { bannerLines, COLOR_OFF, COLOR_ON, escapeTerminal, foldResult, foldThinking, kUnit, palette, renderEvent, renderRecap, renderResumeList, renderSessionLine, renderStatusLine, relativeTime, renderTerminalGap, renderToolSummary, TAGLINE, truncateRow, } from "./render.js";
|
|
12
12
|
export { editFileDiff, truncateDiff, writeFileDiff } from "./diff.js";
|
package/dist/render.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export interface Palette {
|
|
|
25
25
|
readonly red: string;
|
|
26
26
|
readonly green: string;
|
|
27
27
|
readonly code: string;
|
|
28
|
+
readonly rv: string;
|
|
29
|
+
readonly rvEnd: string;
|
|
28
30
|
readonly reset: string;
|
|
29
31
|
}
|
|
30
32
|
export declare const COLOR_ON: Palette;
|
|
@@ -212,11 +214,24 @@ export declare function renderTerminalGap(statusLine: string | null): string;
|
|
|
212
214
|
* Pure.
|
|
213
215
|
*/
|
|
214
216
|
export declare const TAGLINE = "the coding agent that survives kill -9";
|
|
215
|
-
/** v3 §01: truncate a row at `width`, marking the hidden span
|
|
217
|
+
/** v3 §01 (W1): truncate a row at `width`, marking the hidden span
|
|
218
|
+
* " (+N)". W1: the width math is the charWidth authority (the banner's
|
|
219
|
+
* brick glyphs are 1 cell — the art's 38 columns clear 40), and the
|
|
220
|
+
* marker's own cells are part of the row — the visible cut leaves room
|
|
221
|
+
* for it, so a truncated row never exceeds W (a cut row carries the
|
|
222
|
+
* marker INSIDE the width; a row that fits is returned untouched). */
|
|
216
223
|
export declare function truncateRow(row: string, width: number): string;
|
|
217
|
-
/** v3 §01 (V6-2): the banner lines for a width W
|
|
218
|
-
*
|
|
219
|
-
|
|
224
|
+
/** v3 §01 (V6-2) + W1: the banner lines for a width W and height H —
|
|
225
|
+
* the tier table (extends the existing "under 40 columns, skip the
|
|
226
|
+
* logo" rule with a HEIGHT input):
|
|
227
|
+
* W ≥ 40 and H ≥ 20 → BIG (the 36x6 wordmark, 2-column indent)
|
|
228
|
+
* W ≥ 40 and 14–19 rows → COMPACT (v6's LOGO_ROWS, byte-identical)
|
|
229
|
+
* anything smaller → text rows only
|
|
230
|
+
* then the blank, then "vX — tagline" — the art IS the wordmark, so the
|
|
231
|
+
* text row does not repeat the name — then extensions — then the W5
|
|
232
|
+
* resume list (BIG only, W5). Every row truncates at the terminal width
|
|
233
|
+
* with a " (+N)" marker. Pure. */
|
|
234
|
+
export declare function bannerLines(W: number, H: number, version: string, extensionsText: string, resume?: readonly ResumeMeta[], now?: number): string[];
|
|
220
235
|
/** v3 §02 — the recap line that ends a run, replacing the "done" label +
|
|
221
236
|
* the old status line. All fields derive LOCALLY from the event stream
|
|
222
237
|
* (zero tokens): wall seconds, tool counts, usage, cache hit %, ctx left.
|
|
@@ -237,3 +252,19 @@ export declare function renderSessionLine(meta: {
|
|
|
237
252
|
runs: number;
|
|
238
253
|
updatedAt: number;
|
|
239
254
|
}): string;
|
|
255
|
+
/** W5 — the opening-screen resume list. Every field already exists
|
|
256
|
+
* behind renderSessionLine / `kiso sessions`: the relative time, the
|
|
257
|
+
* title, then the right-aligned "N events · M runs". The columns are
|
|
258
|
+
* fixed per W: 4 indent + 7 when + 1 + the title (the ONLY flexible
|
|
259
|
+
* field — cut with the ellipsis marker INSIDE the width) + 1 + the meta
|
|
260
|
+
* (padStart to metaW). The done-when: the meta's right edge lands at
|
|
261
|
+
* exactly W on every row. Returns PLAIN rows — the banner's uniform dim
|
|
262
|
+
* wrap styles them (no dim+bold SGR composition). */
|
|
263
|
+
export interface ResumeMeta {
|
|
264
|
+
readonly title: string;
|
|
265
|
+
readonly events: number;
|
|
266
|
+
readonly runs: number;
|
|
267
|
+
readonly updatedAt: number;
|
|
268
|
+
}
|
|
269
|
+
export declare function relativeTime(updatedAt: number, now: number): string;
|
|
270
|
+
export declare function renderResumeList(metas: readonly ResumeMeta[], W: number, now: number): string[];
|