@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.
- package/dist/components.d.ts +4 -4
- package/dist/components.js +26 -26
- package/dist/compositor.d.ts +2 -2
- package/dist/compositor.js +9 -9
- package/dist/render.d.ts +7 -2
- package/dist/render.js +2 -1
- package/package.json +1 -1
package/dist/components.d.ts
CHANGED
|
@@ -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 "
|
|
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
|
|
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
|
|
200
|
-
/** W20 — the settled block's duration, the `2h 14m` form (the
|
|
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;
|
package/dist/components.js
CHANGED
|
@@ -167,19 +167,20 @@ export function cellComponent(cell) {
|
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
/**
|
|
170
|
-
* The user message — the
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
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
|
|
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(`${
|
|
199
|
+
rows.push(`${p.rv} ${row}${" ".repeat(pad)} ${p.rvEnd}`);
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
|
-
return rows
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 "
|
|
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 `
|
|
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
|
-
? `
|
|
794
|
-
: `
|
|
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
|
-
//
|
|
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)
|
package/dist/compositor.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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,
|
package/dist/compositor.js
CHANGED
|
@@ -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
|
|
56
|
-
* redraws when the items actually changed (the
|
|
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
|
|
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
|
|
322
|
-
// block for the turn (`
|
|
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
|
|
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
|
|
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 (!
|
|
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
|
|
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
|
|
15
|
-
*
|
|
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
|
-
|
|
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.
|
|
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",
|