@vincemakes/kiso-tui-cells 0.39.0 → 0.39.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.
@@ -318,6 +318,18 @@ export declare function formatDuration(totalSeconds: number): string;
318
318
  * seconds, because a clock the user is watching tick should not stop
319
319
  * ticking. */
320
320
  export declare function elapsedLabel(totalSeconds: number): string;
321
+ /** The SETTLED call's duration — R13's grammar (`exit 0 · 90 lines ·
322
+ * 0.4s`). A finished call knows its length to a tenth, which is
323
+ * information a running clock cannot have and a finished one should not
324
+ * throw away: under a minute the tenth stays.
325
+ *
326
+ * Past a minute the tenth stops being the interesting digit and the
327
+ * live label takes over — a twelve-minute call settled as `734.2s`, the
328
+ * same unreadable four-figure count the status row had. NOTHING under a
329
+ * minute moves, which is every duration the suite pinned before this.
330
+ * The suffix is part of the label (the live form's already is), so no
331
+ * caller appends its own `s`. */
332
+ export declare function settledLabel(totalSeconds: number): string;
321
333
  /** The status container's row: the status text (+ the tail) with the
322
334
  * right-aligned "/ commands · ↑ history" hint in the idle state —
323
335
  * the hint CUT FIRST when the width is short (the #16g rule); when
@@ -644,7 +644,7 @@ class ToolExecution {
644
644
  out.push(...toolBlockBody(c, W, ctx));
645
645
  return out;
646
646
  }
647
- const elapsed = c.startedAt !== null && c.doneAt !== null ? ((c.doneAt - c.startedAt) / 1000).toFixed(1) : "?";
647
+ const elapsed = c.startedAt !== null && c.doneAt !== null ? settledLabel((c.doneAt - c.startedAt) / 1000) : "?s";
648
648
  // TUI2-R1.5 ⑤ (VD-6): the line count is stated EXACTLY ONCE. Every
649
649
  // read card carried it twice — `(2 lines, 0.0s) · 2 lines · ctrl+o
650
650
  // expands` — because the parens and the suffix were written by
@@ -726,7 +726,7 @@ class ToolExecution {
726
726
  // which let a narrow row cut the affordance off entirely.
727
727
  // The tier ladder is what gives way; the key never is.
728
728
  const keySuffix = c.expanded ? ` · ${COLLAPSE_ROW}` : "";
729
- const words = pickTier([join(meta, counted, `${elapsed}s`, attr), join(meta, counted, `${elapsed}s`), join(meta, `${elapsed}s`), meta], W - visibleWidth(noteIndent()) - keySuffix.length);
729
+ const words = pickTier([join(meta, counted, elapsed, attr), join(meta, counted, elapsed), join(meta, elapsed), meta], W - visibleWidth(noteIndent()) - keySuffix.length);
730
730
  // DC-50 / R14 — ONE CARD, ONE SKELETON, expanded or not.
731
731
  //
732
732
  // The expanded card used to take a DIFFERENT shape: its head
@@ -780,7 +780,7 @@ class ToolExecution {
780
780
  // ("compil 1s"). The head is cut against the room the duration
781
781
  // leaves; the duration then rides the row, always legible.
782
782
  const elapsed = c.startedAt !== null ? Math.max(1, Math.round((ctx.now - c.startedAt) / 1000)) : 1;
783
- const dur = ` · ${elapsed}s`;
783
+ const dur = ` · ${elapsedLabel(elapsed)}`;
784
784
  // R3 (design §5.2): a running command BREATHES — one glyph, seven
785
785
  // greys, bottoming out on the ground's dim token (§2.2 applies
786
786
  // mid-animation, not just at rest). The quadrant spinner it
@@ -954,18 +954,18 @@ function settledHeadText(verbCol, target, meta, attr, elapsed, room, counted = "
954
954
  // first, then the count, and the core — what happened and how long it
955
955
  // took — is never cut open; below that the target itself truncates.
956
956
  const join = (...xs) => xs.filter((x) => x !== "").join(" · ");
957
- const core = join(meta, counted, `${elapsed}s`);
958
- const withAttr = join(meta, counted, `${elapsed}s`, attr.replace(" · ", ""));
957
+ const core = join(meta, counted, elapsed);
958
+ const withAttr = join(meta, counted, elapsed, attr.replace(" · ", ""));
959
959
  const lead = `${verbCol} `;
960
960
  const row = (tail) => `${lead}${target}${tail === "" ? "" : ` · ${tail}`}`;
961
961
  // 1. everything; 2. the attribution gives way; 2b. the COUNT gives
962
962
  // way next (pin 4), where the suffix is not already carrying it —
963
963
  // the target whole through all three
964
- const whole = firstFit([row(withAttr), row(core), row(join(meta, `${elapsed}s`))], room);
964
+ const whole = firstFit([row(withAttr), row(core), row(join(meta, elapsed))], room);
965
965
  if (whole !== null)
966
966
  return whole;
967
967
  // 3. the target truncates, the core stays whole
968
- const stem = join(meta, `${elapsed}s`);
968
+ const stem = join(meta, elapsed);
969
969
  const budget = room - visibleWidth(lead) - visibleWidth(stem) - 4; // the ellipsis + " · "
970
970
  if (budget >= 1)
971
971
  return `${lead}${widthCut(target, budget)}… · ${stem}`;
@@ -975,7 +975,7 @@ function settledHeadText(verbCol, target, meta, attr, elapsed, room, counted = "
975
975
  // no bracket to leave open, so the reason retired with the
976
976
  // parentheses and pin 4's own rule applies at every width: what
977
977
  // happened and how long it took is never cut away.
978
- const floor = `${elapsed}s`;
978
+ const floor = elapsed;
979
979
  const left = room - visibleWidth(lead) - visibleWidth(floor) - 4; // the ellipsis + " · "
980
980
  if (left >= 1)
981
981
  return `${lead}${widthCut(target, left)}… · ${floor}`;
@@ -1820,6 +1820,21 @@ export function formatDuration(totalSeconds) {
1820
1820
  export function elapsedLabel(totalSeconds) {
1821
1821
  return duration(totalSeconds, true);
1822
1822
  }
1823
+ /** The SETTLED call's duration — R13's grammar (`exit 0 · 90 lines ·
1824
+ * 0.4s`). A finished call knows its length to a tenth, which is
1825
+ * information a running clock cannot have and a finished one should not
1826
+ * throw away: under a minute the tenth stays.
1827
+ *
1828
+ * Past a minute the tenth stops being the interesting digit and the
1829
+ * live label takes over — a twelve-minute call settled as `734.2s`, the
1830
+ * same unreadable four-figure count the status row had. NOTHING under a
1831
+ * minute moves, which is every duration the suite pinned before this.
1832
+ * The suffix is part of the label (the live form's already is), so no
1833
+ * caller appends its own `s`. */
1834
+ export function settledLabel(totalSeconds) {
1835
+ const tenths = Math.max(0, Math.round(totalSeconds * 10) / 10);
1836
+ return tenths < 60 ? `${tenths.toFixed(1)}s` : elapsedLabel(tenths);
1837
+ }
1823
1838
  /**
1824
1839
  * W20 — the task checklist as STATE: ONE live block that redraws in
1825
1840
  * place (the current turn's in-place updates), settling at the turn's
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * package); the cli never imports it directly. Experimental — no
7
7
  * API-stability promise yet.
8
8
  */
9
- export { SPINNER, foldLine, foldWords, visibleWidth, bodySpacing, Container, cellComponent, foldTerms, CAP_TASK_LIVE, elapsedLabel, formatDuration, statusLine, boxTop, boxBottom, terminalPipe, type FrameCtx, type RenderLine, type Component, type BodyCell, } from "./components.js";
9
+ export { SPINNER, foldLine, foldWords, visibleWidth, bodySpacing, Container, cellComponent, foldTerms, CAP_TASK_LIVE, elapsedLabel, settledLabel, formatDuration, statusLine, boxTop, boxBottom, terminalPipe, type FrameCtx, type RenderLine, type Component, type BodyCell, } from "./components.js";
10
10
  export { editFileDiff, truncateDiff, writeFileDiff, type DiffLine, type DiffResult } from "./diff.js";
11
11
  export { pendingQueueRows } from "./components.js";
12
12
  export { charWidth, displayWidth, leadWidth, widthOf } from "./width.js";
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * package); the cli never imports it directly. Experimental — no
7
7
  * API-stability promise yet.
8
8
  */
9
- export { SPINNER, foldLine, foldWords, visibleWidth, bodySpacing, Container, cellComponent, foldTerms, CAP_TASK_LIVE, elapsedLabel, formatDuration, statusLine, boxTop, boxBottom, terminalPipe, } from "./components.js";
9
+ export { SPINNER, foldLine, foldWords, visibleWidth, bodySpacing, Container, cellComponent, foldTerms, CAP_TASK_LIVE, elapsedLabel, settledLabel, formatDuration, statusLine, boxTop, boxBottom, terminalPipe, } from "./components.js";
10
10
  export { editFileDiff, truncateDiff, writeFileDiff } from "./diff.js";
11
11
  // W22 (the v8 input round): the pending-queue chips — the SAME
12
12
  // UserMessage chip with the □ gutter, pre-rendered above the input
package/dist/strings.d.ts CHANGED
@@ -188,15 +188,7 @@ export declare function keysSheetRows(W: number): string[];
188
188
  * sheet renders, joined — so the two can disagree only by deleting a
189
189
  * test. The sheet is the readable form; this is the greppable one. */
190
190
  export declare function keysHelpRow(): string;
191
- /**
192
- * KC3.5 slice ⓪ (the extraction) — the /help command table.
193
- *
194
- * The rows were eight bodyLog calls in the CLI's dispatcher; they are
195
- * presentation, and presentation belongs here (the KC3 §1 pattern —
196
- * the FLOW, which is "print these on the chain, then re-prompt", stays
197
- * in dispatch.ts). The last row carries its own newline exactly as it
198
- * did inline: bodyLog splits on \n, so `exit` and `keys` land as two
199
- * rows from one call — the shape the KC1/KC2/KC3 gestures were added
200
- * to, unchanged.
201
- */
191
+ /** The slash commands `/help` lists. The keys and the `!` gestures in the
192
+ * table are not commands and are not here. */
193
+ export declare function slashCommandNames(): readonly string[];
202
194
  export declare function helpRows(): string[];
package/dist/strings.js CHANGED
@@ -430,51 +430,65 @@ export function keysHelpRow() {
430
430
  * rows from one call — the shape the KC1/KC2/KC3 gestures were added
431
431
  * to, unchanged.
432
432
  */
433
+ /** The rows `/help` prints, as DATA — the one place that says which
434
+ * commands exist. `helpRows` renders it; `slashCommandNames` asks it
435
+ * whether a word is a command at all, so the dispatcher's error line
436
+ * cannot drift from the list the same screen prints. */
437
+ const HELP_TABLE = [
438
+ ["/help", "print this list of commands"],
439
+ ["/think", "show the last full thinking block"],
440
+ ["/last", "show the most recent tool call's input and output"],
441
+ // R4 (C4d): a committed row is the terminal's, and cannot be
442
+ // re-wrapped in place (ADR-0046) — this appends it re-folded.
443
+ ["/rewrap", "re-print the recent prose at the current width"],
444
+ ["/copy", "copy the last answer (raw markdown) — ctrl+x does the same"],
445
+ ["/status", "show session id, event count, and context estimate"],
446
+ // A command with no row is a command nobody can find. `/context` has
447
+ // been dispatchable since TUI2-R1 slice 6 and was never listed here,
448
+ // so the only way to learn it existed was to read the source.
449
+ ["/context", "show where the context went — the per-request rent ledger"],
450
+ ["/mode", "show the approval tier; /mode <name> switches (manual/default/accept-edits/plan/bypass)"],
451
+ ["/model", "list model profiles; /model <name|provider/model> switches"],
452
+ ["/compact", "summarize the older conversation to free context"],
453
+ ["/clear", "start a fresh conversation (the old session stays resumable)"],
454
+ ["/resume", "switch to another session; /resume <id> goes directly"],
455
+ // §2.5: the conversation is untouched — this rereads what kiso was
456
+ // built with, not what it has said.
457
+ ["/reload", "reread extensions, skills and config into this session"],
458
+ // §2.2: the two shell gestures and their one escape. They sit
459
+ // beside the slash commands because that is what a reader is
460
+ // looking for when they look here, even though `!` is not one.
461
+ // §2.3: the switch belongs beside ctrl+o's job, and a gesture the
462
+ // sheet does not name is a gesture nobody uses (DC-30, DC-36).
463
+ ["ctrl+t", "fold the thinking blocks, and fold them back"],
464
+ // §2.4: the composer, in your own editor. It names the variables
465
+ // because that is what a reader has to set for it to work.
466
+ ["ctrl+g", "edit the composer in $VISUAL or $EDITOR — the text comes back unsent"],
467
+ ["!<cmd>", "run a shell command and send it with its output as your turn"],
468
+ ["!!<cmd>", "run one and show it here only — the model never sees it"],
469
+ ["\\!", "send a line that really starts with ! (the only escape)"],
470
+ ["exit", "leave the session"],
471
+ // TUI2-R1 (D): the SENTENCE is deliberately unchanged. Deriving it
472
+ // from KEY_BINDINGS would be an improvement and it would also move
473
+ // an assertion outside the round's declared supersession classes,
474
+ // so the sheet stays the derived surface and this row keeps its
475
+ // words. `keysHelpRow()` exists for the round allowed to swap it;
476
+ // until then the drift guard is the test that every binding in the
477
+ // table is mentioned here. DC-1 changes the PADDING, not the words.
478
+ ["keys", "enter sends \u00b7 ctrl+J newline (shift+enter where encoded) \u00b7 esc stops the run \u00b7 alt+\u23ce stops it and sends this instead \u00b7 @ files \u00b7 1-4 answers an ask"],
479
+ ];
480
+ /** The slash commands `/help` lists. The keys and the `!` gestures in the
481
+ * table are not commands and are not here. */
482
+ export function slashCommandNames() {
483
+ return HELP_TABLE.map(([name]) => name).filter((n) => n.startsWith("/"));
484
+ }
433
485
  export function helpRows() {
434
486
  const p = palette();
435
487
  // DC-1: ONE description column. The gap used to be four spaces after
436
488
  // the name whatever the name's length, so `/help`'s description began
437
489
  // three columns left of `/compact`'s and the second column wandered
438
490
  // down the list. displayWidth is the authority, as everywhere else.
439
- const table = [
440
- ["/help", "print this list of commands"],
441
- ["/think", "show the last full thinking block"],
442
- ["/last", "show the most recent tool call's input and output"],
443
- // R4 (C4d): a committed row is the terminal's, and cannot be
444
- // re-wrapped in place (ADR-0046) — this appends it re-folded.
445
- ["/rewrap", "re-print the recent prose at the current width"],
446
- ["/copy", "copy the last answer (raw markdown) — ctrl+x does the same"],
447
- ["/status", "show session id, event count, and context estimate"],
448
- ["/mode", "show the approval tier; /mode <name> switches (manual/default/accept-edits/plan/bypass)"],
449
- ["/model", "list model profiles; /model <name|provider/model> switches"],
450
- ["/compact", "summarize the older conversation to free context"],
451
- ["/clear", "start a fresh conversation (the old session stays resumable)"],
452
- ["/resume", "switch to another session; /resume <id> goes directly"],
453
- // §2.5: the conversation is untouched — this rereads what kiso was
454
- // built with, not what it has said.
455
- ["/reload", "reread extensions, skills and config into this session"],
456
- // §2.2: the two shell gestures and their one escape. They sit
457
- // beside the slash commands because that is what a reader is
458
- // looking for when they look here, even though `!` is not one.
459
- // §2.3: the switch belongs beside ctrl+o's job, and a gesture the
460
- // sheet does not name is a gesture nobody uses (DC-30, DC-36).
461
- ["ctrl+t", "fold the thinking blocks, and fold them back"],
462
- // §2.4: the composer, in your own editor. It names the variables
463
- // because that is what a reader has to set for it to work.
464
- ["ctrl+g", "edit the composer in $VISUAL or $EDITOR — the text comes back unsent"],
465
- ["!<cmd>", "run a shell command and send it with its output as your turn"],
466
- ["!!<cmd>", "run one and show it here only — the model never sees it"],
467
- ["\\!", "send a line that really starts with ! (the only escape)"],
468
- ["exit", "leave the session"],
469
- // TUI2-R1 (D): the SENTENCE is deliberately unchanged. Deriving it
470
- // from KEY_BINDINGS would be an improvement and it would also move
471
- // an assertion outside the round's declared supersession classes,
472
- // so the sheet stays the derived surface and this row keeps its
473
- // words. `keysHelpRow()` exists for the round allowed to swap it;
474
- // until then the drift guard is the test that every binding in the
475
- // table is mentioned here. DC-1 changes the PADDING, not the words.
476
- ["keys", "enter sends \u00b7 ctrl+J newline (shift+enter where encoded) \u00b7 esc stops the run \u00b7 alt+\u23ce stops it and sends this instead \u00b7 @ files \u00b7 1-4 answers an ask"],
477
- ];
491
+ const table = HELP_TABLE;
478
492
  const stop = Math.max(...table.map(([name]) => displayWidth(name))) + 4;
479
493
  const cmd = (name, desc) => `${p.bold}${name}${p.reset}${" ".repeat(stop - displayWidth(name))}${desc}`;
480
494
  const rows = table.slice(0, -2).map(([name, desc]) => cmd(name, desc));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-tui-cells",
3
- "version": "0.39.0",
3
+ "version": "0.39.2",
4
4
  "description": "kiso tui-cells \u2014 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",