@vincemakes/kiso-tui-cells 0.21.1 → 0.22.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/components.d.ts +2 -2
- package/dist/components.js +224 -47
- package/package.json +1 -1
package/dist/components.d.ts
CHANGED
|
@@ -224,8 +224,8 @@ declare const TOOL_SUMMARY_MAX = 60;
|
|
|
224
224
|
* compositor stays a pure writer. */
|
|
225
225
|
export declare function cellComponent(cell: BodyCell): Component;
|
|
226
226
|
/** W22: the pending-queue chips — queued user lines pre-render above
|
|
227
|
-
* the input row as the SAME UserMessage chip (undimmed:
|
|
228
|
-
*
|
|
227
|
+
* the input row as the SAME UserMessage chip (undimmed: reverse video
|
|
228
|
+
* inverts the CURRENT colours, so a dim span would invert into a
|
|
229
229
|
* dimmed block), the dim `□` gutter marking the queued
|
|
230
230
|
* state (the gutter rides EVERY row — the gutterFold precedent: the
|
|
231
231
|
* left edge alone distinguishes the states). Each chip folds at W−3
|
package/dist/components.js
CHANGED
|
@@ -22,7 +22,7 @@ import { displayWidth, visibleWidth } from "./width.js";
|
|
|
22
22
|
// KEY_BINDINGS). strings.js imports only render/width here, so this edge
|
|
23
23
|
// adds no cycle.
|
|
24
24
|
import { displayVerb } from "./strings.js";
|
|
25
|
-
import { bannerLines, breathFrame, escapeTerminal, foldThinking, foldResult, renderTerminalGap, renderToolSummary, toolTarget, kUnit, palette, } from "./render.js";
|
|
25
|
+
import { bannerLines, breathFrame, escapeTerminal, foldThinking, foldResult, renderTerminalGap, renderToolSummary, toolTarget, kUnit, palette, currentGround, } from "./render.js";
|
|
26
26
|
// TUI2-MD: the markdown renderer's surface reaches the tui through this
|
|
27
27
|
// module (the tui's components shim re-exports it) — one import edge,
|
|
28
28
|
// and it points one way: md.ts measures with the width authority, never
|
|
@@ -186,19 +186,22 @@ export function cellComponent(cell) {
|
|
|
186
186
|
* The padding is by cells (charWidth is the width authority), so a CJK
|
|
187
187
|
* row pads by width, never by chars, and the chip never overruns.
|
|
188
188
|
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
189
|
+
* THE SURFACE IS REVERSE VIDEO, on every ground (owner ruling
|
|
190
|
+
* 2026-09-02, reversing R9 P1 one release after it shipped).
|
|
191
|
+
*
|
|
192
|
+
* R9 P1 moved the chip onto the wash, reading §1.6's "verbatim" as one
|
|
193
|
+
* surface shared by the human's words and the machine's. §1.6 now
|
|
194
|
+
* splits the two, and the split is the reason: reverse video is THE
|
|
195
|
+
* HUMAN'S surface and it is full contrast by construction — it inverts
|
|
196
|
+
* whatever the terminal is, so it is the same weight on every ground
|
|
197
|
+
* and cannot be under-read. The wash is the MACHINE'S verbatim surface
|
|
198
|
+
* (inline code, tool output), where a lighter ground is right because
|
|
199
|
+
* those rows are read as content rather than as an utterance.
|
|
200
|
+
*
|
|
201
|
+
* SGR 7 closed with SGR 27 — never SGR 0, the chip composes with a
|
|
202
|
+
* surrounding span. NEVER dim inside it: reverse video inverts the
|
|
203
|
+
* CURRENT colours, so dimmed text inverts into a dimmed block with no
|
|
204
|
+
* contrast.
|
|
202
205
|
*/
|
|
203
206
|
/**
|
|
204
207
|
* REL-0152-D13 — how much of a turn the chip shows.
|
|
@@ -261,7 +264,7 @@ class UserMessage {
|
|
|
261
264
|
// row is two cells per character and pads by cells.
|
|
262
265
|
const inner = chipW;
|
|
263
266
|
for (const row of content) {
|
|
264
|
-
rows.push(`${p.
|
|
267
|
+
rows.push(`${p.rv} ${row}${" ".repeat(Math.max(0, inner - displayWidth(row)))} ${p.rvEnd}`);
|
|
265
268
|
}
|
|
266
269
|
if (!truncated)
|
|
267
270
|
return rows;
|
|
@@ -278,8 +281,8 @@ class UserMessage {
|
|
|
278
281
|
}
|
|
279
282
|
}
|
|
280
283
|
/** W22: the pending-queue chips — queued user lines pre-render above
|
|
281
|
-
* the input row as the SAME UserMessage chip (undimmed:
|
|
282
|
-
*
|
|
284
|
+
* the input row as the SAME UserMessage chip (undimmed: reverse video
|
|
285
|
+
* inverts the CURRENT colours, so a dim span would invert into a
|
|
283
286
|
* dimmed block), the dim `□` gutter marking the queued
|
|
284
287
|
* state (the gutter rides EVERY row — the gutterFold precedent: the
|
|
285
288
|
* left edge alone distinguishes the states). Each chip folds at W−3
|
|
@@ -729,9 +732,57 @@ class ToolExecution {
|
|
|
729
732
|
// metadata, in words, which is also the only form that survives
|
|
730
733
|
// a pipe with the colour stripped. A failure keeps its colour
|
|
731
734
|
// AND its words — see settledMeta.
|
|
735
|
+
const parts = toolBlockParts(c, W);
|
|
736
|
+
const body = parts.rows;
|
|
737
|
+
if (body.length > 0 && c.expanded) {
|
|
738
|
+
// An EXPANDED block is already showing everything, and its own
|
|
739
|
+
// footer ("ctrl+o collapses") is what closes it. Giving it an
|
|
740
|
+
// outcome row as well would put two closing rows on one block
|
|
741
|
+
// and move the metadata off a head row every width gate pins.
|
|
742
|
+
// It takes the SURFACE and nothing else.
|
|
743
|
+
const head = c.isError ? ` ${p.red}${text}${p.reset}` : ` ${text}`;
|
|
744
|
+
return slabBlock(appendSuffix(head, expandSuffix(hidden, W - visibleWidth(head))), body, null, W);
|
|
745
|
+
}
|
|
746
|
+
if (parts.output > 0) {
|
|
747
|
+
// R9 P2 — THE SLAB'S SHAPE. A call with rows on screen is one
|
|
748
|
+
// object: the head row names it, the output sits inside, and
|
|
749
|
+
// the outcome CLOSES it on its own line (§7.5's words, moved
|
|
750
|
+
// off the head row because the head row is no longer the only
|
|
751
|
+
// row). D6: the target is bold there — the head row's job is
|
|
752
|
+
// to say WHAT was run, and the metadata has its own row now.
|
|
753
|
+
//
|
|
754
|
+
// A failure takes NO tint on the head row (R9): only the
|
|
755
|
+
// outcome word is coloured, which is §1.2 exactly — the
|
|
756
|
+
// colour rides the fact, not the object that carries it.
|
|
757
|
+
const target = escapeTerminal(toolTargetOf(c));
|
|
758
|
+
const head = cutLine(` ${verbCol} ${p.bold}${target}${p.reset}`, W);
|
|
759
|
+
// §7.5's outcome, in words, on its own row: what happened, how
|
|
760
|
+
// much of it there was, how long it took. The count is stated
|
|
761
|
+
// exactly ONCE (VD-6) — a meta that already counts lines does
|
|
762
|
+
// not get a second count beside it.
|
|
763
|
+
const n = countLines(c.resultText);
|
|
764
|
+
const counted = n > 0 && !/^\d+( of \d+)? lines?$/.test(rawMeta) ? `${n} line${n === 1 ? "" : "s"}` : "";
|
|
765
|
+
// pin 4's order, on the row that carries the core now: the
|
|
766
|
+
// ATTRIBUTION drops first, then the count, and the core —
|
|
767
|
+
// what happened and how long it took — is never cut open.
|
|
768
|
+
const join = (...xs) => xs.filter((x) => x !== "").join(" · ");
|
|
769
|
+
const attr = approvedBy.replace(/^ · /, "");
|
|
770
|
+
const words = pickTier([join(meta, counted, `${elapsed}s`, attr), join(meta, counted, `${elapsed}s`), join(meta, `${elapsed}s`), meta], W - visibleWidth(NOTE_ROW));
|
|
771
|
+
const outcome = c.isError ? `${p.red}${words}${p.reset}` : words;
|
|
772
|
+
return slabBlock(head, body, outcome, W);
|
|
773
|
+
}
|
|
774
|
+
// No output on screen: the row is PLAIN, and the outcome stays on
|
|
775
|
+
// it in the form every width gate already pins.
|
|
776
|
+
//
|
|
777
|
+
// Owner ruling 2026-09-02, narrowing R9's "one-row slab": §1.6
|
|
778
|
+
// gives the wash to the machine's VERBATIM text, and a row like
|
|
779
|
+
// `read loop.ts · 412 lines · 0.1s` is kiso's summary of a result
|
|
780
|
+
// — not one line of it. A surface with nothing verbatim on it is
|
|
781
|
+
// a surface making a claim it cannot keep, so the wash appears
|
|
782
|
+
// only where the call's own output does.
|
|
732
783
|
const out = c.isError ? [` ${p.red}${text}${p.reset}`] : [` ${text}`];
|
|
733
784
|
out[0] = appendSuffix(out[0], expandSuffix(hidden, W - visibleWidth(out[0])));
|
|
734
|
-
out.push(...
|
|
785
|
+
out.push(...body);
|
|
735
786
|
return out;
|
|
736
787
|
}
|
|
737
788
|
if (c.state === "approval") {
|
|
@@ -797,12 +848,12 @@ function hiddenLines(c, W) {
|
|
|
797
848
|
return null;
|
|
798
849
|
if (c.isError)
|
|
799
850
|
return null; // errorBody's own cut row is the affordance there
|
|
800
|
-
//
|
|
801
|
-
//
|
|
802
|
-
//
|
|
803
|
-
//
|
|
804
|
-
|
|
805
|
-
|
|
851
|
+
// R9 P2 / D4: a settled shell has its tail back on screen, and when
|
|
852
|
+
// the tail is cut the slab's own note row says so and names the key.
|
|
853
|
+
// A head-row suffix as well would be TUI2-R1's two affordances for
|
|
854
|
+
// one cell — the thing that rule exists to forbid.
|
|
855
|
+
if (c.name === "shell")
|
|
856
|
+
return null;
|
|
806
857
|
return n; // every other settled call renders NO body — all of it is behind the key
|
|
807
858
|
}
|
|
808
859
|
/**
|
|
@@ -1557,6 +1608,93 @@ const CAP_ERROR = 3; // the error text head
|
|
|
1557
1608
|
const BODY_ROW = " ";
|
|
1558
1609
|
const NOTE_ROW = " ";
|
|
1559
1610
|
const CUT_ROW = "└ ";
|
|
1611
|
+
/**
|
|
1612
|
+
* R9 P2 — THE SLAB: a single call's block is one washed object.
|
|
1613
|
+
*
|
|
1614
|
+
* §1.6 gives the wash to the machine's verbatim text, and a call's own
|
|
1615
|
+
* output is exactly that. The slab is the surface that says so: full-
|
|
1616
|
+
* width washed rows, the head row naming the call, the output inside,
|
|
1617
|
+
* the outcome closing it. `└` does not open a slab — the surface is the
|
|
1618
|
+
* container, and a corner inside it is §1.3's empty mark one scale up.
|
|
1619
|
+
*
|
|
1620
|
+
* THE DEGRADATION IS THE POINT OF THE PREDICATE. `wash` is a chosen
|
|
1621
|
+
* background on the two KNOWN grounds and reverse video on the third
|
|
1622
|
+
* (§3 rung 4). A chip inverting for one row is the design working; eight
|
|
1623
|
+
* output rows inverting is a blackboard in the middle of the transcript.
|
|
1624
|
+
* So a slab paints only where the wash is a real background, and where
|
|
1625
|
+
* it is not the block degrades to what it has always been — the R8a
|
|
1626
|
+
* four-column indent with a dim tail. Never to reverse video.
|
|
1627
|
+
*
|
|
1628
|
+
* The content shape does NOT change with the surface: the note row and
|
|
1629
|
+
* the outcome row exist either way, in `washDim` on the slab and in the
|
|
1630
|
+
* ordinary dim off it. Only the surface and its two blank rows are
|
|
1631
|
+
* contingent, because an unpainted blank row is §1.3's empty mark at the
|
|
1632
|
+
* scale of a row.
|
|
1633
|
+
*/
|
|
1634
|
+
function slabPaints() {
|
|
1635
|
+
const p = palette();
|
|
1636
|
+
return p.wash !== "" && currentGround() !== "unknown";
|
|
1637
|
+
}
|
|
1638
|
+
/** One washed row, padded to the full width by DISPLAY width. A reset
|
|
1639
|
+
* inside the content would strand the background for the rest of the
|
|
1640
|
+
* row, so every reset re-opens it — the selection bar's discipline,
|
|
1641
|
+
* applied to a surface that spans many rows instead of one. */
|
|
1642
|
+
function slabRow(inner, W) {
|
|
1643
|
+
const p = palette();
|
|
1644
|
+
const body = inner.replaceAll(p.reset, `${p.reset}${p.wash}`);
|
|
1645
|
+
const pad = Math.max(0, W - visibleWidth(inner));
|
|
1646
|
+
return `${p.wash}${body}${" ".repeat(pad)}${p.washEnd}`;
|
|
1647
|
+
}
|
|
1648
|
+
/** A metadata row inside (or under) a block: `washDim` on the slab,
|
|
1649
|
+
* the ordinary dim off it. §2.1 is why there are two. */
|
|
1650
|
+
/** The widest form that fits the row, or the last one — the head row's
|
|
1651
|
+
* own discipline (TUI2-R1.5 ⑤, pin 4) applied to the slab's two
|
|
1652
|
+
* metadata rows: the parts give way in a PINNED ORDER, and the part
|
|
1653
|
+
* that carries the semantics is the one reserved. */
|
|
1654
|
+
function pickTier(tiers, room) {
|
|
1655
|
+
for (const t of tiers)
|
|
1656
|
+
if (visibleWidth(t) <= room)
|
|
1657
|
+
return t;
|
|
1658
|
+
return tiers[tiers.length - 1];
|
|
1659
|
+
}
|
|
1660
|
+
function noteRow(text, W, tone) {
|
|
1661
|
+
const p = palette();
|
|
1662
|
+
// CUT, never folded — A6's rule for the tool header, and for the same
|
|
1663
|
+
// reason: a metadata row that wraps costs the block a row it did not
|
|
1664
|
+
// budget, and at 30 columns the fold put "expands" alone on a line of
|
|
1665
|
+
// its own. The row names a fact; a cut names it shorter.
|
|
1666
|
+
const open = tone === "body" ? p.washDim : p.dim;
|
|
1667
|
+
const close = tone === "body" ? p.washDimEnd : p.reset;
|
|
1668
|
+
return [cutLine(`${open}${NOTE_ROW}${text}${close}`, W)];
|
|
1669
|
+
}
|
|
1670
|
+
/**
|
|
1671
|
+
* Assemble a call's block.
|
|
1672
|
+
*
|
|
1673
|
+
* `head` is the row that names the call, `body` its rows (already
|
|
1674
|
+
* indented and toned), `outcome` the closing line in words (§7.5) or
|
|
1675
|
+
* null for a call whose head row already carries it — a read has no
|
|
1676
|
+
* body, so nothing needs closing and its outcome stays inline.
|
|
1677
|
+
*/
|
|
1678
|
+
function slabBlock(head, body, outcome, W) {
|
|
1679
|
+
if (!slabPaints()) {
|
|
1680
|
+
const out = [head, ...body];
|
|
1681
|
+
if (outcome !== null)
|
|
1682
|
+
out.push(...noteRow(outcome, W, "dim"));
|
|
1683
|
+
return out;
|
|
1684
|
+
}
|
|
1685
|
+
const rows = [slabRow(head, W)];
|
|
1686
|
+
if (body.length > 0)
|
|
1687
|
+
rows.push(slabRow("", W));
|
|
1688
|
+
for (const r of body)
|
|
1689
|
+
rows.push(slabRow(r, W));
|
|
1690
|
+
if (outcome !== null) {
|
|
1691
|
+
if (body.length > 0)
|
|
1692
|
+
rows.push(slabRow("", W));
|
|
1693
|
+
for (const r of noteRow(outcome, W, "body"))
|
|
1694
|
+
rows.push(slabRow(r, W));
|
|
1695
|
+
}
|
|
1696
|
+
return rows;
|
|
1697
|
+
}
|
|
1560
1698
|
/** R8a — stamp `└` on a block's FIRST row, after every slice and note
|
|
1561
1699
|
* has been assembled, so the mark is always on the first row actually
|
|
1562
1700
|
* emitted rather than on one a cap may have dropped. */
|
|
@@ -1578,12 +1716,15 @@ function openBlock(rows) {
|
|
|
1578
1716
|
}
|
|
1579
1717
|
const blockMemo = new WeakMap();
|
|
1580
1718
|
/** The block's body rows below the header (memoized, W9). */
|
|
1581
|
-
function
|
|
1719
|
+
function toolBlockParts(c, W) {
|
|
1582
1720
|
const memo = blockMemo.get(c);
|
|
1583
|
-
|
|
1721
|
+
// the SURFACE is part of the key: the same cell renders different rows
|
|
1722
|
+
// painted and unpainted, and a ground resolved after the first frame
|
|
1723
|
+
// would otherwise be served the pre-ground shape forever.
|
|
1724
|
+
const state = `${c.state}:${c.isError}:${c.name}:${c.expanded ? "x" : ""}:${slabPaints() ? "slab" : "flat"}`;
|
|
1584
1725
|
const content = c.state === "approval" ? (c.diff ?? null) : c.resultText;
|
|
1585
1726
|
if (memo !== undefined && memo.width === W && memo.state === state && memo.content === content)
|
|
1586
|
-
return memo
|
|
1727
|
+
return memo;
|
|
1587
1728
|
const p = palette();
|
|
1588
1729
|
const rows = c.expanded
|
|
1589
1730
|
? // W15: the toggle's full form — the WHOLE body, no cap, no
|
|
@@ -1602,15 +1743,19 @@ function toolBlockBody(c, W) {
|
|
|
1602
1743
|
? errorBody(c, W)
|
|
1603
1744
|
: c.name === "delegate"
|
|
1604
1745
|
? delegateSettled(c, W)
|
|
1605
|
-
: // TUI2-R1.5 ④(c) (VD-5)
|
|
1606
|
-
//
|
|
1607
|
-
//
|
|
1608
|
-
//
|
|
1609
|
-
//
|
|
1610
|
-
//
|
|
1611
|
-
//
|
|
1612
|
-
//
|
|
1613
|
-
|
|
1746
|
+
: // R9 P2 / D4 — DECLARED REVERSAL of TUI2-R1.5 ④(c) (VD-5),
|
|
1747
|
+
// owner-ruled. VD-5 collapsed a settled shell to its head
|
|
1748
|
+
// row because six ungrounded rows per call let three
|
|
1749
|
+
// shells own a screen. The slab is what changes that
|
|
1750
|
+
// arithmetic: the rows are inside a surface that says
|
|
1751
|
+
// where the call begins and ends, so five of them read as
|
|
1752
|
+
// one object rather than as five loose lines. The cap and
|
|
1753
|
+
// the tail direction are VD-5's own (CAP_SHELL_SETTLED,
|
|
1754
|
+
// the conclusion at the end); only the emptiness is
|
|
1755
|
+
// reversed.
|
|
1756
|
+
c.name === "shell"
|
|
1757
|
+
? shellTail(c.resultText, W, slabPaints() ? "body" : "dim")
|
|
1758
|
+
: []
|
|
1614
1759
|
: c.state === "running"
|
|
1615
1760
|
? c.name === "delegate"
|
|
1616
1761
|
? delegateRunning(c, W)
|
|
@@ -1620,6 +1765,11 @@ function toolBlockBody(c, W) {
|
|
|
1620
1765
|
: c.state === "approval"
|
|
1621
1766
|
? diffBody(c.diff, W)
|
|
1622
1767
|
: [];
|
|
1768
|
+
// R9 P2: how many of these rows are the call's OWN OUTPUT. A cut note
|
|
1769
|
+
// is kiso's sentence about a result the TOOL truncated, not a line of
|
|
1770
|
+
// it — a read that has only that note has nothing verbatim on screen
|
|
1771
|
+
// and stays a ONE-ROW slab, outcome inline, exactly as R9 draws it.
|
|
1772
|
+
const output = rows.length;
|
|
1623
1773
|
const note = c.expanded ? null : toolCutNote(c.name, c.resultText);
|
|
1624
1774
|
if (note !== null)
|
|
1625
1775
|
rows.push(...foldLine(`${p.dim}${NOTE_ROW}${note}${p.reset}`, W));
|
|
@@ -1629,20 +1779,36 @@ function toolBlockBody(c, W) {
|
|
|
1629
1779
|
// row would be an affordance for an empty block.
|
|
1630
1780
|
if (c.expanded && rows.length > 0)
|
|
1631
1781
|
rows.push(...foldLine(`${p.dim}${NOTE_ROW}${COLLAPSE_ROW}${p.reset}`, W));
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1782
|
+
// R9 P2: `└` opens a block that has no surface. Inside a slab the
|
|
1783
|
+
// surface IS the container, and a corner in it is §1.3's empty mark
|
|
1784
|
+
// one scale up — so the corner and the slab are alternatives, never
|
|
1785
|
+
// both.
|
|
1786
|
+
const opened = slabPaints() ? rows : openBlock(rows);
|
|
1787
|
+
const parts = { width: W, state, content, rows: opened, output };
|
|
1788
|
+
blockMemo.set(c, parts);
|
|
1789
|
+
return parts;
|
|
1790
|
+
}
|
|
1791
|
+
/** The rows alone — every caller but the settled branch, which needs to
|
|
1792
|
+
* know whether any of them are the call's own OUTPUT. */
|
|
1793
|
+
function toolBlockBody(c, W) {
|
|
1794
|
+
return toolBlockParts(c, W).rows;
|
|
1635
1795
|
}
|
|
1636
1796
|
/** Fold result text into dim body rows (the BODY_ROW prefix): escape,
|
|
1637
1797
|
* split, fold each line at W−prefix; trailing empty rows (the result's
|
|
1638
1798
|
* final newline) drop. */
|
|
1639
|
-
function blockRows(text, W) {
|
|
1799
|
+
function blockRows(text, W, tone = "dim") {
|
|
1640
1800
|
const p = palette();
|
|
1641
1801
|
const textW = Math.max(1, W - visibleWidth(BODY_ROW));
|
|
1642
1802
|
const rows = [];
|
|
1803
|
+
// R9 P2: inside a SLAB the output rows are body strength, never dim —
|
|
1804
|
+
// §2.1 bars dim from the wash (3.91:1 light, 4.35:1 dark) and these
|
|
1805
|
+
// rows are the verbatim content the surface exists for. The metadata
|
|
1806
|
+
// rows around them take `washDim`, which was chosen for that ground.
|
|
1807
|
+
const open = tone === "dim" ? p.dim : "";
|
|
1808
|
+
const close = tone === "dim" ? p.reset : "";
|
|
1643
1809
|
for (const raw of escapeTerminal(text).split("\n")) {
|
|
1644
1810
|
for (const row of foldLine(raw, textW))
|
|
1645
|
-
rows.push(`${
|
|
1811
|
+
rows.push(`${open}${BODY_ROW}${row}${close}`);
|
|
1646
1812
|
}
|
|
1647
1813
|
while (rows.length > 0 && visibleWidth(rows[rows.length - 1]) === visibleWidth(BODY_ROW))
|
|
1648
1814
|
rows.pop();
|
|
@@ -1652,14 +1818,25 @@ function blockRows(text, W) {
|
|
|
1652
1818
|
* renderer cut at the block's bottom ("earlier rows" — the conclusion
|
|
1653
1819
|
* is at the end, the reference implementation's truncateToVisualLines
|
|
1654
1820
|
* direction). */
|
|
1655
|
-
function shellTail(text, W) {
|
|
1656
|
-
const
|
|
1657
|
-
const rows = blockRows(text, W);
|
|
1821
|
+
function shellTail(text, W, tone = "dim") {
|
|
1822
|
+
const rows = blockRows(text, W, tone);
|
|
1658
1823
|
if (rows.length <= CAP_SHELL_SETTLED)
|
|
1659
1824
|
return rows;
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1825
|
+
// R9 P2 / D4: FIVE output rows, and the note is a row of its own. The
|
|
1826
|
+
// pre-slab arithmetic spent one of the five on the cut note, because
|
|
1827
|
+
// the note had nowhere else to live; the slab's metadata rows are not
|
|
1828
|
+
// output and are not counted against the output's cap.
|
|
1829
|
+
const kept = CAP_SHELL_SETTLED;
|
|
1830
|
+
// The note goes ABOVE the tail: it says what was cut, and what was
|
|
1831
|
+
// cut is what came BEFORE these rows. One position on both surfaces —
|
|
1832
|
+
// the surface degrades, the content shape does not.
|
|
1833
|
+
const cut = rows.length - kept;
|
|
1834
|
+
const n = `${cut} earlier line${cut === 1 ? "" : "s"}`;
|
|
1835
|
+
// the KEY is reserved (TUI2-R1.5 ⑤): the count gives way before it,
|
|
1836
|
+
// because a row that says how much is hidden without saying how to
|
|
1837
|
+
// see it is the silence TUI2-R1 (A) set out to remove.
|
|
1838
|
+
const note = noteRow(pickTier([`… ${n} · ctrl+o expands`, `… ${n} · ctrl+o`, `… ${cut} · ctrl+o`, "· ctrl+o"], W - visibleWidth(NOTE_ROW)), W, tone);
|
|
1839
|
+
return [...note, ...rows.slice(rows.length - kept)];
|
|
1663
1840
|
}
|
|
1664
1841
|
/** The error text head: the FIRST rows, capped at 3 — the answer is at
|
|
1665
1842
|
* the start (opencode's collapseToolOutput direction). The header row
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-tui-cells",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "kiso tui-cells — the components cell renderer (components, diff, width, the render slice). Zero runtime dependencies: input is data, output is bytes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|