@vincemakes/kiso-tui-cells 0.16.7 → 0.17.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 +82 -0
- package/dist/components.js +304 -23
- package/package.json +1 -1
package/dist/components.d.ts
CHANGED
|
@@ -280,7 +280,89 @@ export declare function exploreRows(parts: readonly {
|
|
|
280
280
|
name: string;
|
|
281
281
|
subjects: readonly string[];
|
|
282
282
|
}[], W: number): string[];
|
|
283
|
+
/** Does this tool's fold term count distinct targets rather than calls? */
|
|
284
|
+
export declare function foldCountsObjects(name: string): boolean;
|
|
283
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
|
+
/** A9 — the human's words, on a QUIET turn's fold only. */
|
|
359
|
+
readonly words?: string;
|
|
360
|
+
/** the live mark; the caller passes the spinner's current frame. */
|
|
361
|
+
readonly mark?: string;
|
|
362
|
+
}
|
|
363
|
+
export declare function stretchLine(t: StretchTerms & {
|
|
364
|
+
readonly phase: "thinking" | "acting" | "settled";
|
|
365
|
+
}, W: number): string[];
|
|
284
366
|
export declare function turnFold(t: {
|
|
285
367
|
words: string;
|
|
286
368
|
thoughtSeconds: number;
|
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).
|
|
@@ -920,7 +932,14 @@ export function exploreCounts(parts) {
|
|
|
920
932
|
return parts
|
|
921
933
|
.map((part) => {
|
|
922
934
|
const [singular, plural] = EXPLORE_NOUN[part.name] ?? ["call", "calls"];
|
|
923
|
-
|
|
935
|
+
// R3h (fable, 2026-08-29): DISTINCT subjects. This counted calls
|
|
936
|
+
// while exploreRows — the very next function, the expansion of
|
|
937
|
+
// THIS row — deduped them with `×N`. So the head said "6 files"
|
|
938
|
+
// over a list showing four, one of them `a.ts ×3`. The head and
|
|
939
|
+
// the body are two views of one run and must count alike; the
|
|
940
|
+
// body was right (a file read twice is one file).
|
|
941
|
+
const n = foldCountsObjects(part.name) ? new Set(part.subjects).size : part.subjects.length;
|
|
942
|
+
return `${n} ${n === 1 ? singular : plural}`;
|
|
924
943
|
})
|
|
925
944
|
.join(" · ");
|
|
926
945
|
}
|
|
@@ -965,12 +984,20 @@ function countTerm(n, singular, plural) {
|
|
|
965
984
|
* as first-call-order terms (the ROLLUP_NOUN plurals when the tool opts
|
|
966
985
|
* in, the verb + "s" otherwise).
|
|
967
986
|
* A9 (ruling R2, mock A): the user chip rides the fold — the human's
|
|
968
|
-
* words LEAD the one line,
|
|
969
|
-
*
|
|
970
|
-
*
|
|
971
|
-
*
|
|
972
|
-
*
|
|
973
|
-
*
|
|
987
|
+
* words LEAD the one line, `✦ <chip> · thought 19s · read 5 files` —
|
|
988
|
+
* the chip the SAME SGR-7 bracket as the live user row (#16f, side
|
|
989
|
+
* pads included). The words take the fold's width budget and width-cut
|
|
990
|
+
* at the end with the honest "…" (never a silent truncate — invariant
|
|
991
|
+
* ① holds on the ONE row by construction).
|
|
992
|
+
*
|
|
993
|
+
* DECLARED SUPERSESSION (R3g, 2026-08-28) — A9 also ruled that "the
|
|
994
|
+
* metadata terms give way LAST". They do not any more: the KEY does.
|
|
995
|
+
* A9 was taken when this line carried no key, and at a width where the
|
|
996
|
+
* chip, the full metadata and " · ctrl+r" cannot coexist, a fold with
|
|
997
|
+
* no key is the turn's work behind a line with no way back to it. So
|
|
998
|
+
* the order is now words, then metadata, then — never — the key. The
|
|
999
|
+
* glyph is ✦ and the zero terms are dropped (R3b), so the example above
|
|
1000
|
+
* is written as the code renders it rather than as A9 first wrote it. */
|
|
974
1001
|
/**
|
|
975
1002
|
* R3b — what a run of work DID, in words. One definition, because two
|
|
976
1003
|
* surfaces say it: the fold line (`turnFold`, above) and the expand
|
|
@@ -998,14 +1025,47 @@ function countTerm(n, singular, plural) {
|
|
|
998
1025
|
* commands". A tool with no entry says `3 × <verb>`, which counts calls
|
|
999
1026
|
* without inventing a noun for them.
|
|
1000
1027
|
*/
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
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"],
|
|
1008
1049
|
};
|
|
1050
|
+
const FOLD_TERM = Object.fromEntries(Object.entries(TERM).map(([name, [past, , singular, plural]]) => [name, [past, singular, plural]]));
|
|
1051
|
+
/**
|
|
1052
|
+
* R3h (fable, 2026-08-29) — WHICH TERMS COUNT OBJECTS.
|
|
1053
|
+
*
|
|
1054
|
+
* The rule, one sentence: a term that counts OBJECTS counts distinct
|
|
1055
|
+
* objects; a term that counts ACTS counts calls. `read 2 files` after
|
|
1056
|
+
* reading ONE file twice is a false sentence, and law 1.3 does not
|
|
1057
|
+
* become optional because the falsehood is small. `ran 2 searches`
|
|
1058
|
+
* after searching the same pattern twice is TRUE — the acts happened.
|
|
1059
|
+
*
|
|
1060
|
+
* The table sits beside FOLD_TERM so the two cannot drift: a tool whose
|
|
1061
|
+
* noun is a thing ("files", "directories") belongs here; a tool whose
|
|
1062
|
+
* noun is an act ("searches", "shell commands") does not.
|
|
1063
|
+
*/
|
|
1064
|
+
const FOLD_COUNTS_OBJECTS = new Set(["read_file", "edit_file", "write_file", "list_dir"]);
|
|
1065
|
+
/** Does this tool's fold term count distinct targets rather than calls? */
|
|
1066
|
+
export function foldCountsObjects(name) {
|
|
1067
|
+
return FOLD_COUNTS_OBJECTS.has(name);
|
|
1068
|
+
}
|
|
1009
1069
|
function foldTerm(name, n) {
|
|
1010
1070
|
const t = FOLD_TERM[name];
|
|
1011
1071
|
if (t === undefined)
|
|
@@ -1025,6 +1085,167 @@ export function foldTerms(reads, edits, others) {
|
|
|
1025
1085
|
}
|
|
1026
1086
|
return parts;
|
|
1027
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
|
+
const total = t.calls.reduce((n, [, c]) => n + c, 0);
|
|
1193
|
+
if (total === 1 && t.targets.length === 1) {
|
|
1194
|
+
const [name] = t.calls[0];
|
|
1195
|
+
const e = TERM[name];
|
|
1196
|
+
return [`${e === undefined ? name : e[live ? 1 : 0]} ${t.targets[0]}`];
|
|
1197
|
+
}
|
|
1198
|
+
return t.calls
|
|
1199
|
+
.filter(([, n]) => n > 0)
|
|
1200
|
+
.map(([name, n]) => {
|
|
1201
|
+
const e = TERM[name];
|
|
1202
|
+
if (e === undefined)
|
|
1203
|
+
return `${n} × ${displayVerb(name)}`;
|
|
1204
|
+
return `${e[live ? 1 : 0]} ${n} ${n === 1 ? e[2] : e[3]}`;
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
1207
|
+
/** R3i — the trouble clause: which call, and what happened, in WORDS.
|
|
1208
|
+
* Law 1.3 says an outcome is stated in words, "the only form that
|
|
1209
|
+
* survives a pipe"; the colour on this clause is emphasis over those
|
|
1210
|
+
* words, never the fact itself. */
|
|
1211
|
+
function troubleClause(t) {
|
|
1212
|
+
return t.trouble
|
|
1213
|
+
.filter(([, n]) => n > 0)
|
|
1214
|
+
.map(([kind, n, what]) => (what === "" || kind === "interrupted" ? `${n} ${kind}` : `${n} ${kind}: ${what}`))
|
|
1215
|
+
.join(" · ");
|
|
1216
|
+
}
|
|
1217
|
+
export function stretchLine(t, W) {
|
|
1218
|
+
const p = palette();
|
|
1219
|
+
const live = t.phase !== "settled";
|
|
1220
|
+
const mark = t.phase === "settled" ? `${p.bold}✦${p.reset}` : `${p.dim}${t.mark ?? "✧"}${p.reset}`;
|
|
1221
|
+
const key = t.phase === "settled" ? " · ctrl+r" : "";
|
|
1222
|
+
const lead = t.phase === "thinking" ? [`thinking ${t.thoughtSeconds}s`] : t.phase === "settled" && t.thoughtSeconds > 0 ? [`thought ${t.thoughtSeconds}s`] : [];
|
|
1223
|
+
const clauseText = troubleClause(t);
|
|
1224
|
+
let meta = [...lead, ...(t.phase === "thinking" ? [] : stretchTerms(t, live))].join(" · ");
|
|
1225
|
+
let clause = clauseText === "" ? "" : ` · ${clauseText}`;
|
|
1226
|
+
let words = t.words === undefined || t.words === "" ? "" : ` ${escapeTerminal(t.words).replace(/\s+/g, " ")} `;
|
|
1227
|
+
const width = () => 2 + (words === "" ? 0 : visibleWidth(words) + 3) + visibleWidth(meta) + visibleWidth(clause) + visibleWidth(key);
|
|
1228
|
+
const trim = (text, room, floor) => (room >= floor ? `${widthCut(text, room)}…` : "");
|
|
1229
|
+
if (words !== "" && width() > W)
|
|
1230
|
+
words = trim(words, W - (width() - visibleWidth(words)) - 1, 4);
|
|
1231
|
+
if (width() > W) {
|
|
1232
|
+
for (const [long, short] of STRETCH_COMPACT) {
|
|
1233
|
+
meta = meta.replaceAll(long, short);
|
|
1234
|
+
if (width() <= W)
|
|
1235
|
+
break;
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
if (width() > W) {
|
|
1239
|
+
const room = W - (width() - visibleWidth(meta)) - 1;
|
|
1240
|
+
meta = room >= 2 ? `${widthCut(meta, room)}…` : "…";
|
|
1241
|
+
}
|
|
1242
|
+
if (width() > W && clause !== "")
|
|
1243
|
+
clause = trim(clause, W - (width() - visibleWidth(clause)) - 1, 5);
|
|
1244
|
+
// the degenerate floor: below the width where even the mark and one
|
|
1245
|
+
// character fit, the row is a hard cut of what it would have said.
|
|
1246
|
+
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}`}`;
|
|
1247
|
+
return [visibleWidth(row) <= W ? row : cutLine(row, W)];
|
|
1248
|
+
}
|
|
1028
1249
|
export function turnFold(t, W) {
|
|
1029
1250
|
const p = palette();
|
|
1030
1251
|
// R3b (owner, 2026-08-27): ZERO TERMS ARE DROPPED. W14 always wrote
|
|
@@ -1032,7 +1253,12 @@ export function turnFold(t, W) {
|
|
|
1032
1253
|
// happen — on a segment fold, where a run is usually all reads or all
|
|
1033
1254
|
// edits, half the row was the half that said nothing. A term earns its
|
|
1034
1255
|
// place by having a count.
|
|
1035
|
-
|
|
1256
|
+
// R3h (fable, 2026-08-29): the THOUGHT term obeys the same zero-drop
|
|
1257
|
+
// rule every other term got at R3b. It was exempt by accident — it
|
|
1258
|
+
// was written before the rule — so a model that emits no thinking
|
|
1259
|
+
// folded every turn of its life under `thought 0s`, a sentence about
|
|
1260
|
+
// something that did not happen, in the lead position.
|
|
1261
|
+
const meta = [...(t.thoughtSeconds > 0 ? [`thought ${t.thoughtSeconds}s`] : []), ...foldTerms(t.reads, t.edits, t.others)].join(" · ");
|
|
1036
1262
|
// R3f: the same one-row rule. This one is worse than the thinking
|
|
1037
1263
|
// fold's — the fold is a COMMITTED row, so a multi-line user message
|
|
1038
1264
|
// would desync the committed-line accounting permanently rather than
|
|
@@ -1056,6 +1282,29 @@ export function turnFold(t, W) {
|
|
|
1056
1282
|
// words was unreachable by the key its siblings advertise. Found by
|
|
1057
1283
|
// an independent review (fable) and then by the paced-rollup gate,
|
|
1058
1284
|
// which got `kind: "none"` back from expandNext.
|
|
1285
|
+
// R3h: the COMPACT tier. The owner's own sentence — "thought 17s ·
|
|
1286
|
+
// read 4 files · listed 1 directory · ran 4 shell commands" — is 81
|
|
1287
|
+
// cells with the key at W=80: one over, so the tail was cut off the
|
|
1288
|
+
// exact shape this round exists to produce. The nouns shorten before
|
|
1289
|
+
// anything is lost, which is the same "degrade, never truncate"
|
|
1290
|
+
// ladder every other row here walks.
|
|
1291
|
+
// A LADDER, cheapest first, and it stops as soon as the row fits: the
|
|
1292
|
+
// owner's canonical sentence is ONE cell over at W=80, and spending
|
|
1293
|
+
// every substitution to buy one cell would shorten words that had
|
|
1294
|
+
// room. "directory" is the cheapest to lose (nobody misreads "dir");
|
|
1295
|
+
// "commands" is next and still says what happened.
|
|
1296
|
+
const COMPACT = [
|
|
1297
|
+
["directories", "dirs"],
|
|
1298
|
+
["directory", "dir"],
|
|
1299
|
+
["shell commands", "commands"],
|
|
1300
|
+
["shell command", "command"],
|
|
1301
|
+
];
|
|
1302
|
+
const compactAll = (text) => {
|
|
1303
|
+
let out = text;
|
|
1304
|
+
for (const [long, short] of COMPACT)
|
|
1305
|
+
out = out.replaceAll(long, short);
|
|
1306
|
+
return out;
|
|
1307
|
+
};
|
|
1059
1308
|
const KEY = " · ctrl+r";
|
|
1060
1309
|
const keyW = KEY.length;
|
|
1061
1310
|
const key = `${p.dim}${KEY}${p.reset}`;
|
|
@@ -1063,12 +1312,29 @@ export function turnFold(t, W) {
|
|
|
1063
1312
|
const keyed = `${p.bold}✦${p.reset} ${meta}${key}`;
|
|
1064
1313
|
if (visibleWidth(keyed) <= W)
|
|
1065
1314
|
return [keyed];
|
|
1066
|
-
|
|
1067
|
-
|
|
1315
|
+
let tightMeta = meta;
|
|
1316
|
+
for (const [long, short] of COMPACT) {
|
|
1317
|
+
tightMeta = tightMeta.replaceAll(long, short);
|
|
1318
|
+
const tight = `${p.bold}✦${p.reset} ${tightMeta}${key}`;
|
|
1319
|
+
if (visibleWidth(tight) <= W)
|
|
1320
|
+
return [tight];
|
|
1321
|
+
}
|
|
1322
|
+
// the meta cuts with the honest "…" — the COMPACT form of it, so a
|
|
1323
|
+
// cut row still carries as many whole terms as the width allows;
|
|
1324
|
+
// below the width where even the key fits there is nothing to
|
|
1325
|
+
// preserve and the row is a cut.
|
|
1068
1326
|
const room = W - 2 - keyW - 1;
|
|
1069
|
-
if (room < 2)
|
|
1070
|
-
|
|
1071
|
-
|
|
1327
|
+
if (room < 2) {
|
|
1328
|
+
// R3h: and below FOUR columns even that row is 4 cells wide (the
|
|
1329
|
+
// gutter, one character, the "…"), so invariant ① threw AT the
|
|
1330
|
+
// widths this branch exists to serve — the same class DC-15
|
|
1331
|
+
// closed in the thinking fold, still open here. A width with no
|
|
1332
|
+
// room for the mark is a width with nothing to say: the row is
|
|
1333
|
+
// a hard cut of what it would have said.
|
|
1334
|
+
const cut = `${p.bold}✦${p.reset} ${widthCut(tightMeta, Math.max(1, W - 3))}…`;
|
|
1335
|
+
return [visibleWidth(cut) <= W ? cut : cutLine(`${p.bold}✦${p.reset} ${tightMeta}`, W)];
|
|
1336
|
+
}
|
|
1337
|
+
return [`${p.bold}✦${p.reset} ${widthCut(tightMeta, room)}…${key}`];
|
|
1072
1338
|
}
|
|
1073
1339
|
// A9 (ruling R2, mock A): the user chip rides the fold — the human's
|
|
1074
1340
|
// words LEAD the one line, the same SGR-7 bracket as the live user
|
|
@@ -1083,14 +1349,29 @@ export function turnFold(t, W) {
|
|
|
1083
1349
|
const row = `${p.bold}✦${p.reset} ${p.rv} ${cut} ${p.rvEnd} · ${meta}${key}`;
|
|
1084
1350
|
if (visibleWidth(row) <= W)
|
|
1085
1351
|
return [row];
|
|
1352
|
+
// R3h: the same COMPACT ladder the wordless branch walks — the nouns
|
|
1353
|
+
// shorten before the words or the metadata lose anything.
|
|
1354
|
+
let chipMeta = meta;
|
|
1355
|
+
for (const [long, short] of COMPACT) {
|
|
1356
|
+
chipMeta = chipMeta.replaceAll(long, short);
|
|
1357
|
+
const tighter = `${p.bold}✦${p.reset} ${p.rv} ${cut} ${p.rvEnd} · ${chipMeta}${key}`;
|
|
1358
|
+
if (visibleWidth(tighter) <= W)
|
|
1359
|
+
return [tighter];
|
|
1360
|
+
}
|
|
1086
1361
|
// the last resort: the METADATA gives way — the words hold their
|
|
1087
1362
|
// budget, the key holds its nine cells, the meta cuts with the honest
|
|
1088
1363
|
// "…"; invariant ① never trips at ANY width (a degenerate W's fold is
|
|
1089
1364
|
// a cut, never a crash).
|
|
1090
1365
|
const room = W - 8 - keyW - visibleWidth(cut);
|
|
1091
|
-
if (room < 2)
|
|
1092
|
-
|
|
1093
|
-
|
|
1366
|
+
if (room < 2) {
|
|
1367
|
+
// R3h: the chip branch's own degenerate floor. The gutter, the
|
|
1368
|
+
// bracket's pads, the join and the "…" are ten cells before a
|
|
1369
|
+
// single character of content, so every width below ten threw
|
|
1370
|
+
// invariant ① — see the wordless branch above for the same class.
|
|
1371
|
+
const tail = `${p.bold}✦${p.reset} ${p.rv} ${cut} ${p.rvEnd} · ${widthCut(compactAll(meta), Math.max(1, W - 8 - visibleWidth(cut)))}…`;
|
|
1372
|
+
return [visibleWidth(tail) <= W ? tail : cutLine(`${p.bold}✦${p.reset} ${p.rv} ${cut} ${p.rvEnd}`, W)];
|
|
1373
|
+
}
|
|
1374
|
+
return [`${p.bold}✦${p.reset} ${p.rv} ${cut} ${p.rvEnd} · ${widthCut(compactAll(meta), room)}…${key}`];
|
|
1094
1375
|
}
|
|
1095
1376
|
// ---- the bounded-block flow contract (W7, W8, W10) ----
|
|
1096
1377
|
/** The caps — screen rows counted AFTER the fold, at the current width
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-tui-cells",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.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",
|