@vincemakes/kiso-tui-cells 0.16.8 → 0.18.0
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 +160 -0
- package/dist/components.js +297 -7
- package/dist/strings.js +3 -0
- package/package.json +1 -1
package/dist/components.d.ts
CHANGED
|
@@ -283,6 +283,117 @@ export declare function exploreRows(parts: readonly {
|
|
|
283
283
|
/** Does this tool's fold term count distinct targets rather than calls? */
|
|
284
284
|
export declare function foldCountsObjects(name: string): boolean;
|
|
285
285
|
export declare function foldTerms(reads: number, edits: number, others: readonly [string, number][]): string[];
|
|
286
|
+
/**
|
|
287
|
+
* R3i — THE STRETCH LINE: the turn's one working row, in three phases.
|
|
288
|
+
*
|
|
289
|
+
* A STRETCH is the run of thinking and tool calls between two blocks of
|
|
290
|
+
* the model's prose. While it runs it is this line plus a bounded act
|
|
291
|
+
* window; when it closes it commits as this same line, frozen, with its
|
|
292
|
+
* key. The contract in one sentence: **the line you watch is the line
|
|
293
|
+
* you keep** — the settle changes the mark, the tense and the key, and
|
|
294
|
+
* nothing else.
|
|
295
|
+
*
|
|
296
|
+
* thinking ✧ thinking 4s
|
|
297
|
+
* acting ✶ reading 6 files · running 4 shell commands
|
|
298
|
+
* settled ✦ thought 9s · read 6 files · ran 4 shell commands · ctrl+r
|
|
299
|
+
*
|
|
300
|
+
* THE GIVE-WAY LADDER, in order, because at some width everything
|
|
301
|
+
* cannot fit:
|
|
302
|
+
*
|
|
303
|
+
* 1. the human's WORDS (the A9 chip on a quiet turn) — they are on
|
|
304
|
+
* screen above, in the chip band;
|
|
305
|
+
* 2. the NOUNS compact, cheapest word first, and stop as soon as the
|
|
306
|
+
* row fits — buying one cell must not spend every substitution;
|
|
307
|
+
* 3. the COUNTS cut, with the honest "…";
|
|
308
|
+
* 4. the TROUBLE CLAUSE cuts. The design first said it never gives
|
|
309
|
+
* way, and that was unimplementable: a long clause overflows after
|
|
310
|
+
* the counts have already cut to a bare "…", and invariant ①
|
|
311
|
+
* throws on that row;
|
|
312
|
+
* 5. the KEY gives way NEVER. A fold with no key is the turn's work
|
|
313
|
+
* behind a line with no way back to it, which is the one thing
|
|
314
|
+
* this row must not be.
|
|
315
|
+
*
|
|
316
|
+
* `…` in this file means CUT HERE and nothing else — which is why the
|
|
317
|
+
* live phases carry no trailing ellipsis for in-flight, though the
|
|
318
|
+
* reference implementation uses one. The moving mark and the present
|
|
319
|
+
* tense already say it twice.
|
|
320
|
+
*/
|
|
321
|
+
/**
|
|
322
|
+
* R3i phase 5 — THE ANSWERED QUESTION'S BLOCK.
|
|
323
|
+
*
|
|
324
|
+
* A settled `ask_user` used to render ` ask_user (3 lines, 41.2s)` —
|
|
325
|
+
* an empty target and the answers discarded, though the tool_result
|
|
326
|
+
* already carried them. The owner asked for this block by pointing at
|
|
327
|
+
* one: after they answer, there is a display for that too.
|
|
328
|
+
*
|
|
329
|
+
* asked 2 questions (answered, 41.2s)
|
|
330
|
+
* │ deploy target → staging
|
|
331
|
+
* │ retry policy → give up after 3 attempts (typed)
|
|
332
|
+
*
|
|
333
|
+
* The question is dim, the join is dim, the ANSWER is at body strength
|
|
334
|
+
* — strip every escape and every fact is still there (law 1.2: colour
|
|
335
|
+
* is emphasis, never information). A typed answer says `(typed)`,
|
|
336
|
+
* because where an answer came from is a fact about it.
|
|
337
|
+
*
|
|
338
|
+
* It is WORDS, not work (law 1.7): it never folds into a stretch line,
|
|
339
|
+
* because the one thing a summary must not do is speak for the human.
|
|
340
|
+
*
|
|
341
|
+
* A result that is not the ask's own JSON yields NOTHING. This renderer
|
|
342
|
+
* reads a payload it did not write, and a guess about what it means
|
|
343
|
+
* would be a row the product cannot stand behind.
|
|
344
|
+
*/
|
|
345
|
+
export declare function askedBlock(resultText: string, seconds: number, W: number): string[];
|
|
346
|
+
export interface StretchTerms {
|
|
347
|
+
/** the segment's OWN measured thinking seconds; 0 drops the term */
|
|
348
|
+
readonly thoughtSeconds: number;
|
|
349
|
+
/** the segment's calls as [tool name, count], in first-call order.
|
|
350
|
+
* Object-counting tools are deduped by target upstream (R3h). */
|
|
351
|
+
readonly calls: readonly (readonly [string, number])[];
|
|
352
|
+
/** the targets acted on — read only when the stretch made exactly
|
|
353
|
+
* ONE call, where naming the target says everything the two rows it
|
|
354
|
+
* replaces said (see the one-call rule below). */
|
|
355
|
+
readonly targets: readonly string[];
|
|
356
|
+
/** the trouble the stretch met: [kind, count, what it was]. */
|
|
357
|
+
readonly trouble: readonly (readonly ["failed" | "denied" | "interrupted", number, string])[];
|
|
358
|
+
/** R4 — the tool names that still have a call IN FLIGHT. The tense is
|
|
359
|
+
* PER TERM, not per line: a stretch whose shell has finished while a
|
|
360
|
+
* read runs says `ran 1 shell command · reading 1 file`. The whole
|
|
361
|
+
* line used to go progressive, which the standing act slot made a
|
|
362
|
+
* visible contradiction — the gap frame put `running npm run check`
|
|
363
|
+
* directly above a row reading `(exit 0, 12.4s)`. Absent ⇒ every
|
|
364
|
+
* term takes the line's own tense, which is what the settled line
|
|
365
|
+
* wants. */
|
|
366
|
+
readonly liveNames?: readonly string[];
|
|
367
|
+
/** A9 — the human's words, on a QUIET turn's fold only. */
|
|
368
|
+
readonly words?: string;
|
|
369
|
+
/** the live mark; the caller passes the spinner's current frame. */
|
|
370
|
+
readonly mark?: string;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* R4 (C1) — the fold NAMES ITS OWN TARGET.
|
|
374
|
+
*
|
|
375
|
+
* `ctrl+r` used to be printed identically on every fold on the screen,
|
|
376
|
+
* and the key walked a ring whose order nothing on screen expressed —
|
|
377
|
+
* so the owner's report was exact: "there is no way to know which
|
|
378
|
+
* stretch it opens". The tint that marks the next target can only be
|
|
379
|
+
* drawn on a LIVE row, and every fold worth reopening is, by
|
|
380
|
+
* construction, in the scrollback where nothing can be tinted.
|
|
381
|
+
*
|
|
382
|
+
* A pointer cannot fix this either, and the bound is worth stating
|
|
383
|
+
* once: SGR mouse reports address the VIEWPORT, so a fold that has
|
|
384
|
+
* scrolled into the terminal's own scrollback is unreachable by any
|
|
385
|
+
* pointer, permanently, on the primary screen. The ordinal is not a
|
|
386
|
+
* cheaper substitute for clicking — it is the form of the affordance
|
|
387
|
+
* that reaches every fold, and it survives a pipe as characters.
|
|
388
|
+
*
|
|
389
|
+
* The number rides the KEY, inside the width ladder, so it is paid for
|
|
390
|
+
* by the same give-way order as every other span (law: the key never
|
|
391
|
+
* gives way — it just got two characters longer).
|
|
392
|
+
*/
|
|
393
|
+
export declare function stretchLine(t: StretchTerms & {
|
|
394
|
+
readonly phase: "thinking" | "acting" | "settled";
|
|
395
|
+
readonly foldKey?: number;
|
|
396
|
+
}, W: number): string[];
|
|
286
397
|
export declare function turnFold(t: {
|
|
287
398
|
words: string;
|
|
288
399
|
thoughtSeconds: number;
|
|
@@ -290,6 +401,55 @@ export declare function turnFold(t: {
|
|
|
290
401
|
edits: number;
|
|
291
402
|
others: [string, number][];
|
|
292
403
|
}, W: number): string[];
|
|
404
|
+
/**
|
|
405
|
+
* R4 — the standing act slot.
|
|
406
|
+
*
|
|
407
|
+
* The stretch's ONE line sits above it; this is the region under it,
|
|
408
|
+
* and it STANDS: allocated when the stretch opens, released at the
|
|
409
|
+
* fold.
|
|
410
|
+
*
|
|
411
|
+
* R3i built the same window INTERMITTENTLY — a running call got its
|
|
412
|
+
* fixed 1+3 block (W8), a finished one got nothing — so the live
|
|
413
|
+
* region's height was a function of how many calls happened to be in
|
|
414
|
+
* flight this frame. Over one real stretch that is 2 rows, then 7,
|
|
415
|
+
* then 2, then 17 for a three-call batch, then 2 again, and every
|
|
416
|
+
* transition scrolls everything above it. The owner's report was that
|
|
417
|
+
* the screen "keeps jumping", and it was an accurate description of
|
|
418
|
+
* the design, not a defect in its execution.
|
|
419
|
+
*
|
|
420
|
+
* The cure is not a smaller window, it is a STANDING one: between two
|
|
421
|
+
* calls the slot keeps the call that just finished rather than
|
|
422
|
+
* collapsing, and before any call it keeps the thinking that is
|
|
423
|
+
* producing them — which is R3i ruling 5 ("thinking belongs on the
|
|
424
|
+
* stretch line, IN THE ACT WINDOW, and in full in expansions") finally
|
|
425
|
+
* wired, since R3i stated it while building no window for the thinking
|
|
426
|
+
* phase to live in.
|
|
427
|
+
*
|
|
428
|
+
* Four rows, deliberately the same 1+3 shape W8 gave a running call, so
|
|
429
|
+
* the commonest frame — exactly one call in flight — renders byte-for-
|
|
430
|
+
* byte what 0.17.0 shipped.
|
|
431
|
+
*/
|
|
432
|
+
export declare const ACT_SLOT_ROWS = 4;
|
|
433
|
+
/**
|
|
434
|
+
* R4 — the slot's body rows: the tail of `text`, newest at the BOTTOM,
|
|
435
|
+
* bottom-padded to exactly `rows`.
|
|
436
|
+
*
|
|
437
|
+
* The same dim │ gutter a running call's window uses (W2's table), and
|
|
438
|
+
* the same two VD-4 rules: leading blank gutters are skipped, and the
|
|
439
|
+
* short-output pad goes at the BOTTOM so output starts under its own
|
|
440
|
+
* header and grows downward. The slot's CONTENTS change; its shape
|
|
441
|
+
* does not.
|
|
442
|
+
*/
|
|
443
|
+
export declare function slotTail(text: string, W: number, rows: number): string[];
|
|
444
|
+
/** R4 — clamp or pad assembled slot rows to EXACTLY `rows`. The padding
|
|
445
|
+
* is what makes the slot stand; the clamp is what keeps the slot from
|
|
446
|
+
* ever being the thing that trips the force-commit cap (a slot that
|
|
447
|
+
* could overflow would commit real cells to relieve blank rows). */
|
|
448
|
+
export declare function slotPad(content: readonly string[], rows: number): string[];
|
|
449
|
+
/** R4 — the slot's overflow row: the calls in flight beyond the head
|
|
450
|
+
* budget. It lives INSIDE the slot (it is one of the four rows), which
|
|
451
|
+
* is what keeps a parallel burst from growing the region. */
|
|
452
|
+
export declare function moreRunningRow(n: number, W: number): string;
|
|
293
453
|
/** The approval mini-diff (W7): capped at 12 folded rows — the head +
|
|
294
454
|
* the named middle (the renderer cut — what was cut, how to expand) +
|
|
295
455
|
* the tail. The rows are folded at the current width BEFORE the cap —
|
package/dist/components.js
CHANGED
|
@@ -593,6 +593,18 @@ class ToolExecution {
|
|
|
593
593
|
return out;
|
|
594
594
|
}
|
|
595
595
|
if (c.state === "done") {
|
|
596
|
+
// R3i phase 5: an answered (or declined) ask_user renders its
|
|
597
|
+
// OWN block — the questions and what the human said. The row
|
|
598
|
+
// it replaces was ` ask_user (3 lines, 41.2s)`: an empty
|
|
599
|
+
// target and the answers thrown away, though the result
|
|
600
|
+
// already carried them. `askedBlock` returns [] for anything
|
|
601
|
+
// that is not the ask's own JSON, so a payload this renderer
|
|
602
|
+
// did not write can never be guessed at.
|
|
603
|
+
if (c.name === "ask_user" && c.reason === null && !c.isError) {
|
|
604
|
+
const asked = askedBlock(c.resultText, c.startedAt !== null && c.doneAt !== null ? (c.doneAt - c.startedAt) / 1000 : 0, W);
|
|
605
|
+
if (asked.length > 0)
|
|
606
|
+
return asked;
|
|
607
|
+
}
|
|
596
608
|
// W19: the pinned deny — the claimed shape verbatim: the FULL
|
|
597
609
|
// call name (the denial names the call), the target, the reason
|
|
598
610
|
// in the W4 parentheses idiom, no timing (the call never ran).
|
|
@@ -1013,14 +1025,29 @@ function countTerm(n, singular, plural) {
|
|
|
1013
1025
|
* commands". A tool with no entry says `3 × <verb>`, which counts calls
|
|
1014
1026
|
* without inventing a noun for them.
|
|
1015
1027
|
*/
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1028
|
+
/**
|
|
1029
|
+
* R3i — ONE TERM TABLE, TWO TENSES: [past, progressive, singular, plural].
|
|
1030
|
+
*
|
|
1031
|
+
* The stretch line is the same row at every instant of a turn: while
|
|
1032
|
+
* the work runs it says what it is DOING, and at the settle it says
|
|
1033
|
+
* what it DID. That sentence is only true if both tenses come from one
|
|
1034
|
+
* table. The v9 review found the alternative already happening on a
|
|
1035
|
+
* hand-written prototype — `searching 1 pattern` live against `ran 1
|
|
1036
|
+
* search` settled, the NOUN swapping at the settle, and `running 4
|
|
1037
|
+
* shells`, which is verbatim the R3g defect the previous round removed.
|
|
1038
|
+
*
|
|
1039
|
+
* FOLD_TERM below is derived from this, so the settled vocabulary
|
|
1040
|
+
* cannot drift from the live one by construction.
|
|
1041
|
+
*/
|
|
1042
|
+
const TERM = {
|
|
1043
|
+
read_file: ["read", "reading", "file", "files"],
|
|
1044
|
+
edit_file: ["edited", "editing", "file", "files"],
|
|
1045
|
+
write_file: ["wrote", "writing", "file", "files"],
|
|
1046
|
+
list_dir: ["listed", "listing", "directory", "directories"],
|
|
1047
|
+
search_text: ["ran", "running", "search", "searches"],
|
|
1048
|
+
shell: ["ran", "running", "shell command", "shell commands"],
|
|
1023
1049
|
};
|
|
1050
|
+
const FOLD_TERM = Object.fromEntries(Object.entries(TERM).map(([name, [past, , singular, plural]]) => [name, [past, singular, plural]]));
|
|
1024
1051
|
/**
|
|
1025
1052
|
* R3h (fable, 2026-08-29) — WHICH TERMS COUNT OBJECTS.
|
|
1026
1053
|
*
|
|
@@ -1058,6 +1085,198 @@ export function foldTerms(reads, edits, others) {
|
|
|
1058
1085
|
}
|
|
1059
1086
|
return parts;
|
|
1060
1087
|
}
|
|
1088
|
+
/**
|
|
1089
|
+
* R3i — THE STRETCH LINE: the turn's one working row, in three phases.
|
|
1090
|
+
*
|
|
1091
|
+
* A STRETCH is the run of thinking and tool calls between two blocks of
|
|
1092
|
+
* the model's prose. While it runs it is this line plus a bounded act
|
|
1093
|
+
* window; when it closes it commits as this same line, frozen, with its
|
|
1094
|
+
* key. The contract in one sentence: **the line you watch is the line
|
|
1095
|
+
* you keep** — the settle changes the mark, the tense and the key, and
|
|
1096
|
+
* nothing else.
|
|
1097
|
+
*
|
|
1098
|
+
* thinking ✧ thinking 4s
|
|
1099
|
+
* acting ✶ reading 6 files · running 4 shell commands
|
|
1100
|
+
* settled ✦ thought 9s · read 6 files · ran 4 shell commands · ctrl+r
|
|
1101
|
+
*
|
|
1102
|
+
* THE GIVE-WAY LADDER, in order, because at some width everything
|
|
1103
|
+
* cannot fit:
|
|
1104
|
+
*
|
|
1105
|
+
* 1. the human's WORDS (the A9 chip on a quiet turn) — they are on
|
|
1106
|
+
* screen above, in the chip band;
|
|
1107
|
+
* 2. the NOUNS compact, cheapest word first, and stop as soon as the
|
|
1108
|
+
* row fits — buying one cell must not spend every substitution;
|
|
1109
|
+
* 3. the COUNTS cut, with the honest "…";
|
|
1110
|
+
* 4. the TROUBLE CLAUSE cuts. The design first said it never gives
|
|
1111
|
+
* way, and that was unimplementable: a long clause overflows after
|
|
1112
|
+
* the counts have already cut to a bare "…", and invariant ①
|
|
1113
|
+
* throws on that row;
|
|
1114
|
+
* 5. the KEY gives way NEVER. A fold with no key is the turn's work
|
|
1115
|
+
* behind a line with no way back to it, which is the one thing
|
|
1116
|
+
* this row must not be.
|
|
1117
|
+
*
|
|
1118
|
+
* `…` in this file means CUT HERE and nothing else — which is why the
|
|
1119
|
+
* live phases carry no trailing ellipsis for in-flight, though the
|
|
1120
|
+
* reference implementation uses one. The moving mark and the present
|
|
1121
|
+
* tense already say it twice.
|
|
1122
|
+
*/
|
|
1123
|
+
/**
|
|
1124
|
+
* R3i phase 5 — THE ANSWERED QUESTION'S BLOCK.
|
|
1125
|
+
*
|
|
1126
|
+
* A settled `ask_user` used to render ` ask_user (3 lines, 41.2s)` —
|
|
1127
|
+
* an empty target and the answers discarded, though the tool_result
|
|
1128
|
+
* already carried them. The owner asked for this block by pointing at
|
|
1129
|
+
* one: after they answer, there is a display for that too.
|
|
1130
|
+
*
|
|
1131
|
+
* asked 2 questions (answered, 41.2s)
|
|
1132
|
+
* │ deploy target → staging
|
|
1133
|
+
* │ retry policy → give up after 3 attempts (typed)
|
|
1134
|
+
*
|
|
1135
|
+
* The question is dim, the join is dim, the ANSWER is at body strength
|
|
1136
|
+
* — strip every escape and every fact is still there (law 1.2: colour
|
|
1137
|
+
* is emphasis, never information). A typed answer says `(typed)`,
|
|
1138
|
+
* because where an answer came from is a fact about it.
|
|
1139
|
+
*
|
|
1140
|
+
* It is WORDS, not work (law 1.7): it never folds into a stretch line,
|
|
1141
|
+
* because the one thing a summary must not do is speak for the human.
|
|
1142
|
+
*
|
|
1143
|
+
* A result that is not the ask's own JSON yields NOTHING. This renderer
|
|
1144
|
+
* reads a payload it did not write, and a guess about what it means
|
|
1145
|
+
* would be a row the product cannot stand behind.
|
|
1146
|
+
*/
|
|
1147
|
+
export function askedBlock(resultText, seconds, W) {
|
|
1148
|
+
let parsed;
|
|
1149
|
+
try {
|
|
1150
|
+
parsed = JSON.parse(resultText);
|
|
1151
|
+
}
|
|
1152
|
+
catch {
|
|
1153
|
+
return [];
|
|
1154
|
+
}
|
|
1155
|
+
if (parsed === null || typeof parsed !== "object")
|
|
1156
|
+
return [];
|
|
1157
|
+
const asked = parsed;
|
|
1158
|
+
const p = palette();
|
|
1159
|
+
const head = (n, outcome) => cutLine(` ${p.bold}asked${p.reset} ${n} ${n === 1 ? "question" : "questions"} ${p.dim}(${outcome}, ${seconds.toFixed(1)}s)${p.reset}`, W);
|
|
1160
|
+
const row = (body) => cutLine(` ${p.dim}│${p.reset} ${body}`, W);
|
|
1161
|
+
if (Array.isArray(asked.declined) && asked.declined.length > 0) {
|
|
1162
|
+
// the honest decline record: WHAT went unanswered, and what the
|
|
1163
|
+
// choices had been — the panel already computes both.
|
|
1164
|
+
return [head(asked.declined.length, "declined"), ...asked.declined.map((q) => row(`${p.dim}${escapeTerminal(q)}${p.reset}`))];
|
|
1165
|
+
}
|
|
1166
|
+
if (!Array.isArray(asked.answers) || asked.answers.length === 0)
|
|
1167
|
+
return [];
|
|
1168
|
+
return [
|
|
1169
|
+
head(asked.answers.length, "answered"),
|
|
1170
|
+
...asked.answers.map((a) => {
|
|
1171
|
+
const q = escapeTerminal(String(a.q ?? ""));
|
|
1172
|
+
const typed = typeof a.custom === "string" && a.custom !== "";
|
|
1173
|
+
const value = typed ? a.custom : Array.isArray(a.choices) ? a.choices.join(", ") : String(a.choice ?? "");
|
|
1174
|
+
return row(`${p.dim}${q} →${p.reset} ${escapeTerminal(value)}${typed ? `${p.dim} (typed)${p.reset}` : ""}`);
|
|
1175
|
+
}),
|
|
1176
|
+
];
|
|
1177
|
+
}
|
|
1178
|
+
const STRETCH_COMPACT = [
|
|
1179
|
+
["directories", "dirs"],
|
|
1180
|
+
["directory", "dir"],
|
|
1181
|
+
["shell commands", "commands"],
|
|
1182
|
+
["shell command", "command"],
|
|
1183
|
+
];
|
|
1184
|
+
/** R3i — a stretch of exactly ONE call names its TARGET instead of its
|
|
1185
|
+
* count. `thought 2s · read 1 file` replaces two rows — the thinking
|
|
1186
|
+
* and the call — with a row that says less than either of them did,
|
|
1187
|
+
* and "thinking plus one call" is the commonest shape a narrating
|
|
1188
|
+
* model makes. This is the answer to the defect R3d killed R3b's
|
|
1189
|
+
* per-segment folds over; the "absorbs at least two rows" rule alone
|
|
1190
|
+
* does not answer it. */
|
|
1191
|
+
function stretchTerms(t, live) {
|
|
1192
|
+
// R4 — the tense is per TERM. A name with nothing in flight is in the
|
|
1193
|
+
// past whatever the line's own phase is; with liveNames absent (the
|
|
1194
|
+
// settled line) every term follows the line.
|
|
1195
|
+
const tense = (name) => (live && (t.liveNames === undefined || t.liveNames.includes(name)) ? 1 : 0);
|
|
1196
|
+
const total = t.calls.reduce((n, [, c]) => n + c, 0);
|
|
1197
|
+
// R4 — the one-call TARGET form is the SETTLED line's. Live, the act
|
|
1198
|
+
// slot directly below already names the target on its head row, so
|
|
1199
|
+
// the line was printing the same words twice, one above the other
|
|
1200
|
+
// (`running npm run check` over `shell npm run check`). Settled there
|
|
1201
|
+
// is no slot, and naming the target is strictly more than counting to
|
|
1202
|
+
// one — which is the R3i rule this keeps, where it applies.
|
|
1203
|
+
if (total === 1 && t.targets.length === 1 && !live) {
|
|
1204
|
+
const [name] = t.calls[0];
|
|
1205
|
+
const e = TERM[name];
|
|
1206
|
+
return [`${e === undefined ? name : e[0]} ${t.targets[0]}`];
|
|
1207
|
+
}
|
|
1208
|
+
return t.calls
|
|
1209
|
+
.filter(([, n]) => n > 0)
|
|
1210
|
+
.map(([name, n]) => {
|
|
1211
|
+
const e = TERM[name];
|
|
1212
|
+
if (e === undefined)
|
|
1213
|
+
return `${n} × ${displayVerb(name)}`;
|
|
1214
|
+
return `${e[tense(name)]} ${n} ${n === 1 ? e[2] : e[3]}`;
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
/** R3i — the trouble clause: which call, and what happened, in WORDS.
|
|
1218
|
+
* Law 1.3 says an outcome is stated in words, "the only form that
|
|
1219
|
+
* survives a pipe"; the colour on this clause is emphasis over those
|
|
1220
|
+
* words, never the fact itself. */
|
|
1221
|
+
function troubleClause(t) {
|
|
1222
|
+
return t.trouble
|
|
1223
|
+
.filter(([, n]) => n > 0)
|
|
1224
|
+
.map(([kind, n, what]) => (what === "" || kind === "interrupted" ? `${n} ${kind}` : `${n} ${kind}: ${what}`))
|
|
1225
|
+
.join(" · ");
|
|
1226
|
+
}
|
|
1227
|
+
/**
|
|
1228
|
+
* R4 (C1) — the fold NAMES ITS OWN TARGET.
|
|
1229
|
+
*
|
|
1230
|
+
* `ctrl+r` used to be printed identically on every fold on the screen,
|
|
1231
|
+
* and the key walked a ring whose order nothing on screen expressed —
|
|
1232
|
+
* so the owner's report was exact: "there is no way to know which
|
|
1233
|
+
* stretch it opens". The tint that marks the next target can only be
|
|
1234
|
+
* drawn on a LIVE row, and every fold worth reopening is, by
|
|
1235
|
+
* construction, in the scrollback where nothing can be tinted.
|
|
1236
|
+
*
|
|
1237
|
+
* A pointer cannot fix this either, and the bound is worth stating
|
|
1238
|
+
* once: SGR mouse reports address the VIEWPORT, so a fold that has
|
|
1239
|
+
* scrolled into the terminal's own scrollback is unreachable by any
|
|
1240
|
+
* pointer, permanently, on the primary screen. The ordinal is not a
|
|
1241
|
+
* cheaper substitute for clicking — it is the form of the affordance
|
|
1242
|
+
* that reaches every fold, and it survives a pipe as characters.
|
|
1243
|
+
*
|
|
1244
|
+
* The number rides the KEY, inside the width ladder, so it is paid for
|
|
1245
|
+
* by the same give-way order as every other span (law: the key never
|
|
1246
|
+
* gives way — it just got two characters longer).
|
|
1247
|
+
*/
|
|
1248
|
+
export function stretchLine(t, W) {
|
|
1249
|
+
const p = palette();
|
|
1250
|
+
const live = t.phase !== "settled";
|
|
1251
|
+
const mark = t.phase === "settled" ? `${p.bold}✦${p.reset}` : `${p.dim}${t.mark ?? "✧"}${p.reset}`;
|
|
1252
|
+
const key = t.phase === "settled" ? (t.foldKey === undefined ? " · ctrl+r" : ` · ctrl+r ${t.foldKey}`) : "";
|
|
1253
|
+
const lead = t.phase === "thinking" ? [`thinking ${t.thoughtSeconds}s`] : t.phase === "settled" && t.thoughtSeconds > 0 ? [`thought ${t.thoughtSeconds}s`] : [];
|
|
1254
|
+
const clauseText = troubleClause(t);
|
|
1255
|
+
let meta = [...lead, ...(t.phase === "thinking" ? [] : stretchTerms(t, live))].join(" · ");
|
|
1256
|
+
let clause = clauseText === "" ? "" : ` · ${clauseText}`;
|
|
1257
|
+
let words = t.words === undefined || t.words === "" ? "" : ` ${escapeTerminal(t.words).replace(/\s+/g, " ")} `;
|
|
1258
|
+
const width = () => 2 + (words === "" ? 0 : visibleWidth(words) + 3) + visibleWidth(meta) + visibleWidth(clause) + visibleWidth(key);
|
|
1259
|
+
const trim = (text, room, floor) => (room >= floor ? `${widthCut(text, room)}…` : "");
|
|
1260
|
+
if (words !== "" && width() > W)
|
|
1261
|
+
words = trim(words, W - (width() - visibleWidth(words)) - 1, 4);
|
|
1262
|
+
if (width() > W) {
|
|
1263
|
+
for (const [long, short] of STRETCH_COMPACT) {
|
|
1264
|
+
meta = meta.replaceAll(long, short);
|
|
1265
|
+
if (width() <= W)
|
|
1266
|
+
break;
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
if (width() > W) {
|
|
1270
|
+
const room = W - (width() - visibleWidth(meta)) - 1;
|
|
1271
|
+
meta = room >= 2 ? `${widthCut(meta, room)}…` : "…";
|
|
1272
|
+
}
|
|
1273
|
+
if (width() > W && clause !== "")
|
|
1274
|
+
clause = trim(clause, W - (width() - visibleWidth(clause)) - 1, 5);
|
|
1275
|
+
// the degenerate floor: below the width where even the mark and one
|
|
1276
|
+
// character fit, the row is a hard cut of what it would have said.
|
|
1277
|
+
const row = `${mark}${words === "" ? "" : ` ${p.rv}${words}${p.rvEnd}${p.dim} ·${p.reset}`} ${meta}${clause === "" ? "" : `${p.red}${clause}${p.reset}`}${key === "" ? "" : `${p.dim}${key}${p.reset}`}`;
|
|
1278
|
+
return [visibleWidth(row) <= W ? row : cutLine(row, W)];
|
|
1279
|
+
}
|
|
1061
1280
|
export function turnFold(t, W) {
|
|
1062
1281
|
const p = palette();
|
|
1063
1282
|
// R3b (owner, 2026-08-27): ZERO TERMS ARE DROPPED. W14 always wrote
|
|
@@ -1364,6 +1583,77 @@ function shellLiveTail(text, W) {
|
|
|
1364
1583
|
kept.push(`${p.dim}${BODY_ROW}${p.reset}`);
|
|
1365
1584
|
return [...kept, cutLine(`${p.dim}${CUT_ROW}live tail · esc stop · alt+⏎ redirect${p.reset}`, W)];
|
|
1366
1585
|
}
|
|
1586
|
+
/**
|
|
1587
|
+
* R4 — the standing act slot.
|
|
1588
|
+
*
|
|
1589
|
+
* The stretch's ONE line sits above it; this is the region under it,
|
|
1590
|
+
* and it STANDS: allocated when the stretch opens, released at the
|
|
1591
|
+
* fold.
|
|
1592
|
+
*
|
|
1593
|
+
* R3i built the same window INTERMITTENTLY — a running call got its
|
|
1594
|
+
* fixed 1+3 block (W8), a finished one got nothing — so the live
|
|
1595
|
+
* region's height was a function of how many calls happened to be in
|
|
1596
|
+
* flight this frame. Over one real stretch that is 2 rows, then 7,
|
|
1597
|
+
* then 2, then 17 for a three-call batch, then 2 again, and every
|
|
1598
|
+
* transition scrolls everything above it. The owner's report was that
|
|
1599
|
+
* the screen "keeps jumping", and it was an accurate description of
|
|
1600
|
+
* the design, not a defect in its execution.
|
|
1601
|
+
*
|
|
1602
|
+
* The cure is not a smaller window, it is a STANDING one: between two
|
|
1603
|
+
* calls the slot keeps the call that just finished rather than
|
|
1604
|
+
* collapsing, and before any call it keeps the thinking that is
|
|
1605
|
+
* producing them — which is R3i ruling 5 ("thinking belongs on the
|
|
1606
|
+
* stretch line, IN THE ACT WINDOW, and in full in expansions") finally
|
|
1607
|
+
* wired, since R3i stated it while building no window for the thinking
|
|
1608
|
+
* phase to live in.
|
|
1609
|
+
*
|
|
1610
|
+
* Four rows, deliberately the same 1+3 shape W8 gave a running call, so
|
|
1611
|
+
* the commonest frame — exactly one call in flight — renders byte-for-
|
|
1612
|
+
* byte what 0.17.0 shipped.
|
|
1613
|
+
*/
|
|
1614
|
+
export const ACT_SLOT_ROWS = 4;
|
|
1615
|
+
/**
|
|
1616
|
+
* R4 — the slot's body rows: the tail of `text`, newest at the BOTTOM,
|
|
1617
|
+
* bottom-padded to exactly `rows`.
|
|
1618
|
+
*
|
|
1619
|
+
* The same dim │ gutter a running call's window uses (W2's table), and
|
|
1620
|
+
* the same two VD-4 rules: leading blank gutters are skipped, and the
|
|
1621
|
+
* short-output pad goes at the BOTTOM so output starts under its own
|
|
1622
|
+
* header and grows downward. The slot's CONTENTS change; its shape
|
|
1623
|
+
* does not.
|
|
1624
|
+
*/
|
|
1625
|
+
export function slotTail(text, W, rows) {
|
|
1626
|
+
if (rows <= 0)
|
|
1627
|
+
return [];
|
|
1628
|
+
const p = palette();
|
|
1629
|
+
const all = blockRows(text, W);
|
|
1630
|
+
const from = all.findIndex((r) => visibleWidth(r) > visibleWidth(BODY_ROW));
|
|
1631
|
+
const body = from < 0 ? [] : all.slice(from);
|
|
1632
|
+
const kept = body.slice(Math.max(0, body.length - rows));
|
|
1633
|
+
while (kept.length < rows)
|
|
1634
|
+
kept.push(`${p.dim}${BODY_ROW}${p.reset}`);
|
|
1635
|
+
return kept;
|
|
1636
|
+
}
|
|
1637
|
+
/** R4 — clamp or pad assembled slot rows to EXACTLY `rows`. The padding
|
|
1638
|
+
* is what makes the slot stand; the clamp is what keeps the slot from
|
|
1639
|
+
* ever being the thing that trips the force-commit cap (a slot that
|
|
1640
|
+
* could overflow would commit real cells to relieve blank rows). */
|
|
1641
|
+
export function slotPad(content, rows) {
|
|
1642
|
+
if (rows <= 0)
|
|
1643
|
+
return [];
|
|
1644
|
+
const p = palette();
|
|
1645
|
+
const out = content.slice(0, rows);
|
|
1646
|
+
while (out.length < rows)
|
|
1647
|
+
out.push(`${p.dim}${BODY_ROW}${p.reset}`);
|
|
1648
|
+
return out;
|
|
1649
|
+
}
|
|
1650
|
+
/** R4 — the slot's overflow row: the calls in flight beyond the head
|
|
1651
|
+
* budget. It lives INSIDE the slot (it is one of the four rows), which
|
|
1652
|
+
* is what keeps a parallel burst from growing the region. */
|
|
1653
|
+
export function moreRunningRow(n, W) {
|
|
1654
|
+
const p = palette();
|
|
1655
|
+
return cutLine(` ${p.dim}${CUT_ROW}+${n} more running${p.reset}`, W);
|
|
1656
|
+
}
|
|
1367
1657
|
/** W12: the delegate's child sessions collapse to the tool row plus ONE
|
|
1368
1658
|
* line — the height NEVER changes (running → settled replaces the row
|
|
1369
1659
|
* in place). The running row derives from the INPUT: the parent has no
|
package/dist/strings.js
CHANGED
|
@@ -381,6 +381,9 @@ export function helpRows() {
|
|
|
381
381
|
["/help", "print this list of commands"],
|
|
382
382
|
["/think", "show the last full thinking block"],
|
|
383
383
|
["/last", "show the most recent tool call's input and output"],
|
|
384
|
+
// R4 (C4d): a committed row is the terminal's, and cannot be
|
|
385
|
+
// re-wrapped in place (ADR-0046) — this appends it re-folded.
|
|
386
|
+
["/rewrap", "re-print the recent prose at the current width"],
|
|
384
387
|
["/status", "show session id, event count, and context estimate"],
|
|
385
388
|
["/mode", "show the approval tier; /mode <name> switches (manual/default/accept-edits/plan/bypass)"],
|
|
386
389
|
["/model", "list model profiles; /model <name|provider/model> switches"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-tui-cells",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
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",
|