@vincemakes/kiso-tui 0.10.0 → 0.11.0
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/compositor.js +90 -17
- package/package.json +2 -2
package/dist/compositor.js
CHANGED
|
@@ -51,7 +51,7 @@ import { panelAffordanceOf, panelLeadOf, panelRowsOf, panelStatusOf } from "./as
|
|
|
51
51
|
import { atPanelRows, bandHeader } from "./at-picker.js";
|
|
52
52
|
// TUI2-R2 ②: the session picker's rows — the band's third occupant.
|
|
53
53
|
import { sessionPickerRows } from "./session-picker.js";
|
|
54
|
-
import { Container, ROLLUP_NOUN, SPINNER, bodySpacing, boxBottom, boxTop, cellComponent, exploreCounts, focusToken, exploreRows, foldLine, isExploreTool, pendingQueueRows, statusLine, turnFold, visibleWidth, } from "./components.js";
|
|
54
|
+
import { Container, ROLLUP_NOUN, SPINNER, MdStream, bodySpacing, boxBottom, boxTop, cellComponent, exploreCounts, focusToken, exploreRows, foldLine, isExploreTool, pendingQueueRows, statusLine, turnFold, visibleWidth, } from "./components.js";
|
|
55
55
|
import { bannerLines, escapeTerminal, foldResult, foldThinking, palette, renderTerminalGap, renderToolSummary, toolTarget } from "./render.js";
|
|
56
56
|
import { displayVerb, keysSheetRows } from "./strings.js";
|
|
57
57
|
/** The cursor marker — an APC private sequence the focus component
|
|
@@ -96,6 +96,11 @@ export class Body {
|
|
|
96
96
|
#lastTool = null;
|
|
97
97
|
#pendingCalls = new Map();
|
|
98
98
|
#pipeBuf = ""; // the passthrough's thinking buffer
|
|
99
|
+
/** TUI2-MD ⑤ — the markdown scanner of the message currently
|
|
100
|
+
* streaming, and the cell index its first block landed at. Null
|
|
101
|
+
* between messages: the scanner's life is one assistant message. */
|
|
102
|
+
#md = null;
|
|
103
|
+
#mdBase = 0;
|
|
99
104
|
#toolCells = new Map(); // callId → cell index (parallel tools)
|
|
100
105
|
// W15: the collapsed (cut) tool cells — committed cells whose last
|
|
101
106
|
// rendered row carried the "ctrl+r" affordance; the expand key's
|
|
@@ -398,22 +403,71 @@ export class Body {
|
|
|
398
403
|
const turn = this.#turns[this.#turns.length - 1];
|
|
399
404
|
if (turn !== undefined)
|
|
400
405
|
turn.hasText = true;
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
+
// TUI2-MD ⑤: assistant body text is MARKDOWN, scanned as it
|
|
407
|
+
// streams. The scanner yields CLOSED blocks (final source, final
|
|
408
|
+
// render) and one OPEN tail block; each becomes a cell, and the
|
|
409
|
+
// cell is the commit unit the compositor already had — so
|
|
410
|
+
// block-freeze needs no new commit machinery at all. A closed
|
|
411
|
+
// block is a DONE cell the natural loop freezes; the tail is the
|
|
412
|
+
// one cell left live, repainting in place.
|
|
413
|
+
if (this.#md === null) {
|
|
406
414
|
this.#closeOpenThinking();
|
|
407
415
|
this.#closeOpenText();
|
|
408
|
-
this.#
|
|
416
|
+
this.#md = new MdStream();
|
|
417
|
+
this.#mdBase = this.#cells.length;
|
|
409
418
|
}
|
|
419
|
+
this.#md.push(text);
|
|
420
|
+
this.#syncMd();
|
|
410
421
|
this.#mark();
|
|
411
422
|
}
|
|
423
|
+
/** TUI2-MD ⑤ — mirror the scanner's blocks onto cells. Append-only by
|
|
424
|
+
* construction: a block that has closed never changes, so a cell that
|
|
425
|
+
* is done is never touched again (and a committed one could not be).
|
|
426
|
+
* Only the trailing tail cell is rewritten per delta. */
|
|
427
|
+
#syncMd() {
|
|
428
|
+
if (this.#md === null)
|
|
429
|
+
return;
|
|
430
|
+
const blocks = this.#md.blocks();
|
|
431
|
+
const closed = this.#md.closed();
|
|
432
|
+
for (let i = 0; i < blocks.length; i += 1) {
|
|
433
|
+
const at = this.#mdBase + i;
|
|
434
|
+
const cell = this.#cells[at];
|
|
435
|
+
if (cell === undefined) {
|
|
436
|
+
this.#cells.push({ kind: "md", block: blocks[i], done: i < closed });
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
if (cell.kind !== "md" || cell.done)
|
|
440
|
+
continue;
|
|
441
|
+
cell.block = blocks[i];
|
|
442
|
+
cell.done = i < closed;
|
|
443
|
+
}
|
|
444
|
+
// the tail can vanish (a lone whitespace delta that turns out to be
|
|
445
|
+
// a blank line). Drop it only where dropping is safe: never below
|
|
446
|
+
// the commit frontier, where the bytes are already the terminal's.
|
|
447
|
+
const want = this.#mdBase + blocks.length;
|
|
448
|
+
while (this.#cells.length > Math.max(want, this.#committed) && this.#cells[this.#cells.length - 1].kind === "md")
|
|
449
|
+
this.#cells.pop();
|
|
450
|
+
}
|
|
451
|
+
/** TUI2-MD ⑤ — the message ends: the tail block closes and every md
|
|
452
|
+
* cell of this message is final. */
|
|
453
|
+
#endMd() {
|
|
454
|
+
if (this.#md === null)
|
|
455
|
+
return;
|
|
456
|
+
this.#md.end();
|
|
457
|
+
this.#syncMd();
|
|
458
|
+
for (let i = this.#mdBase; i < this.#cells.length; i += 1) {
|
|
459
|
+
const cell = this.#cells[i];
|
|
460
|
+
if (cell.kind === "md")
|
|
461
|
+
cell.done = true;
|
|
462
|
+
}
|
|
463
|
+
this.#md = null;
|
|
464
|
+
}
|
|
412
465
|
textEnd() {
|
|
413
466
|
if (!this.#isActive()) {
|
|
414
467
|
this.#write("\n");
|
|
415
468
|
return;
|
|
416
469
|
}
|
|
470
|
+
this.#endMd();
|
|
417
471
|
const last = this.#cells[this.#cells.length - 1];
|
|
418
472
|
if (last !== undefined && last.kind === "text" && !last.done)
|
|
419
473
|
last.done = true;
|
|
@@ -923,14 +977,13 @@ export class Body {
|
|
|
923
977
|
inputExtra +
|
|
924
978
|
queueRows.length);
|
|
925
979
|
}
|
|
926
|
-
const live = this.#cells.slice(this.#committed);
|
|
927
980
|
const ctx = { spinnerI: this.#spinnerI, now: Date.now(), height: this.#opts.height() };
|
|
928
981
|
const W = this.#opts.width();
|
|
929
982
|
let lines = 0;
|
|
930
983
|
let prev = this.#committed > 0 ? this.#lineCache[this.#committed - 1] : null;
|
|
931
|
-
for (
|
|
932
|
-
const rows = cellComponent(
|
|
933
|
-
lines +=
|
|
984
|
+
for (let i = this.#committed; i < this.#cells.length; i += 1) {
|
|
985
|
+
const rows = cellComponent(this.#cells[i]).render(W, ctx);
|
|
986
|
+
lines += this.#space(i, prev, rows).length;
|
|
934
987
|
prev = rows;
|
|
935
988
|
}
|
|
936
989
|
return lines + CHROME_ROWS + inputExtra + this.#menuRows(W).length + queueRows.length;
|
|
@@ -963,7 +1016,7 @@ export class Body {
|
|
|
963
1016
|
const cell = this.#cells[i];
|
|
964
1017
|
const lines = cellComponent(cell).render(W, ctx);
|
|
965
1018
|
this.#lineCache[i] = lines; // the cell's OWN rows — the cache stays raw
|
|
966
|
-
this.#committedLines +=
|
|
1019
|
+
this.#committedLines += this.#space(i, i > 0 ? this.#lineCache[i - 1] : null, lines).length;
|
|
967
1020
|
}
|
|
968
1021
|
}
|
|
969
1022
|
// 1. the natural commits — the leading DONE cells freeze: their
|
|
@@ -1042,7 +1095,7 @@ export class Body {
|
|
|
1042
1095
|
// nowhere else, which is what makes "exactly one" structural
|
|
1043
1096
|
if (i === focus && rows.length > 0)
|
|
1044
1097
|
rows[0] = focusToken(rows[0], W);
|
|
1045
|
-
liveLines.push(...
|
|
1098
|
+
liveLines.push(...this.#space(i, prev, rows));
|
|
1046
1099
|
prev = rows;
|
|
1047
1100
|
}
|
|
1048
1101
|
}
|
|
@@ -1063,7 +1116,7 @@ export class Body {
|
|
|
1063
1116
|
const rows = cellComponent(cell).render(W, ctx);
|
|
1064
1117
|
if (i === focus && rows.length > 0)
|
|
1065
1118
|
rows[0] = focusToken(rows[0], W);
|
|
1066
|
-
liveLines.push(...
|
|
1119
|
+
liveLines.push(...this.#space(i, prev, rows));
|
|
1067
1120
|
prev = rows;
|
|
1068
1121
|
}
|
|
1069
1122
|
}
|
|
@@ -1106,6 +1159,23 @@ export class Body {
|
|
|
1106
1159
|
// composer (N = 1 ⇒ H−2, the retired hard-coded anchor).
|
|
1107
1160
|
this.#lastAnchorRow = H - 1 - editor.rows.length + editor.markerRow;
|
|
1108
1161
|
}
|
|
1162
|
+
/** TUI2-MD ⑤ — the join blank between cell i−1 and cell i.
|
|
1163
|
+
*
|
|
1164
|
+
* W11's formula ("a blank above a row that is itself a block, or
|
|
1165
|
+
* whose previous sibling was taller than one row") reads ROW COUNTS,
|
|
1166
|
+
* and markdown's rhythm is not a row count: a heading wants a blank
|
|
1167
|
+
* above and below it even between two one-row paragraphs, and two
|
|
1168
|
+
* rows of one fence want none even when a long code line folds to
|
|
1169
|
+
* two. So between two MARKDOWN cells the formula steps aside and the
|
|
1170
|
+
* block's own `gap` decides — the renderer owns the rhythm, which is
|
|
1171
|
+
* the only place that knows it. Every other pair is untouched,
|
|
1172
|
+
* including the boundary INTO a markdown message (the blank under the
|
|
1173
|
+
* user chip is still W11's). */
|
|
1174
|
+
#space(i, prev, rows) {
|
|
1175
|
+
if (i > 0 && this.#cells[i]?.kind === "md" && this.#cells[i - 1]?.kind === "md")
|
|
1176
|
+
return rows;
|
|
1177
|
+
return bodySpacing(prev, rows);
|
|
1178
|
+
}
|
|
1109
1179
|
/** Commit the cell at index i: render + cache its lines (immutable —
|
|
1110
1180
|
* the force-committed form freezes at the current render), advance
|
|
1111
1181
|
* the bookkeeping — and collect the lines for this frame's writes.
|
|
@@ -1128,7 +1198,7 @@ export class Body {
|
|
|
1128
1198
|
if (cell.kind === "tool" && lines.some((l) => l.includes("ctrl+r")))
|
|
1129
1199
|
this.#collapsed.unshift(i);
|
|
1130
1200
|
this.#lineCache[i] = lines;
|
|
1131
|
-
const placed =
|
|
1201
|
+
const placed = this.#space(i, i > 0 ? this.#lineCache[i - 1] : null, lines);
|
|
1132
1202
|
this.#committed += 1;
|
|
1133
1203
|
this.#committedLines += placed.length;
|
|
1134
1204
|
this.#committedLinesThisFrame.push(...placed);
|
|
@@ -1544,7 +1614,7 @@ export class Body {
|
|
|
1544
1614
|
// cell (the V6-1 frozen-loop finding — the banner vanished).
|
|
1545
1615
|
const frozen = [];
|
|
1546
1616
|
for (let i = 0; i < this.#committedAtFrameStart; i += 1) {
|
|
1547
|
-
frozen.push(...
|
|
1617
|
+
frozen.push(...this.#space(i, i > 0 ? this.#lineCache[i - 1] : null, this.#lineCache[i]));
|
|
1548
1618
|
}
|
|
1549
1619
|
// A8: the march is the WINDOW — the model's last H rows. When the
|
|
1550
1620
|
// model total (committed + live + chrome) exceeds H, the window's
|
|
@@ -1884,8 +1954,11 @@ export class Body {
|
|
|
1884
1954
|
return i === undefined ? null : (this.#cells[i] ?? null);
|
|
1885
1955
|
}
|
|
1886
1956
|
/** Close an open TEXT cell when a new cell starts (see v2d — the
|
|
1887
|
-
* runtime emits no text_end; the next cell is the close signal).
|
|
1957
|
+
* runtime emits no text_end; the next cell is the close signal).
|
|
1958
|
+
* TUI2-MD ⑤: that same signal ends the markdown message — the open
|
|
1959
|
+
* tail block closes and its cell becomes commit-eligible. */
|
|
1888
1960
|
#closeOpenText() {
|
|
1961
|
+
this.#endMd();
|
|
1889
1962
|
const last = this.#cells[this.#cells.length - 1];
|
|
1890
1963
|
if (last !== undefined && last.kind === "text" && !last.done)
|
|
1891
1964
|
last.done = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-tui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
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",
|
|
@@ -35,6 +35,6 @@
|
|
|
35
35
|
},
|
|
36
36
|
"homepage": "https://github.com/vincemakes/kiso/tree/main/packages/tui#readme",
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@vincemakes/kiso-tui-cells": "0.
|
|
38
|
+
"@vincemakes/kiso-tui-cells": "0.11.0"
|
|
39
39
|
}
|
|
40
40
|
}
|