@vincemakes/kiso-tui-cells 0.24.0 → 0.24.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.
@@ -8,7 +8,15 @@
8
8
  * lives HERE: every line a component returns must fit the terminal
9
9
  * width — the compositor's crash-on-violation invariant backs it up
10
10
  * (a component that forgets to fold CRASHES with a diagnostic, never
11
- * silently truncates — the crash is the contract, not a symptom).
11
+ * silently truncates — the crash is the contract UNDER TEST; in the
12
+ * field the row is cut and the fact is said, once, through the notice
13
+ * channel. DECLARED REVERSAL of "the crash is the contract, not a
14
+ * symptom" (owner-lane, 2026-09-04): two instances of this class in two
15
+ * days, one caught by a gate (DC-45) and one by the owner on the first
16
+ * frame of an ordinary command (DC-48). In a gate the crash is right; in
17
+ * a human's hands it costs them the composer and the session to save
18
+ * them a row one column too wide. `KISO_INVARIANTS=throw` is what every
19
+ * suite here runs under).
12
20
  *
13
21
  * The fold is SGR-AWARE: a line whose bold/dim span would straddle a
14
22
  * fold boundary closes the span at the break and reopens it on the
@@ -368,6 +376,27 @@ export declare const CAP_PREVIEW = 5;
368
376
  /** The rows a card costs besides its window: two pads, the head, two
369
377
  * blanks and the status row. Below this there is no card (DC-43). */
370
378
  export declare const CARD_CHROME = 6;
379
+ /**
380
+ * 0.24.2 ③ — the appended expansion, as a CARD.
381
+ *
382
+ * `ctrl+o` used to append bare ground under a `✦` — the turn recap's own
383
+ * mark, one symbol for two meanings (§4.1) — in a page where every other
384
+ * piece of machine work is a card. And it lands after the recap, so the
385
+ * only tie to the call it came from was that mark's sentence.
386
+ *
387
+ * The card's head row names the call, which is the tie, so the mark is
388
+ * not needed for it. The body is the WHOLE result: an expansion that
389
+ * capped would be no expansion.
390
+ *
391
+ * Expanding IN PLACE is a different problem — committed rows are final
392
+ * (§7.1) — and it waits for route B (DC-50).
393
+ */
394
+ export declare function expandedCard(verb: string, target: string, meta: string, sections: readonly string[], outcome: string, W: number): string[];
395
+ /** 0.24.2 ② — the live region's `thinking…` placeholder: dim italic at
396
+ * column 2, no glyph, the SAME shape a thinking paragraph takes so that
397
+ * whatever arrives replaces it in place. Never committed — see the
398
+ * compositor's #project for why that is what makes it allowed. */
399
+ export declare function thinkingRow(): string;
371
400
  /**
372
401
  * R4 — the standing act slot.
373
402
  *
@@ -8,7 +8,15 @@
8
8
  * lives HERE: every line a component returns must fit the terminal
9
9
  * width — the compositor's crash-on-violation invariant backs it up
10
10
  * (a component that forgets to fold CRASHES with a diagnostic, never
11
- * silently truncates — the crash is the contract, not a symptom).
11
+ * silently truncates — the crash is the contract UNDER TEST; in the
12
+ * field the row is cut and the fact is said, once, through the notice
13
+ * channel. DECLARED REVERSAL of "the crash is the contract, not a
14
+ * symptom" (owner-lane, 2026-09-04): two instances of this class in two
15
+ * days, one caught by a gate (DC-45) and one by the owner on the first
16
+ * frame of an ordinary command (DC-48). In a gate the crash is right; in
17
+ * a human's hands it costs them the composer and the session to save
18
+ * them a row one column too wide. `KISO_INVARIANTS=throw` is what every
19
+ * suite here runs under).
12
20
  *
13
21
  * The fold is SGR-AWARE: a line whose bold/dim span would straddle a
14
22
  * fold boundary closes the span at the break and reopens it on the
@@ -864,7 +872,6 @@ class ToolExecution {
864
872
  const bare = gutterCut(gutter, `${verbCol} ${liveTarget(c)}`, Math.max(4, W - dur.length));
865
873
  return [`${bare[0]}${p.dim}${dur}${p.reset}`];
866
874
  }
867
- const head = gutterCut(gutter, `${verbCol} ${liveTarget(c)}`, W)[0];
868
875
  // DC-46 — the two GESTURES ride the status row, where they cost
869
876
  // nothing. They were a footer INSIDE the window, spending one of
870
877
  // its rows on a sentence that is not output; with the window
@@ -875,7 +882,32 @@ class ToolExecution {
875
882
  // 3.2s`.
876
883
  const gestures = c.name === "shell" ? " · esc stops · alt+⏎ redirects" : "";
877
884
  const status = pickTier([`${elapsed}s${gestures}`, `${elapsed}s`], Math.max(1, W - visibleWidth(noteIndent())));
878
- return slabBlock(head, toolBlockBody(c, W, ctx), status, W);
885
+ const live = toolBlockBody(c, W, ctx);
886
+ if (live.length > 0)
887
+ return slabBlock(gutterCut(gutter, `${verbCol} ${liveTarget(c)}`, W)[0], live, status, W);
888
+ // DC-48 — THE THREE-ROW CARD IS ONE ROW, so it is assembled here
889
+ // against the room it actually has.
890
+ //
891
+ // This branch used to cut the head to `W` and hand it to
892
+ // `slabBlock`, which joins head and outcome and cut nothing —
893
+ // so the row came out `W` wide PLUS the whole status, and the
894
+ // compositor did what it promises: it threw. On the owner's
895
+ // 80-column terminal a long `find` produced a 113-column row on
896
+ // its FIRST FRAME, which is the first second of every command.
897
+ //
898
+ // Pin 4's order: the command is the cuttable span and the
899
+ // elapsed is never cut open, so the command takes what the
900
+ // status leaves — the `· ` between them and the two-column
901
+ // gutter included.
902
+ // the status takes its own tier against the room the row has, not
903
+ // against a whole width: on a narrow terminal the gestures give
904
+ // way so the COMMAND keeps something to say, and the elapsed —
905
+ // pin 4's core — never does.
906
+ const MIN_TARGET = 10; // the gutter, the verb column, a character of command
907
+ const oneRow = pickTier([`${elapsed}s${gestures}`, `${elapsed}s`], Math.max(1, W - MIN_TARGET - 3));
908
+ const room = Math.max(4, W - visibleWidth(oneRow) - 3);
909
+ const only = gutterCut(gutter, `${verbCol} ${liveTarget(c)}`, room)[0];
910
+ return slabBlock(`${only}${p.dim} · ${oneRow}${p.reset}`, [], null, W);
879
911
  }
880
912
  // W2: ◦ replaces → for QUEUED — · is the separator inside every
881
913
  // metadata group; a queued marker that is also the separator
@@ -1002,8 +1034,16 @@ function settledHeadText(verbCol, target, meta, attr, elapsed, room, counted = "
1002
1034
  const budget = room - visibleWidth(lead) - visibleWidth(stem) - 4; // the ellipsis + " · "
1003
1035
  if (budget >= 1)
1004
1036
  return `${lead}${widthCut(target, budget)}… · ${stem}`;
1005
- // 4. below that even the core cannot ride: the row is the call's
1006
- // identity and its affordance, and no half-open parenthesis.
1037
+ // 4. DC-48 the ELAPSED still rides, and the target takes what is
1038
+ // left. This used to return the target alone, on the argument that
1039
+ // a cut core would leave a half-open parenthesis; R13's chain has
1040
+ // no bracket to leave open, so the reason retired with the
1041
+ // parentheses and pin 4's own rule applies at every width: what
1042
+ // happened and how long it took is never cut away.
1043
+ const floor = `${elapsed}s`;
1044
+ const left = room - visibleWidth(lead) - visibleWidth(floor) - 4; // the ellipsis + " · "
1045
+ if (left >= 1)
1046
+ return `${lead}${widthCut(target, left)}… · ${floor}`;
1007
1047
  return `${lead}${widthCut(target, Math.max(1, room - visibleWidth(lead)))}`;
1008
1048
  }
1009
1049
  /**
@@ -1418,9 +1458,17 @@ const CARD_ROW = " ";
1418
1458
  /** R13 E3 — the column the model's words begin in, the same one the
1419
1459
  * card's rows and the chip's text begin in. */
1420
1460
  const PROSE_COL = " ";
1421
- /** DC-47 — the model's THINKING, one level deeper than its prose, so
1422
- * the two are still told apart once the escapes are stripped (§1.2). */
1423
- const THINK_COL = " ";
1461
+ /** DC-47, ADJUDICATED — the model's THINKING begins in the SAME column
1462
+ * as everything else: two.
1463
+ *
1464
+ * It went to four when E3 moved prose to two, so that stripping the
1465
+ * escapes would still tell them apart (§1.2). The owner looked at it
1466
+ * and ruled against it: "the thinking area is not indented by the same
1467
+ * two as the first line — it needs to keep the same first-line indent
1468
+ * as everything else" (2026-09-04). §1.8's one left edge outranks the
1469
+ * distinction, and §1.2 takes a DECLARED EXCEPTION for this one pair —
1470
+ * see design.md §1.2 and §7.2 for what is given up and what is not. */
1471
+ const THINK_COL = " ";
1424
1472
  const bodyRow = () => (slabPaints() ? CARD_ROW : BODY_ROW_FLAT);
1425
1473
  const noteIndent = () => (slabPaints() ? CARD_ROW : NOTE_ROW_FLAT);
1426
1474
  const CUT_ROW = "└ ";
@@ -1506,7 +1554,12 @@ function slabBlock(head, body, outcome, W) {
1506
1554
  // instead — off the surface R8a's indent is still the fact, which is
1507
1555
  // why the degradation above keeps it.
1508
1556
  if (body.length === 0) {
1509
- const only = outcome === null ? head : `${head} ${outcome}`;
1557
+ // DC-48: the join makes a ROW, so it is cut like every other row.
1558
+ // The callers above size their parts against the room they have;
1559
+ // this is the backstop that makes invariant ① hold whatever they
1560
+ // do, and it is what was missing when a running card's head was
1561
+ // cut to W and then had a whole status appended to it.
1562
+ const only = cutLine(outcome === null ? head : `${head} ${outcome}`, W);
1510
1563
  return [slabRow("", W), slabRow(only, W), slabRow("", W)];
1511
1564
  }
1512
1565
  const top = [slabRow("", W), slabRow(head, W), slabRow("", W), ...body.map((r) => slabRow(r, W))];
@@ -1517,6 +1570,38 @@ function slabBlock(head, body, outcome, W) {
1517
1570
  return [...top, slabRow("", W)];
1518
1571
  return [...top, slabRow("", W), ...noteRow(outcome, W, "body").map((r) => slabRow(r, W)), slabRow("", W)];
1519
1572
  }
1573
+ /**
1574
+ * 0.24.2 ③ — the appended expansion, as a CARD.
1575
+ *
1576
+ * `ctrl+o` used to append bare ground under a `✦` — the turn recap's own
1577
+ * mark, one symbol for two meanings (§4.1) — in a page where every other
1578
+ * piece of machine work is a card. And it lands after the recap, so the
1579
+ * only tie to the call it came from was that mark's sentence.
1580
+ *
1581
+ * The card's head row names the call, which is the tie, so the mark is
1582
+ * not needed for it. The body is the WHOLE result: an expansion that
1583
+ * capped would be no expansion.
1584
+ *
1585
+ * Expanding IN PLACE is a different problem — committed rows are final
1586
+ * (§7.1) — and it waits for route B (DC-50).
1587
+ */
1588
+ export function expandedCard(verb, target, meta, sections, outcome, W) {
1589
+ const p = palette();
1590
+ const head = cutLine(` ${verb} ${p.bold}${escapeTerminal(target)}${p.reset}${slabPaints() ? p.washDim : p.dim} · ${escapeTerminal(meta)}${slabPaints() ? p.washDimEnd : p.reset}`, W);
1591
+ const body = [];
1592
+ for (const raw of sections)
1593
+ for (const row of blockRows(raw, W, slabPaints() ? "body" : "dim"))
1594
+ body.push(row);
1595
+ return slabBlock(head, body, outcome, W);
1596
+ }
1597
+ /** 0.24.2 ② — the live region's `thinking…` placeholder: dim italic at
1598
+ * column 2, no glyph, the SAME shape a thinking paragraph takes so that
1599
+ * whatever arrives replaces it in place. Never committed — see the
1600
+ * compositor's #project for why that is what makes it allowed. */
1601
+ export function thinkingRow() {
1602
+ const p = palette();
1603
+ return `${THINK_COL}${p.dim}${p.italic}thinking…${p.italicEnd}${p.reset}`;
1604
+ }
1520
1605
  /** R8a — stamp `└` on a block's FIRST row, after every slice and note
1521
1606
  * has been assembled, so the mark is always on the first row actually
1522
1607
  * emitted rather than on one a cap may have dropped. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-tui-cells",
3
- "version": "0.24.0",
3
+ "version": "0.24.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",