@vincemakes/kiso-tui-cells 0.20.0 → 0.20.2
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/approval-panel.d.ts +15 -3
- package/dist/approval-panel.js +30 -3
- package/dist/components.d.ts +15 -0
- package/dist/components.js +86 -16
- package/dist/strings.d.ts +7 -0
- package/dist/strings.js +30 -2
- package/package.json +1 -1
package/dist/approval-panel.d.ts
CHANGED
|
@@ -215,9 +215,13 @@ export interface PickOption {
|
|
|
215
215
|
export interface PickSpec {
|
|
216
216
|
readonly header: string;
|
|
217
217
|
readonly options: readonly PickOption[];
|
|
218
|
-
/** the `t` row
|
|
219
|
-
* list
|
|
220
|
-
|
|
218
|
+
/** the `t` row — typing directly. OPTIONAL, and its absence means the
|
|
219
|
+
* option list is the WHOLE world: `/model`'s profiles never are (a
|
|
220
|
+
* model that exists but is not configured has to be typeable), and
|
|
221
|
+
* `/mode`'s five tiers always are. Offering a `t` row over a closed
|
|
222
|
+
* set is a row that carries no fact — §1.3 — and it is what made
|
|
223
|
+
* the owner read the mode panel as "type the answer". */
|
|
224
|
+
readonly typeHint?: string;
|
|
221
225
|
/** shown INSTEAD of the options when there are none. The copy is the
|
|
222
226
|
* caller's and is reproduced verbatim. */
|
|
223
227
|
readonly emptyNote?: string;
|
|
@@ -432,4 +436,12 @@ export declare function pickAffordance(state: PickRuntime): string;
|
|
|
432
436
|
/** Compose a pick view. The flavor/name/title/args fields exist for the
|
|
433
437
|
* approval path and are given inert values here \u2014 the pick block
|
|
434
438
|
* reads none of them. */
|
|
439
|
+
/** DC-36 — the same shell for the MODE picker.
|
|
440
|
+
*
|
|
441
|
+
* `/model` learned to pick in TUI2-R2 ④; `/mode` never did, and the
|
|
442
|
+
* five tiers are a CLOSED set — the one case where making a human
|
|
443
|
+
* type the answer is least defensible. The pick block reads only
|
|
444
|
+
* `pick` and `statusText`, so a second flavour is a name and a
|
|
445
|
+
* fallback question, not a second mechanism. */
|
|
446
|
+
export declare function modePickView(spec: PickSpec, statusText: string): PanelView;
|
|
435
447
|
export declare function modelPickView(spec: PickSpec, statusText: string): PanelView;
|
package/dist/approval-panel.js
CHANGED
|
@@ -543,8 +543,10 @@ export function pickBlockRows(view, state, W, maxRows) {
|
|
|
543
543
|
}
|
|
544
544
|
}
|
|
545
545
|
const typing = state.phase === "custom";
|
|
546
|
-
|
|
547
|
-
|
|
546
|
+
if (spec.typeHint !== undefined) {
|
|
547
|
+
const tText = cutLine(`${typing ? p.bold : ""}${typing ? "\u2192" : " "} t ${p.reset}${p.dim}${escapeTerminal(spec.typeHint)}${p.reset}`, room);
|
|
548
|
+
rows.push(typing ? selectionBar(tText, visibleWidth(tText), W) : ` ${tText}`);
|
|
549
|
+
}
|
|
548
550
|
rows.push(` ${p.dim}${cutLine(pickAffordance(state), room)}${p.reset}`);
|
|
549
551
|
rows.push(`${p.dim}${"\u2500".repeat(Math.max(0, W))}${p.reset}`);
|
|
550
552
|
return rows;
|
|
@@ -571,11 +573,36 @@ export function pickStatus(view) {
|
|
|
571
573
|
return view.statusText;
|
|
572
574
|
}
|
|
573
575
|
export function pickAffordance(state) {
|
|
574
|
-
|
|
576
|
+
// DC-36 — the row NAMES the arrows. TUI2-R2 ④ bound ↑↓ to the pick's
|
|
577
|
+
// cursor and the keys sheet has said `panels: ↑↓ move` ever since,
|
|
578
|
+
// but this row — the one a human is actually looking at while the
|
|
579
|
+
// panel is up — advertised only the digits. The owner read it as
|
|
580
|
+
// "type the answer", which is the same lesson DC-30 filed: a hint
|
|
581
|
+
// that omits the gesture is why the gesture goes unused.
|
|
582
|
+
return state.phase === "custom" ? "enter commits \u00b7 esc backs out" : "\u2191\u2193 move \u00b7 digits pick \u00b7 \u23ce confirms \u00b7 esc";
|
|
575
583
|
}
|
|
576
584
|
/** Compose a pick view. The flavor/name/title/args fields exist for the
|
|
577
585
|
* approval path and are given inert values here \u2014 the pick block
|
|
578
586
|
* reads none of them. */
|
|
587
|
+
/** DC-36 — the same shell for the MODE picker.
|
|
588
|
+
*
|
|
589
|
+
* `/model` learned to pick in TUI2-R2 ④; `/mode` never did, and the
|
|
590
|
+
* five tiers are a CLOSED set — the one case where making a human
|
|
591
|
+
* type the answer is least defensible. The pick block reads only
|
|
592
|
+
* `pick` and `statusText`, so a second flavour is a name and a
|
|
593
|
+
* fallback question, not a second mechanism. */
|
|
594
|
+
export function modePickView(spec, statusText) {
|
|
595
|
+
return {
|
|
596
|
+
flavor: "simple",
|
|
597
|
+
name: "mode",
|
|
598
|
+
title: "mode",
|
|
599
|
+
speaker: "you",
|
|
600
|
+
statusText,
|
|
601
|
+
args: { kind: "text", lines: [] },
|
|
602
|
+
fallbackQuestion: "switch mode? (name) ",
|
|
603
|
+
pick: spec,
|
|
604
|
+
};
|
|
605
|
+
}
|
|
579
606
|
export function modelPickView(spec, statusText) {
|
|
580
607
|
return {
|
|
581
608
|
flavor: "simple",
|
package/dist/components.d.ts
CHANGED
|
@@ -508,6 +508,21 @@ export declare function formatDuration(totalSeconds: number): string;
|
|
|
508
508
|
* W21: the question param is gone — the old question slot retires; a
|
|
509
509
|
* pending approval's status IS the panel's (the compositor derives
|
|
510
510
|
* it from the bound panel state). */
|
|
511
|
+
/** R8b — THE IDLE HINT GIVES WAY IN ORDER, and `ctrl+o` is on it.
|
|
512
|
+
*
|
|
513
|
+
* The transcript viewer shipped in 0.19.0 and was reachable only from
|
|
514
|
+
* the `?` sheet: not on the banner's key line, not here. A feature
|
|
515
|
+
* whose only advertisement is a screen you have to already know to
|
|
516
|
+
* open is DC-30's lesson pointing the other way.
|
|
517
|
+
*
|
|
518
|
+
* It cannot simply be appended, because this hint is dropped WHOLE
|
|
519
|
+
* when it does not fit — a longer string would take `/ commands` down
|
|
520
|
+
* with it on a narrow terminal. So the forms are a ladder, and the
|
|
521
|
+
* order says which affordance is least replaceable: `/ commands`
|
|
522
|
+
* survives longest because it is the door to everything; `ctrl+o`
|
|
523
|
+
* outranks `↑ history` because pressing up is how a person finds the
|
|
524
|
+
* history by accident, and nothing finds ctrl+o by accident. */
|
|
525
|
+
export declare function idleHint(room: number): string;
|
|
511
526
|
export declare function statusLine(status: string, tail: string, W: number, hint?: string): string;
|
|
512
527
|
/** The display-width prefix of a plain (SGR-free) text. W21: exported
|
|
513
528
|
* for the approval panel's option-2 rule-name cut. */
|
package/dist/components.js
CHANGED
|
@@ -1018,8 +1018,10 @@ export function exploreRows(parts, W) {
|
|
|
1018
1018
|
// outputs". /last shows the LAST call only — for a nine-call burst that
|
|
1019
1019
|
// is one output out of nine, and a footer that sends the human to a
|
|
1020
1020
|
// place the content is not is worse than a footer that says nothing.
|
|
1021
|
-
|
|
1022
|
-
|
|
1021
|
+
// R8a: the footer is an in-block note — the same indent, no glyph —
|
|
1022
|
+
// and the corner opens the block's first row, like every other one.
|
|
1023
|
+
rows.push(cutLine(`${p.dim}${NOTE_ROW}${COLLAPSE_ROW}${p.reset}`, W));
|
|
1024
|
+
return openBlock(rows);
|
|
1023
1025
|
}
|
|
1024
1026
|
/** The count term with the singular/plural forms — "no reads", "1 read",
|
|
1025
1027
|
* "5 reads". The noun's singular drops the plural suffix ("dirs" → "dir",
|
|
@@ -1518,8 +1520,43 @@ const CAP_ERROR = 3; // the error text head
|
|
|
1518
1520
|
* block's body, └ the block's last row — what was cut, where the rest
|
|
1519
1521
|
* is — at the LEFT EDGE (the gutter column: the left edge alone
|
|
1520
1522
|
* distinguishes the states at --plain). Structural (constraint 1). */
|
|
1521
|
-
|
|
1523
|
+
/** R8a — A TOOL BLOCK'S ROWS ARE INDENTED, NOT GUTTERED.
|
|
1524
|
+
*
|
|
1525
|
+
* `│ ` on every row drew a bar down the left of every multi-row
|
|
1526
|
+
* output, which is what the owner kept pointing at. The fact the bar
|
|
1527
|
+
* carried — "these rows are the call's output, not prose" — is real
|
|
1528
|
+
* and law 1.2 requires it survive a pipe, so it moves into the
|
|
1529
|
+
* INDENT: four columns, one level deeper than the header (2) and than
|
|
1530
|
+
* prose (2). Bytes still tell them apart; no column of glyphs.
|
|
1531
|
+
*
|
|
1532
|
+
* `└` survives as the mark that OPENS the block, once, on its first
|
|
1533
|
+
* row (see openBlock). In-block notes take NOTE_ROW — the same indent,
|
|
1534
|
+
* no glyph — because a second `└` inside one block would be the same
|
|
1535
|
+
* mark meaning two things (§4.1). CUT_ROW is unchanged for the
|
|
1536
|
+
* surfaces that are not a tool block: the fold row's target list, the
|
|
1537
|
+
* slot's overflow count. */
|
|
1538
|
+
const BODY_ROW = " ";
|
|
1539
|
+
const NOTE_ROW = " ";
|
|
1522
1540
|
const CUT_ROW = "└ ";
|
|
1541
|
+
/** R8a — stamp `└` on a block's FIRST row, after every slice and note
|
|
1542
|
+
* has been assembled, so the mark is always on the first row actually
|
|
1543
|
+
* emitted rather than on one a cap may have dropped. */
|
|
1544
|
+
function openBlock(rows) {
|
|
1545
|
+
// the corner goes on the first row that HAS something on it. A cap
|
|
1546
|
+
// or a blank leading output line can put an empty row first, and a
|
|
1547
|
+
// corner there would be a mark on a row with nothing to mark — law
|
|
1548
|
+
// 1.3, which is the rule this whole change is serving.
|
|
1549
|
+
const i = rows.findIndex((r) => visibleWidth(r) > visibleWidth(BODY_ROW));
|
|
1550
|
+
if (i < 0)
|
|
1551
|
+
return rows;
|
|
1552
|
+
const first = rows[i];
|
|
1553
|
+
const at = first.indexOf(BODY_ROW);
|
|
1554
|
+
if (at < 0)
|
|
1555
|
+
return rows;
|
|
1556
|
+
// the corner REPLACES two of the four indent columns, so the text
|
|
1557
|
+
// stays in the same column as every other row of the block.
|
|
1558
|
+
return [...rows.slice(0, i), `${first.slice(0, at)} \u2514 ${first.slice(at + BODY_ROW.length)}`, ...rows.slice(i + 1)];
|
|
1559
|
+
}
|
|
1523
1560
|
const blockMemo = new WeakMap();
|
|
1524
1561
|
/** The block's body rows below the header (memoized, W9). */
|
|
1525
1562
|
function toolBlockBody(c, W) {
|
|
@@ -1566,15 +1603,16 @@ function toolBlockBody(c, W) {
|
|
|
1566
1603
|
: [];
|
|
1567
1604
|
const note = c.expanded ? null : toolCutNote(c.name, c.resultText);
|
|
1568
1605
|
if (note !== null)
|
|
1569
|
-
rows.push(...foldLine(`${p.dim}${
|
|
1606
|
+
rows.push(...foldLine(`${p.dim}${NOTE_ROW}${note}${p.reset}`, W));
|
|
1570
1607
|
// TUI2-R1 (A): an EXPANDED block says how to put it back. The footer
|
|
1571
1608
|
// rides a block that HAS rows — an expanded delegate whose summary
|
|
1572
1609
|
// marker is missing renders nothing, and a lone footer under a head
|
|
1573
1610
|
// row would be an affordance for an empty block.
|
|
1574
1611
|
if (c.expanded && rows.length > 0)
|
|
1575
|
-
rows.push(...foldLine(`${p.dim}${
|
|
1576
|
-
|
|
1577
|
-
|
|
1612
|
+
rows.push(...foldLine(`${p.dim}${NOTE_ROW}${COLLAPSE_ROW}${p.reset}`, W));
|
|
1613
|
+
const opened = openBlock(rows);
|
|
1614
|
+
blockMemo.set(c, { width: W, state, content, rows: opened });
|
|
1615
|
+
return opened;
|
|
1578
1616
|
}
|
|
1579
1617
|
/** Fold result text into dim body rows (the BODY_ROW prefix): escape,
|
|
1580
1618
|
* split, fold each line at W−prefix; trailing empty rows (the result's
|
|
@@ -1601,7 +1639,7 @@ function shellTail(text, W) {
|
|
|
1601
1639
|
if (rows.length <= CAP_SHELL_SETTLED)
|
|
1602
1640
|
return rows;
|
|
1603
1641
|
const kept = CAP_SHELL_SETTLED - 1;
|
|
1604
|
-
const cut = foldLine(`${p.dim}${
|
|
1642
|
+
const cut = foldLine(`${p.dim}${NOTE_ROW}+${rows.length - kept} earlier rows · ctrl+r${p.reset}`, W);
|
|
1605
1643
|
return [...rows.slice(rows.length - kept), ...cut];
|
|
1606
1644
|
}
|
|
1607
1645
|
/** The error text head: the FIRST rows, capped at 3 — the answer is at
|
|
@@ -1621,7 +1659,7 @@ function errorBody(c, W) {
|
|
|
1621
1659
|
const rows = blockRows(c.resultText.split("\n").slice(skipFirst).join("\n"), W);
|
|
1622
1660
|
if (rows.length <= CAP_ERROR)
|
|
1623
1661
|
return rows;
|
|
1624
|
-
const cut = foldLine(`${p.dim}${
|
|
1662
|
+
const cut = foldLine(`${p.dim}${NOTE_ROW}+${rows.length - (CAP_ERROR - 1)} more · ctrl+r${p.reset}`, W);
|
|
1625
1663
|
return [...rows.slice(0, CAP_ERROR - 1), ...cut];
|
|
1626
1664
|
}
|
|
1627
1665
|
/** The running tool's FIXED-height window (W8): exactly 3 rows from
|
|
@@ -1640,7 +1678,7 @@ function liveWindow(text, W) {
|
|
|
1640
1678
|
// blanks below it — VD-4's own rule ("the output starts under its
|
|
1641
1679
|
// own header and grows downward"), which the gutter rows used to
|
|
1642
1680
|
// satisfy by accident and blanks made visible as a two-row gap.
|
|
1643
|
-
return [`${p.dim}${
|
|
1681
|
+
return [`${p.dim}${NOTE_ROW}waiting for output${p.reset}`, "", ""];
|
|
1644
1682
|
}
|
|
1645
1683
|
const rows = blockRows(text, W);
|
|
1646
1684
|
if (rows.length <= CAP_LIVE_WINDOW) {
|
|
@@ -1648,7 +1686,7 @@ function liveWindow(text, W) {
|
|
|
1648
1686
|
rows.push(""); // R7a: blank, not a bar
|
|
1649
1687
|
return rows;
|
|
1650
1688
|
}
|
|
1651
|
-
const cut = foldLine(`${p.dim}${
|
|
1689
|
+
const cut = foldLine(`${p.dim}${NOTE_ROW}+${rows.length - (CAP_LIVE_WINDOW - 1)} earlier rows · ctrl+r${p.reset}`, W);
|
|
1652
1690
|
return [...rows.slice(rows.length - (CAP_LIVE_WINDOW - 1)), ...cut];
|
|
1653
1691
|
}
|
|
1654
1692
|
/**
|
|
@@ -1692,7 +1730,7 @@ function shellLiveTail(text, W) {
|
|
|
1692
1730
|
const kept = rows.slice(Math.max(0, rows.length - (CAP_LIVE_WINDOW - 1)));
|
|
1693
1731
|
while (kept.length < CAP_LIVE_WINDOW - 1)
|
|
1694
1732
|
kept.push(""); // R7a: blank, not a bar
|
|
1695
|
-
return [...kept, cutLine(`${p.dim}${
|
|
1733
|
+
return [...kept, cutLine(`${p.dim}${NOTE_ROW}live tail · esc stop · alt+⏎ redirect${p.reset}`, W)];
|
|
1696
1734
|
}
|
|
1697
1735
|
/**
|
|
1698
1736
|
* R4 — the standing act slot.
|
|
@@ -1743,7 +1781,8 @@ export function slotTail(text, W, rows) {
|
|
|
1743
1781
|
// R7a: no pad. The slot stopped padding (see slotPad) and this was
|
|
1744
1782
|
// the same pad by another route — three blank rows under a call with
|
|
1745
1783
|
// nothing to say yet, which is the hole a7's blank-run guard prices.
|
|
1746
|
-
|
|
1784
|
+
// R8a: the corner opens whatever slice survives the cap.
|
|
1785
|
+
return openBlock(body.slice(Math.max(0, body.length - rows)));
|
|
1747
1786
|
}
|
|
1748
1787
|
/** R4 — clamp or pad assembled slot rows to EXACTLY `rows`. The padding
|
|
1749
1788
|
* is what makes the slot stand; the clamp is what keeps the slot from
|
|
@@ -2000,7 +2039,11 @@ export function cutLine(line, W) {
|
|
|
2000
2039
|
width += cw;
|
|
2001
2040
|
i += 1;
|
|
2002
2041
|
}
|
|
2003
|
-
|
|
2042
|
+
// R8a: the reset comes from the PALETTE, not hardcoded. `\x1b[0m`
|
|
2043
|
+
// here put an escape into every cut row under NO_COLOR and behind a
|
|
2044
|
+
// pipe — the one context COLOR_OFF exists to keep clean (§1.2). A
|
|
2045
|
+
// coloured palette is byte-identical, because its reset IS `\x1b[0m`.
|
|
2046
|
+
return `${out}${palette().reset}…`;
|
|
2004
2047
|
}
|
|
2005
2048
|
/** W20 — the settled block's duration, the `2h 14m` form (the task
|
|
2006
2049
|
* narrative's long-horizon idiom): minutes+seconds under an hour,
|
|
@@ -2085,6 +2128,33 @@ class Checklist {
|
|
|
2085
2128
|
* W21: the question param is gone — the old question slot retires; a
|
|
2086
2129
|
* pending approval's status IS the panel's (the compositor derives
|
|
2087
2130
|
* it from the bound panel state). */
|
|
2131
|
+
/** R8b — THE IDLE HINT GIVES WAY IN ORDER, and `ctrl+o` is on it.
|
|
2132
|
+
*
|
|
2133
|
+
* The transcript viewer shipped in 0.19.0 and was reachable only from
|
|
2134
|
+
* the `?` sheet: not on the banner's key line, not here. A feature
|
|
2135
|
+
* whose only advertisement is a screen you have to already know to
|
|
2136
|
+
* open is DC-30's lesson pointing the other way.
|
|
2137
|
+
*
|
|
2138
|
+
* It cannot simply be appended, because this hint is dropped WHOLE
|
|
2139
|
+
* when it does not fit — a longer string would take `/ commands` down
|
|
2140
|
+
* with it on a narrow terminal. So the forms are a ladder, and the
|
|
2141
|
+
* order says which affordance is least replaceable: `/ commands`
|
|
2142
|
+
* survives longest because it is the door to everything; `ctrl+o`
|
|
2143
|
+
* outranks `↑ history` because pressing up is how a person finds the
|
|
2144
|
+
* history by accident, and nothing finds ctrl+o by accident. */
|
|
2145
|
+
export function idleHint(room) {
|
|
2146
|
+
// The third rung is today's hint, kept so that NO width loses
|
|
2147
|
+
// something that used to fit: without it, a room of 24-30 columns
|
|
2148
|
+
// fell all the way to `/ commands` even though the old form fitted.
|
|
2149
|
+
// So the ladder is not a strict ranking of the three affordances —
|
|
2150
|
+
// it is the widest honest form at each room, and ctrl+o is on the
|
|
2151
|
+
// first two rungs rather than on all of them.
|
|
2152
|
+
for (const form of [" / commands · ↑ history · ctrl+o transcript", " / commands · ctrl+o transcript", " / commands · ↑ history", " / commands"]) {
|
|
2153
|
+
if (visibleWidth(form) <= room)
|
|
2154
|
+
return form;
|
|
2155
|
+
}
|
|
2156
|
+
return "";
|
|
2157
|
+
}
|
|
2088
2158
|
export function statusLine(status, tail, W, hint) {
|
|
2089
2159
|
const p = palette();
|
|
2090
2160
|
const text = `${status}${tail === "" ? "" : ` · ${tail}`}`;
|
|
@@ -2092,13 +2162,13 @@ export function statusLine(status, tail, W, hint) {
|
|
|
2092
2162
|
// "esc to cancel" (the same one-line-bounded shape as W12's delegate
|
|
2093
2163
|
// row; the #16g rule still cuts the HINT first, then the status with
|
|
2094
2164
|
// a "…" — never a fold).
|
|
2095
|
-
const hintText = hint ?? " / commands · ↑ history";
|
|
2096
2165
|
const statusW = visibleWidth(text);
|
|
2097
2166
|
if (statusW > W) {
|
|
2098
2167
|
return `${p.dim}${widthCut(text, W - 1)}…${p.reset}`;
|
|
2099
2168
|
}
|
|
2169
|
+
const hintText = hint ?? idleHint(Math.max(0, W - statusW));
|
|
2100
2170
|
const hintW = visibleWidth(hintText);
|
|
2101
|
-
if (statusW + hintW > W)
|
|
2171
|
+
if (hintW === 0 || statusW + hintW > W)
|
|
2102
2172
|
return `${p.dim}${text}${p.reset}`;
|
|
2103
2173
|
return `${p.dim}${text}${" ".repeat(Math.max(0, W - statusW - hintW))}${hintText}${p.reset}`;
|
|
2104
2174
|
}
|
package/dist/strings.d.ts
CHANGED
|
@@ -176,6 +176,13 @@ export declare const PANEL_KEYS_ROW = "panels: \u2191\u2193 move \u00B7 \u23CE c
|
|
|
176
176
|
* screens pretending to be one. A narrow terminal shows fewer columns
|
|
177
177
|
* of the same truth, which is the honest degradation.
|
|
178
178
|
*/
|
|
179
|
+
/** R8b — the band's own opening row: a labelled rule at full width.
|
|
180
|
+
*
|
|
181
|
+
* Moved here from the @ picker, unchanged in every byte, because the
|
|
182
|
+
* keys sheet needs it too and `components.ts` already imports this
|
|
183
|
+
* module — the dependency only runs one way. `at-picker.ts` re-exports
|
|
184
|
+
* it, so every existing import site is untouched. */
|
|
185
|
+
export declare function bandHeader(label: string, W: number): string;
|
|
179
186
|
export declare function keysSheetRows(W: number): string[];
|
|
180
187
|
/** TUI2-R1 (D) — the keys as ONE line, for /help. The same table the
|
|
181
188
|
* sheet renders, joined — so the two can disagree only by deleting a
|
package/dist/strings.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* looked up here.
|
|
19
19
|
*/
|
|
20
20
|
import { escapeTerminal, palette } from "./render.js";
|
|
21
|
-
import { displayWidth } from "./width.js";
|
|
21
|
+
import { displayWidth, visibleWidth } from "./width.js";
|
|
22
22
|
/** v2a: the interactive prompt — the identity accent. readline owns the
|
|
23
23
|
* echo of what the user types; we own the prompt's color. (v2c: the
|
|
24
24
|
* readline prompt keeps "you> " — the brick ▌ is the dock's row only;
|
|
@@ -296,6 +296,27 @@ const SHEET_STOPS = [
|
|
|
296
296
|
* screens pretending to be one. A narrow terminal shows fewer columns
|
|
297
297
|
* of the same truth, which is the honest degradation.
|
|
298
298
|
*/
|
|
299
|
+
/** R8b — the band's own opening row: a labelled rule at full width.
|
|
300
|
+
*
|
|
301
|
+
* Moved here from the @ picker, unchanged in every byte, because the
|
|
302
|
+
* keys sheet needs it too and `components.ts` already imports this
|
|
303
|
+
* module — the dependency only runs one way. `at-picker.ts` re-exports
|
|
304
|
+
* it, so every existing import site is untouched. */
|
|
305
|
+
export function bandHeader(label, W) {
|
|
306
|
+
const p = palette();
|
|
307
|
+
const head = `\u2500\u2500\u2500 ${label} `;
|
|
308
|
+
const line = `${head}${"\u2500".repeat(Math.max(1, W - head.length))}`;
|
|
309
|
+
let out = "";
|
|
310
|
+
let w = 0;
|
|
311
|
+
for (const ch of line) {
|
|
312
|
+
const cw = displayWidth(ch);
|
|
313
|
+
if (w + cw > Math.max(1, W))
|
|
314
|
+
break;
|
|
315
|
+
out += ch;
|
|
316
|
+
w += cw;
|
|
317
|
+
}
|
|
318
|
+
return `${p.dim}${out}${p.reset}`;
|
|
319
|
+
}
|
|
299
320
|
export function keysSheetRows(W) {
|
|
300
321
|
const p = palette();
|
|
301
322
|
const cell = (i) => {
|
|
@@ -309,7 +330,14 @@ export function keysSheetRows(W) {
|
|
|
309
330
|
const b = KEY_BINDINGS[i];
|
|
310
331
|
return b === undefined ? "" : `${b.keys} ${b.what}`;
|
|
311
332
|
};
|
|
312
|
-
|
|
333
|
+
// R8b — THE SHEET NAMES ITSELF, in the band vocabulary.
|
|
334
|
+
//
|
|
335
|
+
// Every other overlay does: `─── commands ───`, `─── files ───`,
|
|
336
|
+
// `─── sessions ───`, `── transcript · N folds ──`. This one opened
|
|
337
|
+
// with a bare bold word at column 0, which is the exact condition
|
|
338
|
+
// TUI2-R1.5 ⑦(b) named when it made the rule — with scrollback
|
|
339
|
+
// behind an overlay, nothing said where the surface began.
|
|
340
|
+
const rows = [bandHeader("keys", W)];
|
|
313
341
|
for (let r = 0; r < SHEET_GRID.length; r += 1) {
|
|
314
342
|
const indexes = SHEET_GRID[r];
|
|
315
343
|
let row = "";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-tui-cells",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.2",
|
|
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",
|