@vincemakes/kiso-tui 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.
Files changed (2) hide show
  1. package/dist/compositor.js +59 -12
  2. package/package.json +2 -2
@@ -52,7 +52,7 @@ import { MOUSE_OFF } from "./editor.js";
52
52
  import { atPanelRows, bandHeader } from "./at-picker.js";
53
53
  // TUI2-R2 ②: the session picker's rows — the band's third occupant.
54
54
  import { sessionPickerRows } from "./session-picker.js";
55
- import { CAP_PREVIEW, Container, ROLLUP_NOUN, MOTION_FRAMES, MdStream, bodySpacing, boxBottom, boxTop, cellComponent, foldCountsObjects, foldTerms, focusToken, foldLine, gutterCut, cutLine, isExploreTool, moreRunningRow, pendingQueueRows, slotPad, slotTail, statusLine, visibleWidth, breathFrame, } from "./components.js";
55
+ import { CAP_PREVIEW, thinkingRow, expandedCard, Container, ROLLUP_NOUN, MOTION_FRAMES, MdStream, bodySpacing, boxBottom, boxTop, cellComponent, foldCountsObjects, foldTerms, focusToken, foldLine, gutterCut, cutLine, isExploreTool, moreRunningRow, pendingQueueRows, slotPad, slotTail, statusLine, visibleWidth, breathFrame, } from "./components.js";
56
56
  import { bannerLines, escapeTerminal, foldResult, foldThinking, palette, renderTerminalGap, renderToolSummary, toolTarget } from "./render.js";
57
57
  import { displayVerb, keysSheetRows } from "./strings.js";
58
58
  // R5 — the transcript viewer's PURE projection. The compositor supplies
@@ -1258,18 +1258,19 @@ export class Body {
1258
1258
  }
1259
1259
  const turnsBack = this.#cells.slice(idx + 1).filter((c) => c.kind === "user").length;
1260
1260
  const p = palette();
1261
- const header = `${p.bold}✦${p.reset} expanded · ${escapeTerminal(`${displayVerb(cell.name)} ${toolTarget(cell.name, input)}`)} · ${turnsBack} ${turnsBack === 1 ? "turn" : "turns"} back`;
1261
+ // 0.24.2 the expansion is a CARD, and `✦` is not its mark: that
1262
+ // glyph is the turn recap's, and one symbol with two meanings is
1263
+ // §4.1. The head row names the call, which is the tie to it that
1264
+ // the mark's sentence used to be.
1265
+ const verb = displayVerb(cell.name);
1266
+ const elapsed = cell.startedAt !== null && cell.doneAt !== null ? ((cell.doneAt - cell.startedAt) / 1000).toFixed(1) : "?";
1267
+ const n = cell.resultText === "" ? 0 : cell.resultText.split("\n").length;
1262
1268
  return {
1263
1269
  kind: "appended",
1264
- lines: [
1265
- header,
1266
- // TUI2-R2pre ④: the SECTION HEADERS say the act; the payloads
1267
- // below them (inputFull, resultText) are RAW and byte-identical.
1268
- `--- ${displayVerb(cell.name)} input ---`,
1269
- cell.inputFull,
1270
- `--- ${displayVerb(cell.name)} output${cell.isError ? " (error)" : ""} ---`,
1271
- cell.resultText,
1272
- ],
1270
+ lines: expandedCard(verb, toolTarget(cell.name, input), `expanded · ${turnsBack} ${turnsBack === 1 ? "turn" : "turns"} back`,
1271
+ // TUI2-R2pre ④: the SECTION HEADERS say the act; the payloads
1272
+ // below them (inputFull, resultText) are RAW and byte-identical.
1273
+ [`--- ${verb} input ---`, cell.inputFull, `--- ${verb} output${cell.isError ? " (error)" : ""} ---`, cell.resultText], [cell.isError ? "failed" : "exit 0", `${n} line${n === 1 ? "" : "s"}`, `${elapsed}s`].filter((x) => x !== "").join(" · "), this.#opts.width()),
1273
1274
  };
1274
1275
  }
1275
1276
  // ---- the Dock façade (the CLI's chrome API — same shape as the old dock) ----
@@ -1821,8 +1822,33 @@ export class Body {
1821
1822
  out.push(...this.#space(i, prev, rows));
1822
1823
  prev = rows;
1823
1824
  }
1825
+ // 0.24.2 ② — THE PLACEHOLDER. A turn in flight whose live region
1826
+ // has drawn nothing leaves the composer under a blank stretch,
1827
+ // with only the status row saying anything is happening; the owner
1828
+ // read that as the product having stopped.
1829
+ //
1830
+ // So one row says it: `thinking…`, dim italic at column 2, no
1831
+ // glyph — the SAME column and the same font a thinking paragraph
1832
+ // takes, so whatever arrives replaces it in place and the eye sees
1833
+ // a word change rather than a jump.
1834
+ //
1835
+ // It is NOT a cell. It never commits, never reaches the
1836
+ // scrollback, and neither `/last` nor the pipe has heard of it —
1837
+ // which is the whole of why a row that is a guess about the future
1838
+ // is allowed at all: a row that never becomes history cannot make
1839
+ // history wrong.
1840
+ if (out.length === 0 && this.#thinkingGap())
1841
+ return [...bodySpacing(this.#committed > 0 ? this.#lineCache[this.#committed - 1] : null, [thinkingRow()])];
1824
1842
  return out;
1825
1843
  }
1844
+ /** 0.24.2 ② — is this the gap the placeholder is for? A turn has
1845
+ * begun and not ended, and nothing else is drawing a live row (the
1846
+ * caller has already found the projection empty, which is what "no
1847
+ * card is running" reduces to once every cell renders itself). */
1848
+ #thinkingGap() {
1849
+ const turn = this.#turns[this.#turns.length - 1];
1850
+ return turn !== undefined && !turn.ended;
1851
+ }
1826
1852
  /** The live region's scalar — the unit tests assert the cap directly
1827
1853
  * (the e2e gate pins the screen consequence). W11: the formula's
1828
1854
  * blanks are join artifacts — the count includes them (they are real
@@ -3119,6 +3145,8 @@ export class Body {
3119
3145
  }
3120
3146
  /** Invariant ①: every emitted line fits the width — a violation is a
3121
3147
  * CRASH with the diagnostic, never a silent truncate. */
3148
+ /** DC-48 — the field cut is announced ONCE per session. */
3149
+ #cutOnce = false;
3122
3150
  #checked(line, W) {
3123
3151
  // Invariant ①b (R3f): a ROW IS ONE PHYSICAL ROW.
3124
3152
  //
@@ -3148,7 +3176,26 @@ export class Body {
3148
3176
  }
3149
3177
  const w = visibleWidth(line);
3150
3178
  if (w > W) {
3151
- throw new Error(`kiso-tui invariant ① violated: a line of visible width ${w} > ${W} was about to be emitted — ${JSON.stringify(line.slice(0, 80))}`);
3179
+ // DECLARED REVERSAL of "the crash is the contract, not a
3180
+ // symptom" — owner-lane ruling 2026-09-04, after the SECOND
3181
+ // instance of this class in two days: DC-45 in a gate, DC-48 in
3182
+ // the owner's hands. Under test the crash is exactly right and
3183
+ // keeps every tooth; in the field it costs a human the
3184
+ // composer's contents and the whole session, to save them a row
3185
+ // that is one column too wide.
3186
+ //
3187
+ // So: THROW UNDER TEST, CUT IN THE FIELD, and say so. The
3188
+ // notice is once per session — a defect worth reporting is
3189
+ // worth saying once, and a row-by-row complaint on a resize
3190
+ // storm would be its own defect.
3191
+ const why = `kiso-tui invariant ① violated: a line of visible width ${w} > ${W} was about to be emitted — ${JSON.stringify(line.slice(0, 80))}`;
3192
+ if (process.env.KISO_INVARIANTS === "throw")
3193
+ throw new Error(why);
3194
+ if (!this.#cutOnce) {
3195
+ this.#cutOnce = true;
3196
+ queueMicrotask(() => this.notice("kiso-tui: a row was cut to width (invariant ①) · please report"));
3197
+ }
3198
+ return cutLine(line, W);
3152
3199
  }
3153
3200
  return line;
3154
3201
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-tui",
3
- "version": "0.24.0",
3
+ "version": "0.24.2",
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.24.0"
38
+ "@vincemakes/kiso-tui-cells": "0.24.2"
39
39
  }
40
40
  }