@vincemakes/kiso-tui 0.1.38 → 0.1.40

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.
@@ -145,7 +145,7 @@ export type BodyCell = {
145
145
  } | {
146
146
  kind: "checklist";
147
147
  /** the model-authored header tail (parseChecklist's count line —
148
- * chat.ts). The compositor's fixed "todo" prefix rides BEFORE it
148
+ * chat.ts). The compositor's fixed "task" prefix rides BEFORE it
149
149
  * (W20 naming ruling: never model-controlled). */
150
150
  header: string;
151
151
  items: {
@@ -191,13 +191,13 @@ export declare function turnFold(t: {
191
191
  edits: number;
192
192
  others: [string, number][];
193
193
  }): string[];
194
- /** W20 — the todo block's fixed-window height: the whole live block
194
+ /** W20 — the task block's fixed-window height: the whole live block
195
195
  * (header + rows) in POST-FOLD screen rows at EVERY width: the header,
196
196
  * the active row, up to 2 pending, the overflow-pending fold, the
197
197
  * done-collapse. Every live row CUTS at W (never folds) — the block's
198
198
  * height is its row count. */
199
- export declare const CAP_TODO_LIVE = 6;
200
- /** W20 — the settled block's duration, the `2h 14m` form (the todo
199
+ export declare const CAP_TASK_LIVE = 6;
200
+ /** W20 — the settled block's duration, the `2h 14m` form (the task
201
201
  * narrative's long-horizon idiom): minutes+seconds under an hour,
202
202
  * hours+minutes past it. */
203
203
  export declare function formatDuration(totalSeconds: number): string;
@@ -167,19 +167,20 @@ export function cellComponent(cell) {
167
167
  }
168
168
  }
169
169
  /**
170
- * The user message — the left rail (bright-white BOLD per row, the
171
- * v4.1 design) + the W16 inset chip. The chip folds the text at W−6
172
- * (the rail + the indent + the side pads), then pads EVERY row to the
173
- * longest row's DISPLAY width + one space each side, indented two: the
174
- * block is only as wide as what was said (never the full-width band
175
- * a short message like /think would paint a bar across the terminal).
176
- * The padding is by cells (charWidth is the width authority), so a CJK
177
- * row pads by width, never by chars, and the chip never overruns its
178
- * fold. SGR 7 closed with SGR 27 never SGR 0, the chip composes
179
- * with a surrounding span and NEVER dim: reverse video inverts the
180
- * CURRENT colours, so dimmed text would invert into a dimmed block
181
- * with no contrast. The rail stays: SGR is an emphasis on top, the
182
- * rail is the structural fallback that survives a pipe.
170
+ * The user message — the W16 inset chip ALONE (the 2026-08-09 ruling:
171
+ * the rail and the indent are retired the rail's stated pipe
172
+ * fallback was theoretical redundancy: the CLI's pipe path is the
173
+ * line-mode "you>" form and never renders UserMessage). The chip folds
174
+ * the text at W−2 (the side pads), then pads EVERY row to the longest
175
+ * row's DISPLAY width + one space each side, flush left: the block is
176
+ * only as wide as what was said (never the full-width band a short
177
+ * message like /think would paint a bar across the terminal). The
178
+ * padding is by cells (charWidth is the width authority), so a CJK row
179
+ * pads by width, never by chars, and the chip never overruns its fold.
180
+ * SGR 7 closed with SGR 27 never SGR 0, the chip composes with a
181
+ * surrounding span and NEVER dim: reverse video inverts the CURRENT
182
+ * colours, so dimmed text would invert into a dimmed block with no
183
+ * contrast.
183
184
  */
184
185
  class UserMessage {
185
186
  cell;
@@ -188,18 +189,17 @@ class UserMessage {
188
189
  }
189
190
  render(W, _ctx) {
190
191
  const p = palette();
191
- const rail = `${p.bold}▍${p.reset} `;
192
- const chipW = Math.max(1, W - 6);
192
+ const chipW = Math.max(1, W - 2);
193
193
  const rows = [];
194
194
  for (const para of this.cell.text.split("\n")) {
195
195
  const folded = foldLine(escapeTerminal(para), chipW);
196
196
  const inner = Math.max(...folded.map((r) => displayWidth(r)));
197
197
  for (const row of folded) {
198
198
  const pad = inner - displayWidth(row);
199
- rows.push(`${rail} ${p.rv} ${row}${" ".repeat(pad)} ${p.rvEnd}`);
199
+ rows.push(`${p.rv} ${row}${" ".repeat(pad)} ${p.rvEnd}`);
200
200
  }
201
201
  }
202
- return rows.length > 0 ? rows : [rail.trimEnd()];
202
+ return rows;
203
203
  }
204
204
  }
205
205
  /** The thinking fold — one dim line, width-capped so the /think suffix
@@ -718,12 +718,12 @@ class Banner {
718
718
  return rows.map((r) => `${p.dim}${r}${p.reset}`);
719
719
  }
720
720
  }
721
- /** W20 — the todo block's fixed-window height: the whole live block
721
+ /** W20 — the task block's fixed-window height: the whole live block
722
722
  * (header + rows) in POST-FOLD screen rows at EVERY width: the header,
723
723
  * the active row, up to 2 pending, the overflow-pending fold, the
724
724
  * done-collapse. Every live row CUTS at W (never folds) — the block's
725
725
  * height is its row count. */
726
- export const CAP_TODO_LIVE = 6;
726
+ export const CAP_TASK_LIVE = 6;
727
727
  /** W20 — the live block's fixed-window row cut: an SGR-aware ONE-ROW
728
728
  * truncation (foldLine wraps; a wrapped row would break the height
729
729
  * cap — every live row is exactly one screen row at every width).
@@ -752,7 +752,7 @@ function cutLine(line, W) {
752
752
  }
753
753
  return `${out}\x1b[0m…`;
754
754
  }
755
- /** W20 — the settled block's duration, the `2h 14m` form (the todo
755
+ /** W20 — the settled block's duration, the `2h 14m` form (the task
756
756
  * narrative's long-horizon idiom): minutes+seconds under an hour,
757
757
  * hours+minutes past it. */
758
758
  export function formatDuration(totalSeconds) {
@@ -763,16 +763,16 @@ export function formatDuration(totalSeconds) {
763
763
  return m < 60 ? `${m}m ${s % 60}s` : `${Math.floor(m / 60)}h ${m % 60}m`;
764
764
  }
765
765
  /**
766
- * W20 — the todo checklist as STATE: ONE live block that redraws in
766
+ * W20 — the task checklist as STATE: ONE live block that redraws in
767
767
  * place (the current turn's in-place updates), settling at the turn's
768
- * end as ONE recap block. LIVE (done:false): the fixed "todo" prefix +
768
+ * end as ONE recap block. LIVE (done:false): the fixed "task" prefix +
769
769
  * the compositor-derived counts (the model tail rides AFTER — never
770
770
  * model-controlled), the active item first with ▸ (the menu's "the
771
771
  * current one"), pending next (≤2), the done items COLLAPSED behind the
772
772
  * W10 cut family `└ +N done · ctrl+r`, overflow pending behind
773
773
  * `└ +N more · ctrl+r` — every row cut at W so the cap holds at every
774
774
  * width. ctrl+r (W15) toggles the full list in place (expanded). SETTLED
775
- * (done:true): the recap idiom `todo done · N items · <duration>` + the
775
+ * (done:true): the recap idiom `task done · N items · <duration>` + the
776
776
  * FULL final item list in the checklist's existing shape (▖/□/▣ —
777
777
  * indented two, the glyph leads, no │ gutter).
778
778
  */
@@ -790,8 +790,8 @@ class Checklist {
790
790
  const plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
791
791
  const tail = this.cell.header === "" ? "" : ` · ${this.cell.header}`;
792
792
  const fixed = done
793
- ? `todo done · ${plural(items.length, "item")} · ${formatDuration(durationSeconds)}`
794
- : `todo · ${plural(items.length, "item")} · ${active.length} active · ${doneCount} done`;
793
+ ? `task done · ${plural(items.length, "item")} · ${formatDuration(durationSeconds)}`
794
+ : `task · ${plural(items.length, "item")} · ${active.length} active · ${doneCount} done`;
795
795
  const header = `${p.bold}▞${p.reset} ${escapeTerminal(fixed + tail)}`;
796
796
  // the FULL-list forms: SETTLED — the durable record (the fold is
797
797
  // fine — committed content wraps naturally) — and the LIVE ctrl+r
@@ -811,7 +811,7 @@ class Checklist {
811
811
  }
812
812
  // LIVE — the fixed window: the header + the item rows CUT at W
813
813
  // (one screen row each — the block's height is its row count,
814
- // CAP_TODO_LIVE, at every width). The cut is the momentary view;
814
+ // CAP_TASK_LIVE, at every width). The cut is the momentary view;
815
815
  // the settle (and the ctrl+r toggle) show everything.
816
816
  const itemRows = [];
817
817
  if (active.length > 0)
@@ -94,12 +94,12 @@ export declare class Body {
94
94
  endTurn(thoughtSeconds: number): void;
95
95
  terminal(label: string, statusLineText: string): void;
96
96
  notice(text: string): void;
97
- /** W20 — the todo checklist as STATE, not events: the FIRST call of a
97
+ /** W20 — the task checklist as STATE, not events: the FIRST call of a
98
98
  * turn creates the ONE live block (done:false — the commit loop only
99
99
  * takes done cells, so it stays in the live region); later calls of
100
100
  * the SAME turn MUTATE that block in place — same position, same
101
101
  * height, zero committed rows (the W8 fixed-window rule generalised
102
- * to state). An unchanged whole-table replace (the todo extension's
102
+ * to state). An unchanged whole-table replace (the task extension's
103
103
  * idempotent shape) is a no-op — no mark, no frame. The block commits
104
104
  * ONCE at the turn's end (endTurn); the next turn's first call starts
105
105
  * a fresh block — one settled block per turn that touched the list,
@@ -52,10 +52,10 @@ export const CURSOR_MARKER = "\x1b_[kiso-cur]\x1b\\";
52
52
  const FRAME_MS = 16; // state changes coalesce to ≥16ms frames
53
53
  const SPINNER_MS = 200; // the spinner cadence — a ONE-SHOT re-armed on demand
54
54
  const CHROME_ROWS = 4; // box top + input + box bottom + status — the design §03 chrome (V6-3; the box is W6)
55
- /** W20 — the whole-table-replace comparison: the live todo block only
56
- * redraws when the items actually changed (the todo extension's
55
+ /** W20 — the whole-table-replace comparison: the live task block only
56
+ * redraws when the items actually changed (the task extension's
57
57
  * idempotent shape — an unchanged replace is a no-op, no frame). */
58
- function sameTodo(a, b) {
58
+ function sameTask(a, b) {
59
59
  return a.length === b.length && a.every((x, i) => x.text === b[i].text && x.status === b[i].status);
60
60
  }
61
61
  /** The one compositor — implements the Body façade AND the Dock chrome
@@ -318,8 +318,8 @@ export class Body {
318
318
  return;
319
319
  turn.ended = true;
320
320
  turn.thoughtSeconds = thoughtSeconds;
321
- // W20: the turn's live todo block settles HERE — the ONE recap
322
- // block for the turn (`todo done · N items · <duration>`, the
321
+ // W20: the turn's live task block settles HERE — the ONE recap
322
+ // block for the turn ("`task done · N items · <duration>", the
323
323
  // duration clocked compositor-side from the block's first call —
324
324
  // the CLI stays unchanged). A turn that never touched the list has
325
325
  // no live block — nothing settles. Newest-first: the live block is
@@ -371,12 +371,12 @@ export class Body {
371
371
  this.#cells.push({ kind: "notice", text, done: true });
372
372
  this.#mark();
373
373
  }
374
- /** W20 — the todo checklist as STATE, not events: the FIRST call of a
374
+ /** W20 — the task checklist as STATE, not events: the FIRST call of a
375
375
  * turn creates the ONE live block (done:false — the commit loop only
376
376
  * takes done cells, so it stays in the live region); later calls of
377
377
  * the SAME turn MUTATE that block in place — same position, same
378
378
  * height, zero committed rows (the W8 fixed-window rule generalised
379
- * to state). An unchanged whole-table replace (the todo extension's
379
+ * to state). An unchanged whole-table replace (the task extension's
380
380
  * idempotent shape) is a no-op — no mark, no frame. The block commits
381
381
  * ONCE at the turn's end (endTurn); the next turn's first call starts
382
382
  * a fresh block — one settled block per turn that touched the list,
@@ -398,7 +398,7 @@ export class Body {
398
398
  const turn = this.#turns.length - 1;
399
399
  const last = this.#cells[this.#cells.length - 1];
400
400
  if (last !== undefined && last.kind === "checklist" && !last.done && last.turn === turn) {
401
- if (!sameTodo(last.items, items)) {
401
+ if (!sameTask(last.items, items)) {
402
402
  Object.assign(last, { header, items });
403
403
  this.#mark();
404
404
  }
@@ -478,7 +478,7 @@ export class Body {
478
478
  this.#mark();
479
479
  return { kind: "toggled" };
480
480
  }
481
- // W20: the LIVE todo block toggles in place too — the capped
481
+ // W20: the LIVE task block toggles in place too — the capped
482
482
  // form flips to the full list (the "done-collapse expands
483
483
  // under ctrl+r" claim). The SETTLED block is already full —
484
484
  // no toggle, and its rows carry no affordance, so it never
package/dist/render.d.ts CHANGED
@@ -11,8 +11,9 @@
11
11
  * v2a — the palette, centralized (no hard-coded codes elsewhere); v5
12
12
  * (TUI v5 #16e, the v4.1 design): the decorative blue (38;5;75) is
13
13
  * RETIRED — the identity accents (the you> prompt, the banner tagline,
14
- * ✓ marks, slash-command names, the ▍ user rail, the input brick) are
15
- * bright-white BOLD (SGR 1); `code` is the content semantic tint for
14
+ * ✓ marks, slash-command names, the input brick) are bright-white BOLD
15
+ * (SGR 1); the user message is the SGR-7 chip (the 2026-08-09 ruling
16
+ * retired the ▍ rail); `code` is the content semantic tint for
16
17
  * inline code spans in assistant text (256-color 110 — the cube color
17
18
  * nearest the design's #8fb4d8); red for errors, dim for metadata,
18
19
  * green for the diff additions. NO_COLOR set, or a non-TTY output →
@@ -246,6 +247,10 @@ export interface RecapStats {
246
247
  readonly tools: number;
247
248
  readonly edits: number;
248
249
  readonly usage: RunUsage;
250
+ /** R-C item 4: the per-turn cache miss (min(prevIn, in) − cacheRead),
251
+ * passed only when above the noise floor — the re-sent-uncached
252
+ * prefix. Absent → the recap bytes stay the historical form. */
253
+ readonly missed?: number;
249
254
  readonly ctxLeftPct: number | null;
250
255
  /** W19 — the mode the turn ran under. Under "plan" the recap becomes
251
256
  * the way-forward row (the claimed shape): a plan turn's currency is
package/dist/render.js CHANGED
@@ -413,7 +413,8 @@ export function renderRecap(s) {
413
413
  if (seg !== "")
414
414
  parts.push(seg);
415
415
  if (s.usage.cache !== null && s.usage.in !== null && s.usage.in > 0) {
416
- parts.push(`cache ${Math.round((s.usage.cache / s.usage.in) * 100)}%`);
416
+ const hit = `cache ${Math.round((s.usage.cache / s.usage.in) * 100)}%`;
417
+ parts.push(s.missed !== undefined && s.missed > 0 ? `${hit} · miss ${kUnit(s.missed)}` : hit);
417
418
  }
418
419
  }
419
420
  if (s.ctxLeftPct !== null)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-tui",
3
- "version": "0.1.38",
3
+ "version": "0.1.40",
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",