@vincemakes/kiso-tui-cells 0.16.3 → 0.16.5
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/approval-panel.d.ts +4 -3
- package/dist/approval-panel.js +134 -38
- package/dist/components.d.ts +17 -10
- package/dist/components.js +130 -45
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/md.js +5 -3
- package/dist/render.d.ts +42 -5
- package/dist/render.js +155 -23
- package/dist/strings.js +4 -4
- package/package.json +1 -1
package/dist/approval-panel.d.ts
CHANGED
|
@@ -262,7 +262,7 @@ export interface PanelView {
|
|
|
262
262
|
/** The fix hint per speaker (the v8 design §3.5 table). */
|
|
263
263
|
readonly hint?: string;
|
|
264
264
|
/** The options-phase status-left text — the CLI knows the context
|
|
265
|
-
* ("
|
|
265
|
+
* ("⏸ run paused", the trust gate's line). */
|
|
266
266
|
readonly statusText: string;
|
|
267
267
|
/** The ALWAYS-verbose args — the full command/content/diff. */
|
|
268
268
|
readonly args: PanelArgs;
|
|
@@ -385,12 +385,13 @@ export declare function panelBlockLayout(view: PanelView, phase: PanelPhase, cur
|
|
|
385
385
|
*/
|
|
386
386
|
export declare function panelLead(view: PanelView, phase: PanelPhase, cursor: number): string;
|
|
387
387
|
/** The lead's plain text — the editor's reflow width (the line must
|
|
388
|
-
* fit the lead + the
|
|
388
|
+
* fit the lead + the drawn cursor's own cell — R2 retired the box and
|
|
389
|
+
* its walls with it). */
|
|
389
390
|
export declare function panelLeadPlain(view: PanelView, phase: PanelPhase, cursor: number): string;
|
|
390
391
|
export declare function panelLeadWidth(view: PanelView, phase: PanelPhase, cursor: number): number;
|
|
391
392
|
/** The status row's left text while the panel is up — the phase, not
|
|
392
393
|
* the CLI's painting status (the compositor derives it from the panel
|
|
393
|
-
* state; the "
|
|
394
|
+
* state; the "⏸ run paused" etc. ride the options phase). */
|
|
394
395
|
export declare function panelStatus(view: PanelView, phase: PanelPhase, cursor: number): string;
|
|
395
396
|
/**
|
|
396
397
|
* The status row's right-aligned hint — the v4 frame's line, verbatim.
|
package/dist/approval-panel.js
CHANGED
|
@@ -205,21 +205,52 @@ function panelRuleText(view) {
|
|
|
205
205
|
* trade to make: each option owns a row, a narrow window cuts LABELS,
|
|
206
206
|
* and every choice stays reachable at every width the product survives.
|
|
207
207
|
*
|
|
208
|
-
* The unselected row
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
208
|
+
* The unselected row is a two-space indent; the selected row is the
|
|
209
|
+
* shared selectionBar, which spends its own two cells of frame. Both
|
|
210
|
+
* build their span against W−2, so the digit column does not shift as
|
|
211
|
+
* the bar walks — a column that moves per row reads as damage, which is
|
|
212
|
+
* the R2 picker's finding, inherited.
|
|
213
|
+
*
|
|
214
|
+
* R2 — two changes. The unselected row carried the block's │ gutter: a
|
|
215
|
+
* gutter SCOPES a verbatim block (the args keep theirs), and an option
|
|
216
|
+
* list is not verbatim, so it draws a boundary the block already has a
|
|
217
|
+
* rule for. And the cursor now carries `→` as well as the bar (design
|
|
218
|
+
* §7.5) — the bar is the loud signal, the arrow is the one that
|
|
219
|
+
* survives a strip, which is law 1.3's test applied to a selection.
|
|
213
220
|
*/
|
|
214
|
-
function panelOptionRow(option, n, selected, W) {
|
|
221
|
+
function panelOptionRow(option, n, selected, W, note, stop = 0) {
|
|
215
222
|
const p = palette();
|
|
216
223
|
const room = Math.max(1, W - 2);
|
|
217
|
-
const plain =
|
|
218
|
-
const
|
|
224
|
+
const plain = optionLead(option, n, selected);
|
|
225
|
+
const tail = note === undefined || note === ""
|
|
226
|
+
? ""
|
|
227
|
+
: stop > 0
|
|
228
|
+
? `${" ".repeat(Math.max(1, stop - visibleWidth(plain)))}${p.dim}${widthCut(escapeTerminal(note), Math.max(0, room - stop))}${p.reset}`
|
|
229
|
+
: `${p.dim} — ${escapeTerminal(note)}${p.reset}`;
|
|
230
|
+
const text = cutLine(`${selected ? p.bold : ""}${escapeTerminal(plain)}${p.reset}${tail}`, room);
|
|
219
231
|
if (!selected)
|
|
220
|
-
return
|
|
232
|
+
return ` ${text}`;
|
|
221
233
|
return selectionBar(text, visibleWidth(text), W);
|
|
222
234
|
}
|
|
235
|
+
/** The row's left span, PLAIN — written once so the column arithmetic
|
|
236
|
+
* and the row cannot disagree about how wide it is. */
|
|
237
|
+
function optionLead(option, n, selected) {
|
|
238
|
+
return `${selected ? "→" : " "} ${n} ${option.label}`;
|
|
239
|
+
}
|
|
240
|
+
/** R2 — the safer list's `why` column. Same rule as the ask panel's
|
|
241
|
+
* descriptions: computed over the WHOLE list so the column belongs to
|
|
242
|
+
* the list, and 0 (the em-dash fallback) when there is no room for it. */
|
|
243
|
+
function saferStop(options, W) {
|
|
244
|
+
// an empty list has no column to compute — `Math.max()` of nothing is
|
|
245
|
+
// -Infinity, which would sail through both guards below and return a
|
|
246
|
+
// negative stop
|
|
247
|
+
if (options.length === 0)
|
|
248
|
+
return 0;
|
|
249
|
+
const room = Math.max(1, W - 2);
|
|
250
|
+
const widest = Math.max(...options.map((o, i) => visibleWidth(optionLead({ kind: "allow", label: o.command }, i + 1, false))));
|
|
251
|
+
const stop = widest + 2;
|
|
252
|
+
return stop > Math.floor(room / 2) || room - stop < 18 ? 0 : stop;
|
|
253
|
+
}
|
|
223
254
|
/** The block's rows — EXACTLY the preview's frame shape, the gutter at
|
|
224
255
|
* the left edge (the preview's two-space mock indent is its own
|
|
225
256
|
* styling; the real rows sit at column 1, like every tool cell).
|
|
@@ -229,15 +260,27 @@ export function panelBlockRows(view, phase, cursor, W, maxRows, note, safer) {
|
|
|
229
260
|
}
|
|
230
261
|
export function panelBlockLayout(view, phase, cursor, W, maxRows, note, safer) {
|
|
231
262
|
const p = palette();
|
|
232
|
-
|
|
263
|
+
// R2: the block's own PROSE rows (the risk line, the safer-options
|
|
264
|
+
// note, the affordance) take the two-space indent every other row in
|
|
265
|
+
// the block takes. The │ gutter stays where it means something — on
|
|
266
|
+
// the args, which are verbatim, and which is the whole distinction:
|
|
267
|
+
// a gutter SCOPES a quotation, it is not a left edge for a panel.
|
|
268
|
+
const gutter = " ";
|
|
233
269
|
const rows = [];
|
|
234
|
-
|
|
235
|
-
|
|
270
|
+
// R2 — the block opens and closes with the SAME dashed rule the
|
|
271
|
+
// composer uses. It used to open with the │ gutter, divide with a
|
|
272
|
+
// ─ run and close with a └ rule: three edge vocabularies inside one
|
|
273
|
+
// block, and none of them the composer's. A rule SEPARATES, a gutter
|
|
274
|
+
// SCOPES — the args keep their gutter because they are a verbatim
|
|
275
|
+
// block; everything that was drawing a boundary is one rule now.
|
|
276
|
+
rows.push(`${p.dim}${"\u2500".repeat(Math.max(0, W))}${p.reset}`);
|
|
277
|
+
rows.push(` ${cutLine(panelRuleText(view), Math.max(1, W - 2))}`);
|
|
278
|
+
rows.push(` ${cutLine(`${p.bold}${escapeTerminal(view.title)}${p.reset}`, Math.max(1, W - 2))}`);
|
|
236
279
|
// TUI2-R1.5 ⑤ (VD-11): the divider is a LABEL, not a design note. "the
|
|
237
280
|
// full args — never truncated" is a sentence about the implementation,
|
|
238
281
|
// addressed to whoever was building the panel; the human reading it
|
|
239
282
|
// during an approval wants to know what the block below is.
|
|
240
|
-
rows.push(
|
|
283
|
+
rows.push("");
|
|
241
284
|
// the args — the bounded block's body: fold, then cap. The └ cut is
|
|
242
285
|
// ONE row (the W20 discipline): when the args exceed the budget, one
|
|
243
286
|
// notice row carries the count and where the rest is (the event log).
|
|
@@ -252,7 +295,12 @@ export function panelBlockLayout(view, phase, cursor, W, maxRows, note, safer) {
|
|
|
252
295
|
// they can also read in the event log is worth less than the row that
|
|
253
296
|
// carries the choice. The args keep a floor of one row so the block
|
|
254
297
|
// never claims to show what it is asking about and then shows nothing.
|
|
255
|
-
const chrome =
|
|
298
|
+
const chrome =
|
|
299
|
+
// R2: SIX rows of frame, not five — the block opens with a rule now
|
|
300
|
+
// as well as closing with one, and the divider row became a blank.
|
|
301
|
+
// The count is the same shape it always was: every row the block
|
|
302
|
+
// spends on itself before the args and the list share what is left.
|
|
303
|
+
6 +
|
|
256
304
|
(phase === "options" && note !== undefined ? 1 : 0) +
|
|
257
305
|
(view.riskHint !== undefined && view.riskHint !== "" ? 1 : 0) +
|
|
258
306
|
(phase === "asking" ? 1 : 0) +
|
|
@@ -297,9 +345,13 @@ export function panelBlockLayout(view, phase, cursor, W, maxRows, note, safer) {
|
|
|
297
345
|
// out of would be a trap.
|
|
298
346
|
if (phase === "safer" && safer !== undefined) {
|
|
299
347
|
offset = rows.length;
|
|
348
|
+
// R2: the `why` takes a COLUMN rather than running on after an em
|
|
349
|
+
// dash — the commands are what is being chosen between, and they
|
|
350
|
+
// only scan when they all start and end at the same columns.
|
|
351
|
+
const stop = saferStop(safer.options, W);
|
|
300
352
|
for (let i = 0; i < safer.options.length; i += 1) {
|
|
301
353
|
const o = safer.options[i];
|
|
302
|
-
rows.push(panelOptionRow({ kind: "allow", label:
|
|
354
|
+
rows.push(panelOptionRow({ kind: "allow", label: o.command }, i + 1, i === safer.cursor, W, o.why, stop));
|
|
303
355
|
}
|
|
304
356
|
rows.push(panelOptionRow({ kind: "deny", label: SAFER_BACK }, safer.options.length + 1, safer.cursor === safer.options.length, W));
|
|
305
357
|
}
|
|
@@ -336,7 +388,7 @@ export function panelBlockLayout(view, phase, cursor, W, maxRows, note, safer) {
|
|
|
336
388
|
// else in the product, so a CAPPED panel emitted two elbow rows in a
|
|
337
389
|
// row meaning entirely different things. The rule reads as an edge,
|
|
338
390
|
// and the cut notice above it reads as a notice.
|
|
339
|
-
rows.push(`${p.dim}
|
|
391
|
+
rows.push(`${p.dim}${"\u2500".repeat(Math.max(0, W))}${p.reset}`);
|
|
340
392
|
return { rows, ...layout };
|
|
341
393
|
}
|
|
342
394
|
/**
|
|
@@ -354,13 +406,24 @@ export function panelLead(view, phase, cursor) {
|
|
|
354
406
|
const p = palette();
|
|
355
407
|
if (phase === "amend")
|
|
356
408
|
return `${p.dim}amend› ${p.reset}`;
|
|
357
|
-
|
|
409
|
+
// R2: an EMPTY lead emits no bytes at all — `dim + reset` around
|
|
410
|
+
// nothing is eight bytes on the composer row of every frame a panel
|
|
411
|
+
// is up, and the row it wraps has no content to style.
|
|
412
|
+
return PANEL_IDLE_LEAD === "" ? "" : `${p.dim}${PANEL_IDLE_LEAD}${p.reset}`;
|
|
358
413
|
}
|
|
359
|
-
/** The composer's lead while a selection list owns the keys
|
|
360
|
-
*
|
|
361
|
-
|
|
414
|
+
/** The composer's lead while a selection list owns the keys.
|
|
415
|
+
*
|
|
416
|
+
* R2: EMPTY. It was a quiet chevron, on the argument that it is "not a
|
|
417
|
+
* prompt for input that is not being asked for" — but the composer
|
|
418
|
+
* dropped its own chevron this round (the cursor sits at column one),
|
|
419
|
+
* so the panel would have been the one surface reintroducing the glyph
|
|
420
|
+
* the rest of the product just removed. The NAMED leads stay: `amend›`
|
|
421
|
+
* and the pick panel's `1-4>` say where the keystrokes go, which is
|
|
422
|
+
* information rather than decoration. */
|
|
423
|
+
const PANEL_IDLE_LEAD = "";
|
|
362
424
|
/** The lead's plain text — the editor's reflow width (the line must
|
|
363
|
-
* fit the lead + the
|
|
425
|
+
* fit the lead + the drawn cursor's own cell — R2 retired the box and
|
|
426
|
+
* its walls with it). */
|
|
364
427
|
export function panelLeadPlain(view, phase, cursor) {
|
|
365
428
|
return phase === "amend" ? "amend› " : PANEL_IDLE_LEAD;
|
|
366
429
|
}
|
|
@@ -369,20 +432,25 @@ export function panelLeadWidth(view, phase, cursor) {
|
|
|
369
432
|
}
|
|
370
433
|
/** The status row's left text while the panel is up — the phase, not
|
|
371
434
|
* the CLI's painting status (the compositor derives it from the panel
|
|
372
|
-
* state; the "
|
|
435
|
+
* state; the "⏸ run paused" etc. ride the options phase). */
|
|
436
|
+
// R2 (design §4, the ⏸ ruling): a panel that is WAITING ON A HUMAN says
|
|
437
|
+
// so with the one mark that means it. `▸` is the checklist's "the
|
|
438
|
+
// current one" — a mark meaning two things is worse than two marks
|
|
439
|
+
// (law 4.2), and the thing this row has to convey is not "here" but
|
|
440
|
+
// "nothing moves until you answer".
|
|
373
441
|
export function panelStatus(view, phase, cursor) {
|
|
374
442
|
// TUI2-R3v2 ③: the frames' own words — what the panel is doing, and
|
|
375
443
|
// (in the safer list) what it did.
|
|
376
444
|
if (phase === "asking")
|
|
377
|
-
return "\
|
|
445
|
+
return "\u23f8 asked the model for safer options";
|
|
378
446
|
if (phase === "safer")
|
|
379
|
-
return "\
|
|
447
|
+
return "\u23f8 asked the model for safer options";
|
|
380
448
|
// TUI2-R3v2 ①: the typed phase says where the words GO. "the words ride
|
|
381
449
|
// the verdict" described the plumbing to whoever wrote it; the human
|
|
382
450
|
// typing needs to know the model will read this and answer with a new
|
|
383
451
|
// call — which is what the v4 frame says, in those words.
|
|
384
452
|
if (phase === "amend")
|
|
385
|
-
return "
|
|
453
|
+
return "⏸ your note goes to the model — it will propose a new call";
|
|
386
454
|
return view.statusText;
|
|
387
455
|
}
|
|
388
456
|
/**
|
|
@@ -425,32 +493,60 @@ export function panelAffordance(view, phase, cursor, safer) {
|
|
|
425
493
|
export function pickBlockRows(view, state, W, maxRows) {
|
|
426
494
|
const p = palette();
|
|
427
495
|
const spec = view.pick;
|
|
428
|
-
const
|
|
429
|
-
const rows = [];
|
|
496
|
+
const rows = [`${p.dim}${"\u2500".repeat(Math.max(0, W))}${p.reset}`]; // R2: the same rule the composer and the other panels use
|
|
430
497
|
const room = Math.max(1, W - 2);
|
|
431
|
-
rows.push(
|
|
498
|
+
rows.push(` ${cutLine(`${p.bold}${escapeTerminal(spec.header.split(" \u2014 ")[0] ?? spec.header)}${p.reset}${p.dim}${escapeTerminal(spec.header.slice((spec.header.split(" \u2014 ")[0] ?? "").length))}${p.reset}`, room)}`);
|
|
432
499
|
if (spec.options.length === 0) {
|
|
433
500
|
// the honest empty state \u2014 the caller's own copy, verbatim
|
|
434
|
-
rows.push(
|
|
501
|
+
rows.push(` ${cutLine(`${p.dim} ${escapeTerminal(spec.emptyNote ?? "no options")}${p.reset}`, room)}`);
|
|
435
502
|
}
|
|
436
503
|
else {
|
|
437
|
-
// the budget: the header, the t row, the
|
|
438
|
-
|
|
504
|
+
// the budget: the OPENING rule, the header, the t row, the
|
|
505
|
+
// affordance and the CLOSING rule — five, not four. R2 added the
|
|
506
|
+
// opening rule to this block and bumped the ask panel (5→6) and the
|
|
507
|
+
// approval panel (5→6) to pay for it, and missed this one: the
|
|
508
|
+
// block ran two rows over its budget, and two rows of committed
|
|
509
|
+
// content were scrolled irreversibly into the scrollback every time
|
|
510
|
+
// `/model` opened on a tight screen. The `+N more` row is a sixth
|
|
511
|
+
// when it appears, so it is paid for too.
|
|
512
|
+
const chrome = 5 + (spec.options.length > Math.min(Math.max(1, maxRows - 5), PICK_MAX) ? 1 : 0);
|
|
513
|
+
const budget = Math.max(1, maxRows - chrome);
|
|
439
514
|
const shown = spec.options.slice(0, Math.min(budget, PICK_MAX));
|
|
515
|
+
// R2: the note takes a COLUMN, not three spaces after a label of
|
|
516
|
+
// whatever length this row happened to have, and the cursor row
|
|
517
|
+
// wears the bar and the arrow like every other list in the
|
|
518
|
+
// product. This panel was the last one still saying "selected"
|
|
519
|
+
// with bold alone.
|
|
520
|
+
const lead = (o, i, cursor) => `${cursor ? "\u2192" : " "} ${i + 1} ${escapeTerminal(o.label)}`;
|
|
521
|
+
const widest = Math.max(...shown.map((o, i) => visibleWidth(lead(o, i, false))));
|
|
522
|
+
const stop = shown.some((o) => o.note !== undefined) && widest + 2 <= Math.floor(room / 2) && room - widest - 2 >= 18 ? widest + 2 : 0;
|
|
440
523
|
for (let i = 0; i < shown.length; i += 1) {
|
|
441
524
|
const o = shown[i];
|
|
442
525
|
const mark = i === state.cursor && state.phase === "options";
|
|
443
|
-
const
|
|
444
|
-
const
|
|
445
|
-
|
|
526
|
+
const plain = lead(o, i, mark);
|
|
527
|
+
const head = `${mark ? p.bold : ""}${plain}${mark ? p.reset : ""}`;
|
|
528
|
+
const note = o.note === undefined
|
|
529
|
+
? ""
|
|
530
|
+
: stop > 0
|
|
531
|
+
? `${" ".repeat(Math.max(1, stop - visibleWidth(plain)))}${p.dim}${widthCut(escapeTerminal(o.note), Math.max(0, room - stop))}${p.reset}`
|
|
532
|
+
: `${p.dim} ${escapeTerminal(o.note)}${p.reset}`;
|
|
533
|
+
const text = cutLine(`${head}${note}`, room);
|
|
534
|
+
// ONE space, like the approval and ask panels: the bar spends a
|
|
535
|
+
// leading cell of its own, so a two-space unselected prefix
|
|
536
|
+
// moves the digit column by one as the cursor walks — the exact
|
|
537
|
+
// "a column that moves per row reads as damage" this file
|
|
538
|
+
// quotes twice as its standard.
|
|
539
|
+
rows.push(mark ? selectionBar(text, visibleWidth(text), W) : ` ${text}`);
|
|
446
540
|
}
|
|
447
541
|
if (spec.options.length > shown.length) {
|
|
448
|
-
rows.push(
|
|
542
|
+
rows.push(` ${cutLine(`${p.dim} \u2514 +${spec.options.length - shown.length} more \u2014 /model <name> takes any of them${p.reset}`, room)}`);
|
|
449
543
|
}
|
|
450
544
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
rows.push(
|
|
545
|
+
const typing = state.phase === "custom";
|
|
546
|
+
const tText = cutLine(`${typing ? p.bold : ""}${typing ? "\u2192" : " "} t ${p.reset}${p.dim}${escapeTerminal(spec.typeHint)}${p.reset}`, room);
|
|
547
|
+
rows.push(typing ? selectionBar(tText, visibleWidth(tText), W) : ` ${tText}`);
|
|
548
|
+
rows.push(` ${p.dim}${cutLine(pickAffordance(state), room)}${p.reset}`);
|
|
549
|
+
rows.push(`${p.dim}${"\u2500".repeat(Math.max(0, W))}${p.reset}`);
|
|
454
550
|
return rows;
|
|
455
551
|
}
|
|
456
552
|
/** The digits are the keys, so the list the panel offers is bounded by
|
package/dist/components.d.ts
CHANGED
|
@@ -378,20 +378,27 @@ export declare function selectionBar(styled: string, visible: number, W: number)
|
|
|
378
378
|
/**
|
|
379
379
|
* R2 — the composer's rails, and the ONE edge vocabulary.
|
|
380
380
|
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
* the
|
|
381
|
+
* R3 (owner, 2026-08-27): the rule is a SOLID hairline (`\u2500`), not
|
|
382
|
+
* the dashed `\u254c` R2 shipped, and it is solid EVERYWHERE — the
|
|
383
|
+
* composer, every panel's open and close, the band headers and the
|
|
384
|
+
* markdown rule. One line, one weight, no exceptions to remember.
|
|
385
|
+
*
|
|
386
|
+
* W6 turned two \u254c dotted rows into a rounded box, reasoning that
|
|
387
|
+
* "the box already says input lives here". That is reversed here, and
|
|
388
|
+
* the reason is not taste: a rule is a DELIMITER and a box is a
|
|
389
|
+
* CONTAINER, and the screen was carrying six edge vocabularies at once
|
|
390
|
+
* (this box, the panel's \u2502 gutter and \u2514\u2500\u2500 tail, the
|
|
391
|
+
* diff gutter, the quote's \u258f, the table's rails, the markdown
|
|
392
|
+
* rule). ONE rule replaces the ones that SEPARATE; the \u2502 gutter
|
|
393
|
+
* survives where it SCOPES.
|
|
388
394
|
*
|
|
389
395
|
* Row-neutral by construction: CHROME_ROWS is still 4, so every gate
|
|
390
|
-
* keyed on H
|
|
391
|
-
* the walls were taking.
|
|
396
|
+
* keyed on H \u2212 4 is untouched, and the input row gains the two
|
|
397
|
+
* columns the walls were taking.
|
|
392
398
|
*/
|
|
393
399
|
export declare function boxTop(W: number): string;
|
|
394
|
-
/**
|
|
400
|
+
/** R2 — the same rule below. Named for its POSITION, not its shape, so
|
|
401
|
+
* the compositor's two call sites did not have to move. */
|
|
395
402
|
export declare function boxBottom(W: number): string;
|
|
396
403
|
/** The terminal label + rhythm gap (the pipe path's v2c bytes — the
|
|
397
404
|
* exact render the passthrough needs). */
|
package/dist/components.js
CHANGED
|
@@ -22,7 +22,7 @@ import { displayWidth, visibleWidth } from "./width.js";
|
|
|
22
22
|
// KEY_BINDINGS). strings.js imports only render/width here, so this edge
|
|
23
23
|
// adds no cycle.
|
|
24
24
|
import { displayVerb } from "./strings.js";
|
|
25
|
-
import { bannerLines, escapeTerminal, foldThinking, foldResult, renderTerminalGap, renderToolSummary, toolTarget, kUnit, palette, } from "./render.js";
|
|
25
|
+
import { bannerLines, breathFrame, escapeTerminal, foldThinking, foldResult, renderTerminalGap, renderToolSummary, toolTarget, kUnit, palette, } from "./render.js";
|
|
26
26
|
// TUI2-MD: the markdown renderer's surface reaches the tui through this
|
|
27
27
|
// module (the tui's components shim re-exports it) — one import edge,
|
|
28
28
|
// and it points one way: md.ts measures with the width authority, never
|
|
@@ -171,12 +171,17 @@ export function cellComponent(cell) {
|
|
|
171
171
|
* the ▍ rail and the indent are retired — the rail's stated pipe
|
|
172
172
|
* fallback was theoretical redundancy: the CLI's pipe path is the
|
|
173
173
|
* line-mode "you>" form and never renders UserMessage). The chip folds
|
|
174
|
-
* the text at W−2 (the side pads)
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
174
|
+
* the text at W−2 (the side pads) and pads every row to the FULL width.
|
|
175
|
+
*
|
|
176
|
+
* R2 — law 1.6's recorded reversal. This used to size the block to its
|
|
177
|
+
* longest row, on the argument that "a short message like /think would
|
|
178
|
+
* paint a bar across the terminal". That optimises the degenerate case
|
|
179
|
+
* at the cost of every real message, which is a paragraph and reads as
|
|
180
|
+
* a block only when the block has an edge. The `/think` case is the
|
|
181
|
+
* accepted price, and it is recorded as such in design.md §1.6.
|
|
182
|
+
*
|
|
183
|
+
* The padding is by cells (charWidth is the width authority), so a CJK
|
|
184
|
+
* row pads by width, never by chars, and the chip never overruns.
|
|
180
185
|
* SGR 7 closed with SGR 27 — never SGR 0, the chip composes with a
|
|
181
186
|
* surrounding span — and NEVER dim: reverse video inverts the CURRENT
|
|
182
187
|
* colours, so dimmed text would invert into a dimmed block with no
|
|
@@ -226,11 +231,18 @@ class UserMessage {
|
|
|
226
231
|
content.push(row);
|
|
227
232
|
}
|
|
228
233
|
}
|
|
234
|
+
// R2 (law 1.6's recorded reversal): the band is FULL WIDTH. It was
|
|
235
|
+
// sized to its longest row, on the argument that a one-word turn
|
|
236
|
+
// like `/think` would otherwise paint a bar across the terminal —
|
|
237
|
+
// which optimises the degenerate case at the cost of every real
|
|
238
|
+
// message. The human's words are the one surface that gets the
|
|
239
|
+
// whole row.
|
|
240
|
+
//
|
|
229
241
|
// displayWidth stays the padding authority (never `length`): a CJK
|
|
230
242
|
// row is two cells per character and pads by cells.
|
|
231
|
-
const inner =
|
|
243
|
+
const inner = chipW;
|
|
232
244
|
for (const row of content) {
|
|
233
|
-
rows.push(`${p.rv} ${row}${" ".repeat(inner - displayWidth(row))} ${p.rvEnd}`);
|
|
245
|
+
rows.push(`${p.rv} ${row}${" ".repeat(Math.max(0, inner - displayWidth(row)))} ${p.rvEnd}`);
|
|
234
246
|
}
|
|
235
247
|
if (!truncated)
|
|
236
248
|
return rows;
|
|
@@ -275,19 +287,57 @@ class ThinkingFold {
|
|
|
275
287
|
this.cell = cell;
|
|
276
288
|
}
|
|
277
289
|
render(W, _ctx) {
|
|
290
|
+
const p = palette();
|
|
278
291
|
const block = this.cell.text;
|
|
279
292
|
const trimmed = escapeTerminal(block.trim());
|
|
280
|
-
//
|
|
281
|
-
//
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
293
|
+
// R2 (owner, 2026-08-27) — three changes, each independent.
|
|
294
|
+
//
|
|
295
|
+
// ITALIC marks the row as not-the-answer without spending a colour,
|
|
296
|
+
// on the same argument that admitted italic to the alphabet.
|
|
297
|
+
//
|
|
298
|
+
// The cut lands on a WORD. It used to cut on a byte, so the fold
|
|
299
|
+
// read as `…the user's h` and the reader's eye had to reassemble a
|
|
300
|
+
// word it already knew.
|
|
301
|
+
//
|
|
302
|
+
// The affordance moves to the RIGHT EDGE, so the left edge of every
|
|
303
|
+
// row on screen is content. The char count goes with the move: it
|
|
304
|
+
// told the reader nothing they could act on, and the row it was
|
|
305
|
+
// crowding is the one thing this cell says.
|
|
306
|
+
//
|
|
307
|
+
// The ≤100 short-circuit stays width-aware: a short block at a
|
|
308
|
+
// narrow width returned the line UNFOLDED and tripped invariant ①.
|
|
309
|
+
// DC-15: the affordance is DROPPED, not squeezed. `room` floored at
|
|
310
|
+
// 1 while `pad` floored at 1 independently, so a narrow terminal
|
|
311
|
+
// produced 2 + cut + pad + 6 = 11 cells no matter what W was — and
|
|
312
|
+
// invariant ① does not truncate, it THROWS. Measured 11 cells at
|
|
313
|
+
// every W ≤ 10. Below the width where the tail and one word of
|
|
314
|
+
// content can both stand, the row is the CONTENT: a cell that
|
|
315
|
+
// cannot hold the key's name has nothing to say about the key.
|
|
316
|
+
const tail = "/think";
|
|
317
|
+
const room = W - 2 - tail.length - 1;
|
|
318
|
+
// the belt: cutLine is SGR-aware and single-row, so the invariant
|
|
319
|
+
// holds by CONSTRUCTION at every width rather than by arithmetic
|
|
320
|
+
// that has to be re-proved every time a span moves.
|
|
321
|
+
if (room < 2)
|
|
322
|
+
return [cutLine(`${p.dim}⋯ ${p.italic}${wordCut(trimmed, Math.max(1, W - 2))}${p.italicEnd}${p.reset}`, W)];
|
|
323
|
+
const cut = wordCut(trimmed, room);
|
|
324
|
+
const body = `⋯ ${p.italic}${cut}${p.italicEnd}`;
|
|
325
|
+
const pad = Math.max(1, W - 2 - visibleWidth(cut) - tail.length);
|
|
326
|
+
return [cutLine(`${p.dim}${body}${" ".repeat(pad)}${tail}${p.reset}`, W)];
|
|
289
327
|
}
|
|
290
328
|
}
|
|
329
|
+
/** R2 — cut at a word boundary, with the honest ellipsis. widthCut cuts
|
|
330
|
+
* at a cell, which is right for verbatim output and wrong for prose:
|
|
331
|
+
* the reader has to reassemble "h" into "home". Falls back to the cell
|
|
332
|
+
* cut when the first word alone overruns, because a row that cannot
|
|
333
|
+
* hold one word has no boundary to find. */
|
|
334
|
+
function wordCut(text, room) {
|
|
335
|
+
if (visibleWidth(text) <= room)
|
|
336
|
+
return text;
|
|
337
|
+
const hard = widthCut(text, Math.max(1, room - 1));
|
|
338
|
+
const at = hard.lastIndexOf(" ");
|
|
339
|
+
return `${at > room / 3 ? hard.slice(0, at) : hard}\u2026`;
|
|
340
|
+
}
|
|
291
341
|
/** Fold a line's CONTENT at W−2 and prefix EVERY row with the gutter
|
|
292
342
|
* (W2: a wrapped tool row keeps its state mark — the left edge alone
|
|
293
343
|
* distinguishes the states at --plain; the UserMessage rail precedent,
|
|
@@ -508,7 +558,7 @@ class ToolExecution {
|
|
|
508
558
|
// list of calls, not a body of output.
|
|
509
559
|
const r = c.rolled;
|
|
510
560
|
const counts = exploreCounts(parts);
|
|
511
|
-
const head =
|
|
561
|
+
const head = ` explored ${p.bold}${counts}${p.reset}`; // R2: no tick
|
|
512
562
|
const tail = ` (${r.elapsed}s)`;
|
|
513
563
|
const room = W - visibleWidth(head) - tail.length;
|
|
514
564
|
const affordance = " · ctrl+r lists them";
|
|
@@ -523,7 +573,7 @@ class ToolExecution {
|
|
|
523
573
|
// W15 expand history — the head's commit captures it).
|
|
524
574
|
const r = c.rolled;
|
|
525
575
|
const noun = ROLLUP_NOUN[c.name] ?? "calls";
|
|
526
|
-
const out = gutterCut(
|
|
576
|
+
const out = gutterCut(" ", `${verbCol} ${r.count} ${noun} (${kUnit(r.lines)} lines, ${r.elapsed}s)`, W); // R2: no tick
|
|
527
577
|
const shown = r.targets.slice(0, 3);
|
|
528
578
|
if (shown.length > 0)
|
|
529
579
|
out.push(` ${p.dim}${CUT_ROW}${escapeTerminal(shown.join(" · "))}${p.reset}`);
|
|
@@ -541,7 +591,7 @@ class ToolExecution {
|
|
|
541
591
|
// names the decider; a human denial (no decidedBy) has no tail.
|
|
542
592
|
if (c.reason !== null) {
|
|
543
593
|
const by = attribution(c);
|
|
544
|
-
const out = gutterCut(
|
|
594
|
+
const out = gutterCut(" ", `${p.red}${escapeTerminal(`${c.name} ${toolTargetOf(c)}`)} (${escapeTerminal(c.reason)}${by})${p.reset}`, W);
|
|
545
595
|
out.push(...toolBlockBody(c, W));
|
|
546
596
|
return out;
|
|
547
597
|
}
|
|
@@ -578,7 +628,14 @@ class ToolExecution {
|
|
|
578
628
|
// the semantics. TUI2-R1.5 pin 4: and the parts give way in a
|
|
579
629
|
// PINNED ORDER, rather than whichever happened to be last.
|
|
580
630
|
const text = settledHeadText(verbCol, escapeTerminal(toolTargetOf(c)), meta, approvedBy, elapsed, W - 2 - (hidden === null ? 0 : SUFFIX_MIN));
|
|
581
|
-
|
|
631
|
+
// R2 (owner, 2026-08-27): no tick, no cross. A symbol earns its
|
|
632
|
+
// cell by carrying a fact the words do not, and a row that
|
|
633
|
+
// already says `exit 0` does not need one more thing saying it
|
|
634
|
+
// went fine. The gutter is two spaces; the OUTCOME lives in the
|
|
635
|
+
// metadata, in words, which is also the only form that survives
|
|
636
|
+
// a pipe with the colour stripped. A failure keeps its colour
|
|
637
|
+
// AND its words — see settledMeta.
|
|
638
|
+
const out = c.isError ? [` ${p.red}${text}${p.reset}`] : [` ${text}`];
|
|
582
639
|
out[0] = appendSuffix(out[0], expandSuffix(hidden, W - visibleWidth(out[0])));
|
|
583
640
|
out.push(...toolBlockBody(c, W));
|
|
584
641
|
return out;
|
|
@@ -600,7 +657,14 @@ class ToolExecution {
|
|
|
600
657
|
// leaves; the duration then rides the row, always legible.
|
|
601
658
|
const elapsed = c.startedAt !== null ? Math.max(1, Math.round((ctx.now - c.startedAt) / 1000)) : 1;
|
|
602
659
|
const dur = ` · ${elapsed}s`;
|
|
603
|
-
|
|
660
|
+
// R3 (design §5.2): a running command BREATHES — one glyph, seven
|
|
661
|
+
// greys, bottoming out on the ground's dim token (§2.2 applies
|
|
662
|
+
// mid-animation, not just at rest). The quadrant spinner it
|
|
663
|
+
// replaces ROTATED, which §5.3 forbids for a call whose duration
|
|
664
|
+
// cannot be predicted: a turning mark implies progress the
|
|
665
|
+
// product does not have. With no ground the breath freezes to a
|
|
666
|
+
// static `●` and says the same thing more quietly.
|
|
667
|
+
const out = gutterCut(`${breathFrame(ctx.spinnerI)} `, `${verbCol} ${liveTarget(c)}`, Math.max(4, W - dur.length));
|
|
604
668
|
out[0] = `${out[0]}${p.dim}${dur}${p.reset}`;
|
|
605
669
|
out.push(...toolBlockBody(c, W));
|
|
606
670
|
return out;
|
|
@@ -912,26 +976,26 @@ export function turnFold(t, W) {
|
|
|
912
976
|
const meta = parts.join(" · ");
|
|
913
977
|
const words = escapeTerminal(t.words);
|
|
914
978
|
if (words === "") {
|
|
915
|
-
const row = `${p.bold}
|
|
916
|
-
return visibleWidth(row) <= W ? [row] : [`${p.bold}
|
|
979
|
+
const row = `${p.bold}✦${p.reset} ${meta}`;
|
|
980
|
+
return visibleWidth(row) <= W ? [row] : [`${p.bold}✦${p.reset} ${widthCut(meta, Math.max(1, W - 3))}…`]; // a wordless turn folds to the W14 shape
|
|
917
981
|
}
|
|
918
982
|
// A9 (ruling R2, mock A): the user chip rides the fold — the human's
|
|
919
983
|
// words LEAD the one line, the same SGR-7 bracket as the live user
|
|
920
984
|
// row (#16f, side pads included). The words take the fold's width
|
|
921
|
-
// budget: W − the gutter ("
|
|
985
|
+
// budget: W − the gutter ("✦ " = 2) − the join (" · " = 3) − the
|
|
922
986
|
// chip's side pads (2) − the cut-tail reserve (1, the "…") − the
|
|
923
987
|
// metadata's own width — the metadata survives, the words width-cut
|
|
924
988
|
// at the end with the honest "…" (the "…" alone is the honest floor:
|
|
925
989
|
// the words were there, cut).
|
|
926
|
-
const budget = Math.max(0, W - visibleWidth(
|
|
990
|
+
const budget = Math.max(0, W - visibleWidth(`✦ ${meta}`) - 6);
|
|
927
991
|
const cut = visibleWidth(words) > budget ? `${widthCut(words, budget)}…` : words;
|
|
928
|
-
const row = `${p.bold}
|
|
992
|
+
const row = `${p.bold}✦${p.reset} ${p.rv} ${cut} ${p.rvEnd} · ${meta}`;
|
|
929
993
|
if (visibleWidth(row) <= W)
|
|
930
994
|
return [row];
|
|
931
995
|
// the last resort: the METADATA gives way — the words hold their
|
|
932
996
|
// budget, the meta cuts with the honest "…"; invariant ① never trips
|
|
933
997
|
// at ANY width (a degenerate W's fold is a cut, never a crash).
|
|
934
|
-
return [`${p.bold}
|
|
998
|
+
return [`${p.bold}✦${p.reset} ${p.rv} ${cut} ${p.rvEnd} · ${widthCut(meta, Math.max(1, W - 8 - visibleWidth(cut)))}…`];
|
|
935
999
|
}
|
|
936
1000
|
// ---- the bounded-block flow contract (W7, W8, W10) ----
|
|
937
1001
|
/** The caps — screen rows counted AFTER the fold, at the current width
|
|
@@ -1288,8 +1352,11 @@ class Banner {
|
|
|
1288
1352
|
}
|
|
1289
1353
|
render(W, ctx) {
|
|
1290
1354
|
const p = palette();
|
|
1291
|
-
|
|
1292
|
-
|
|
1355
|
+
// R2: NO blanket dim. bannerLines styles itself — the labels are
|
|
1356
|
+
// dim, the values are ink — and wrapping the whole thing in dim
|
|
1357
|
+
// made the answers as faint as the questions.
|
|
1358
|
+
void p;
|
|
1359
|
+
return bannerLines(W, ctx.height, this.cell.version, this.cell.extensionsText, this.cell.resume, ctx.now, this.cell.meta);
|
|
1293
1360
|
}
|
|
1294
1361
|
}
|
|
1295
1362
|
/** W20 — the task block's fixed-window height: the whole live block
|
|
@@ -1373,7 +1440,7 @@ class Checklist {
|
|
|
1373
1440
|
const fixed = done
|
|
1374
1441
|
? `task done · ${plural(items.length, "item")} · ${formatDuration(durationSeconds)}`
|
|
1375
1442
|
: `task · ${plural(items.length, "item")} · ${active.length} active · ${doneCount} done`;
|
|
1376
|
-
const header = `${p.bold}
|
|
1443
|
+
const header = `${p.bold}✦${p.reset} ${escapeTerminal(fixed + tail)}`;
|
|
1377
1444
|
// the FULL-list forms: SETTLED — the durable record (the fold is
|
|
1378
1445
|
// fine — committed content wraps naturally) — and the LIVE ctrl+r
|
|
1379
1446
|
// toggle (the header CUTS — the block stays one window high; the
|
|
@@ -1475,30 +1542,48 @@ export function widthCut(text, max) {
|
|
|
1475
1542
|
*/
|
|
1476
1543
|
export function selectionBar(styled, visible, W) {
|
|
1477
1544
|
const p = palette();
|
|
1478
|
-
|
|
1545
|
+
// R2 (design §2.1 — nothing dim ever sits on the wash): the bar IS a
|
|
1546
|
+
// wash. A dim span inside it renders grey-on-grey — 3.91:1 on the
|
|
1547
|
+
// light ground, under the 4.5 floor — and the dim spans are exactly
|
|
1548
|
+
// the descriptions and the metadata, i.e. the half of the row the
|
|
1549
|
+
// selection was supposed to help you read. Dim is dropped INSIDE the
|
|
1550
|
+
// bar and nowhere else; the same row unselected keeps it.
|
|
1551
|
+
const inner = (p.dim === "" ? styled : styled.replaceAll(p.dim, "")).replaceAll(p.reset, `${p.reset}${p.rv}`);
|
|
1479
1552
|
return `${p.rv} ${inner}${" ".repeat(Math.max(0, W - visible - 2))} ${p.rvEnd}`;
|
|
1480
1553
|
}
|
|
1481
1554
|
/**
|
|
1482
1555
|
* R2 — the composer's rails, and the ONE edge vocabulary.
|
|
1483
1556
|
*
|
|
1484
|
-
*
|
|
1485
|
-
*
|
|
1486
|
-
*
|
|
1487
|
-
*
|
|
1488
|
-
*
|
|
1489
|
-
*
|
|
1490
|
-
* the
|
|
1557
|
+
* R3 (owner, 2026-08-27): the rule is a SOLID hairline (`\u2500`), not
|
|
1558
|
+
* the dashed `\u254c` R2 shipped, and it is solid EVERYWHERE — the
|
|
1559
|
+
* composer, every panel's open and close, the band headers and the
|
|
1560
|
+
* markdown rule. One line, one weight, no exceptions to remember.
|
|
1561
|
+
*
|
|
1562
|
+
* W6 turned two \u254c dotted rows into a rounded box, reasoning that
|
|
1563
|
+
* "the box already says input lives here". That is reversed here, and
|
|
1564
|
+
* the reason is not taste: a rule is a DELIMITER and a box is a
|
|
1565
|
+
* CONTAINER, and the screen was carrying six edge vocabularies at once
|
|
1566
|
+
* (this box, the panel's \u2502 gutter and \u2514\u2500\u2500 tail, the
|
|
1567
|
+
* diff gutter, the quote's \u258f, the table's rails, the markdown
|
|
1568
|
+
* rule). ONE rule replaces the ones that SEPARATE; the \u2502 gutter
|
|
1569
|
+
* survives where it SCOPES.
|
|
1491
1570
|
*
|
|
1492
1571
|
* Row-neutral by construction: CHROME_ROWS is still 4, so every gate
|
|
1493
|
-
* keyed on H
|
|
1494
|
-
* the walls were taking.
|
|
1572
|
+
* keyed on H \u2212 4 is untouched, and the input row gains the two
|
|
1573
|
+
* columns the walls were taking.
|
|
1495
1574
|
*/
|
|
1496
1575
|
export function boxTop(W) {
|
|
1497
|
-
|
|
1576
|
+
// R3: the palette's dim, not a hardcoded SGR 2 — `dim` is an absolute
|
|
1577
|
+
// grey once the ground is known, and a rail that hardcodes the
|
|
1578
|
+
// attribute would be the one chrome row not obeying the table.
|
|
1579
|
+
const p = palette();
|
|
1580
|
+
return `${p.dim}${"\u2500".repeat(Math.max(0, W))}${p.reset}`;
|
|
1498
1581
|
}
|
|
1499
|
-
/**
|
|
1582
|
+
/** R2 — the same rule below. Named for its POSITION, not its shape, so
|
|
1583
|
+
* the compositor's two call sites did not have to move. */
|
|
1500
1584
|
export function boxBottom(W) {
|
|
1501
|
-
|
|
1585
|
+
const p = palette();
|
|
1586
|
+
return `${p.dim}${"\u2500".repeat(Math.max(0, W))}${p.reset}`;
|
|
1502
1587
|
}
|
|
1503
1588
|
/** The terminal label + rhythm gap (the pipe path's v2c bytes — the
|
|
1504
1589
|
* exact render the passthrough needs). */
|
package/dist/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export { panelAffordance, panelBlockRows, panelLead, panelLeadPlain, panelLeadWi
|
|
|
14
14
|
export { interactivePrompt, projectTrustRows, projectTrustView, projectUntrustedNote, uncertainView, type TrustArtifact, } from "./strings.js";
|
|
15
15
|
export { extensionsBannerText, helpRows, unansweredAskView, type BannerExtension } from "./strings.js";
|
|
16
16
|
export { displayVerb } from "./strings.js";
|
|
17
|
-
export { bannerLines, COLOR_OFF, COLOR_DARK, COLOR_LIGHT, COLOR_NEUTRAL, COLOR_ON, currentGround, setGround, escapeTerminal, foldResult, foldThinking, kUnit, palette, relativeTime, renderResumeList, renderTerminalGap, renderToolSummary, TAGLINE, toolTarget, truncateRow, type Palette, type ResumeMeta, } from "./render.js";
|
|
17
|
+
export { bannerLines, COLOR_OFF, COLOR_DARK, COLOR_LIGHT, COLOR_NEUTRAL, COLOR_ON, currentGround, setGround, escapeTerminal, foldResult, foldThinking, kUnit, palette, relativeTime, renderResumeList, MOTION_FRAMES, TWINKLE, breathFrame, twinkleFrame, renderTerminalGap, renderToolSummary, TAGLINE, toolTarget, truncateRow, type Palette, type ResumeMeta, } from "./render.js";
|
|
18
18
|
/** DC-3 — the ground: is the terminal light or dark. Pure; see the
|
|
19
19
|
* module comment for why `unknown` is a result and not a failure. */
|
|
20
20
|
export { groundFrom, parseOscColor, relativeLuminance, resolveGround, type Ground, type GroundInputs, type Rgb } from "./ground.js";
|
package/dist/index.js
CHANGED
|
@@ -28,7 +28,7 @@ export { extensionsBannerText, helpRows, unansweredAskView } from "./strings.js"
|
|
|
28
28
|
// TUI2-R2pre ④: the ONE display-verb table — the screen names the act,
|
|
29
29
|
// the tool table names the call.
|
|
30
30
|
export { displayVerb } from "./strings.js";
|
|
31
|
-
export { bannerLines, COLOR_OFF, COLOR_DARK, COLOR_LIGHT, COLOR_NEUTRAL, COLOR_ON, currentGround, setGround, escapeTerminal, foldResult, foldThinking, kUnit, palette, relativeTime, renderResumeList, renderTerminalGap, renderToolSummary, TAGLINE, toolTarget, truncateRow, } from "./render.js";
|
|
31
|
+
export { bannerLines, COLOR_OFF, COLOR_DARK, COLOR_LIGHT, COLOR_NEUTRAL, COLOR_ON, currentGround, setGround, escapeTerminal, foldResult, foldThinking, kUnit, palette, relativeTime, renderResumeList, MOTION_FRAMES, TWINKLE, breathFrame, twinkleFrame, renderTerminalGap, renderToolSummary, TAGLINE, toolTarget, truncateRow, } from "./render.js";
|
|
32
32
|
/** DC-3 — the ground: is the terminal light or dark. Pure; see the
|
|
33
33
|
* module comment for why `unknown` is a result and not a failure. */
|
|
34
34
|
export { groundFrom, parseOscColor, relativeLuminance, resolveGround } from "./ground.js";
|
package/dist/md.js
CHANGED
|
@@ -258,7 +258,7 @@ function blockBody(b, W) {
|
|
|
258
258
|
// R2: the dashed rule, at the block's own width. The 28 was a
|
|
259
259
|
// guess that read as a short line rather than a divider, and ─
|
|
260
260
|
// belonged to the box vocabulary this round is collapsing.
|
|
261
|
-
return [`${p.dim}${"\
|
|
261
|
+
return [`${p.dim}${"\u2500".repeat(Math.max(1, W))}${p.reset}`];
|
|
262
262
|
case "fence-open":
|
|
263
263
|
// E2: the RAIL, not a gutter. A block drawn with ``` is still a
|
|
264
264
|
// fenced block when a human selects it and pastes it somewhere
|
|
@@ -282,8 +282,10 @@ function blockBody(b, W) {
|
|
|
282
282
|
// DC-3: a fenced BODY carries no colour token. It used to take
|
|
283
283
|
// `code` — 1.54:1 on a white terminal, applied to whole blocks,
|
|
284
284
|
// which made the code the model just wrote the least readable
|
|
285
|
-
// thing on screen. The
|
|
286
|
-
// verbatim"
|
|
285
|
+
// thing on screen. The block's own ``` RAILS already say "this
|
|
286
|
+
// is verbatim" (E2 replaced the `│` gutter this comment used to
|
|
287
|
+
// name with them); saying it twice cost legibility and bought
|
|
288
|
+
// nothing.
|
|
287
289
|
return foldLineWidth(src.slice(indent.length), W - visibleWidth(gutter), indent).map((r) => `${gutter}${r}`);
|
|
288
290
|
}
|
|
289
291
|
case "quote": {
|
package/dist/render.d.ts
CHANGED
|
@@ -82,11 +82,22 @@ export interface Palette {
|
|
|
82
82
|
/**
|
|
83
83
|
* DC-3 — one table per ground.
|
|
84
84
|
*
|
|
85
|
-
* `dim` is
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
85
|
+
* R3 (owner, 2026-08-27) — `dim` is ABSOLUTE once the ground is known,
|
|
86
|
+
* and design.md §2's table always said so: light `243` `#767676` at
|
|
87
|
+
* 4.54:1, dark `246` `#949494` at 5.50:1 (both re-measured here).
|
|
88
|
+
*
|
|
89
|
+
* DC-3 shipped SGR 2 instead, on the argument that an attribute adapts
|
|
90
|
+
* to the ground while an absolute grey asserts one. That argument is
|
|
91
|
+
* right about what SGR 2 IS and wrong about what it MEASURES: a
|
|
92
|
+
* terminal renders it as a fraction of its own foreground, and on Apple
|
|
93
|
+
* Terminal's light profile that lands well under the 4.5:1 floor — the
|
|
94
|
+
* labels, the keys row and the status row were all reported unreadable
|
|
95
|
+
* in real use. An attribute that adapts to an unknown ratio is not a
|
|
96
|
+
* contrast guarantee; the table's measured value is.
|
|
97
|
+
*
|
|
98
|
+
* The UNKNOWN ground keeps SGR 2, because §3.1 forbids an absolute
|
|
99
|
+
* foreground in a palette that has not established a background — the
|
|
100
|
+
* attribute is exactly the "correct on any ground" degradation there.
|
|
90
101
|
*/
|
|
91
102
|
export declare const COLOR_NEUTRAL: Palette;
|
|
92
103
|
export declare const COLOR_LIGHT: Palette;
|
|
@@ -151,6 +162,32 @@ export declare function renderTerminalGap(statusLine: string | null): string;
|
|
|
151
162
|
* 40 columns skips the logo + the blank entirely — only the info rows.
|
|
152
163
|
* Pure.
|
|
153
164
|
*/
|
|
165
|
+
/**
|
|
166
|
+
* design.md §5.2 — THE TWO CYCLES, built. Seven frames each, walked at
|
|
167
|
+
* the existing 200ms spinner cadence, so a waiting screen's byte volume
|
|
168
|
+
* and frame rate are exactly what they were.
|
|
169
|
+
*
|
|
170
|
+
* §5.3 is why neither rotates: "a breath says alive; a turn says
|
|
171
|
+
* counting". A call whose duration cannot be predicted must not wear a
|
|
172
|
+
* mark that implies progress it does not have.
|
|
173
|
+
*/
|
|
174
|
+
/** The THINKING twinkle — glyphs only, no colour at all, so it survives
|
|
175
|
+
* NO_COLOR and any ground intact. §4.1: it settles onto `✦`, which is
|
|
176
|
+
* the same mark the collapsed segment keeps, so nothing new appears at
|
|
177
|
+
* the transition. Every glyph is in Menlo and absent from Apple Color
|
|
178
|
+
* Emoji (§6.1's test, run). */
|
|
179
|
+
export declare const TWINKLE: readonly ["✧", "✦", "✶", "✸", "✺", "✸", "✦"];
|
|
180
|
+
/** The breath's frame: `●` at the step's grey, for the CURRENT ground.
|
|
181
|
+
* With no ground — or under NO_COLOR — it freezes to a static `●`,
|
|
182
|
+
* because a brightness ramp needs a background to be a ramp against and
|
|
183
|
+
* §3.1 forbids guessing one. The glyph never changes, so the freeze
|
|
184
|
+
* degrades the motion and never the meaning. */
|
|
185
|
+
export declare function breathFrame(step: number): string;
|
|
186
|
+
/** The twinkle's frame — pure glyph, no palette involved. */
|
|
187
|
+
export declare function twinkleFrame(step: number): string;
|
|
188
|
+
/** Both cycles are seven frames, so ONE counter walks them and the two
|
|
189
|
+
* marks stay in step on a screen showing both. */
|
|
190
|
+
export declare const MOTION_FRAMES = 7;
|
|
154
191
|
export declare const TAGLINE = "the coding agent that survives kill -9";
|
|
155
192
|
/** R2 — what the opening knows about the session. Optional because the
|
|
156
193
|
* off-TTY caller prints a banner before a model is bound. */
|
package/dist/render.js
CHANGED
|
@@ -7,19 +7,51 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { charWidth, displayWidth } from "./width.js";
|
|
9
9
|
const BASE = { bold: "\x1b[1m", dim: "\x1b[2m", red: "\x1b[31m", green: "\x1b[32m", warn: "\x1b[33m", italic: "\x1b[3m", italicEnd: "\x1b[23m", underline: "\x1b[4m", underlineEnd: "\x1b[24m", rv: "\x1b[7m", rvEnd: "\x1b[27m", reset: "\x1b[0m" };
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* DC-9 (design §2.3) — the failure colour is theme-resolved.
|
|
12
|
+
*
|
|
13
|
+
* ANSI 31 is 5.89:1 on a white ground and 2.83:1 on a dark one: the one
|
|
14
|
+
* token in the alphabet whose whole job is "this went wrong" was the
|
|
15
|
+
* least readable thing on the screen exactly where a dark-terminal user
|
|
16
|
+
* reads it. A failure is CONTENT (law 1.2 admits colour there), so it
|
|
17
|
+
* cannot degrade to an attribute the way `dim` does — it needs a value
|
|
18
|
+
* per ground, and the ground is what §3's ladder is for.
|
|
19
|
+
*
|
|
20
|
+
* 256-cube indices, never truecolor (§2). Measured against the grounds
|
|
21
|
+
* §2 measures against — white, and #1E1E1E:
|
|
22
|
+
*
|
|
23
|
+
* light 124 `#af0000` 7.44:1
|
|
24
|
+
* dark 173 `#d7875f` 5.97:1
|
|
25
|
+
*
|
|
26
|
+
* With NO ground established the token stays ANSI 31 — the TERMINAL's
|
|
27
|
+
* own red, which its theme picked for its own background. That is rung
|
|
28
|
+
* 4's principle exactly: when the ground is unknown, use the thing that
|
|
29
|
+
* is correct on any ground rather than guessing one.
|
|
30
|
+
*/
|
|
31
|
+
const withWash = (wash, washEnd, red = BASE.red, dim = BASE.dim) => ({ ...BASE, red, dim, wash, washEnd, code: wash });
|
|
11
32
|
/**
|
|
12
33
|
* DC-3 — one table per ground.
|
|
13
34
|
*
|
|
14
|
-
* `dim` is
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
35
|
+
* R3 (owner, 2026-08-27) — `dim` is ABSOLUTE once the ground is known,
|
|
36
|
+
* and design.md §2's table always said so: light `243` `#767676` at
|
|
37
|
+
* 4.54:1, dark `246` `#949494` at 5.50:1 (both re-measured here).
|
|
38
|
+
*
|
|
39
|
+
* DC-3 shipped SGR 2 instead, on the argument that an attribute adapts
|
|
40
|
+
* to the ground while an absolute grey asserts one. That argument is
|
|
41
|
+
* right about what SGR 2 IS and wrong about what it MEASURES: a
|
|
42
|
+
* terminal renders it as a fraction of its own foreground, and on Apple
|
|
43
|
+
* Terminal's light profile that lands well under the 4.5:1 floor — the
|
|
44
|
+
* labels, the keys row and the status row were all reported unreadable
|
|
45
|
+
* in real use. An attribute that adapts to an unknown ratio is not a
|
|
46
|
+
* contrast guarantee; the table's measured value is.
|
|
47
|
+
*
|
|
48
|
+
* The UNKNOWN ground keeps SGR 2, because §3.1 forbids an absolute
|
|
49
|
+
* foreground in a palette that has not established a background — the
|
|
50
|
+
* attribute is exactly the "correct on any ground" degradation there.
|
|
19
51
|
*/
|
|
20
52
|
export const COLOR_NEUTRAL = withWash("\x1b[7m", "\x1b[27m");
|
|
21
|
-
export const COLOR_LIGHT = withWash("\x1b[48;5;255m", "\x1b[49m");
|
|
22
|
-
export const COLOR_DARK = withWash("\x1b[48;5;236m", "\x1b[49m");
|
|
53
|
+
export const COLOR_LIGHT = withWash("\x1b[48;5;255m", "\x1b[49m", "\x1b[38;5;124m", "\x1b[38;5;243m");
|
|
54
|
+
export const COLOR_DARK = withWash("\x1b[48;5;236m", "\x1b[49m", "\x1b[38;5;173m", "\x1b[38;5;246m");
|
|
23
55
|
/** The historical name — the palette for a colour TTY whose ground has
|
|
24
56
|
* not been established. Unchanged in every byte except `code`, which
|
|
25
57
|
* was the defect. */
|
|
@@ -190,6 +222,60 @@ export function renderTerminalGap(statusLine) {
|
|
|
190
222
|
* 40 columns skips the logo + the blank entirely — only the info rows.
|
|
191
223
|
* Pure.
|
|
192
224
|
*/
|
|
225
|
+
/**
|
|
226
|
+
* design.md §5.2 — THE TWO CYCLES, built. Seven frames each, walked at
|
|
227
|
+
* the existing 200ms spinner cadence, so a waiting screen's byte volume
|
|
228
|
+
* and frame rate are exactly what they were.
|
|
229
|
+
*
|
|
230
|
+
* §5.3 is why neither rotates: "a breath says alive; a turn says
|
|
231
|
+
* counting". A call whose duration cannot be predicted must not wear a
|
|
232
|
+
* mark that implies progress it does not have.
|
|
233
|
+
*/
|
|
234
|
+
/** The THINKING twinkle — glyphs only, no colour at all, so it survives
|
|
235
|
+
* NO_COLOR and any ground intact. §4.1: it settles onto `✦`, which is
|
|
236
|
+
* the same mark the collapsed segment keeps, so nothing new appears at
|
|
237
|
+
* the transition. Every glyph is in Menlo and absent from Apple Color
|
|
238
|
+
* Emoji (§6.1's test, run). */
|
|
239
|
+
export const TWINKLE = ["\u2727", "\u2726", "\u2736", "\u2738", "\u273a", "\u2738", "\u2726"];
|
|
240
|
+
/** The COMMAND breath — brightness only, one glyph. The ramps bottom out
|
|
241
|
+
* EXACTLY on the ground's dim token (§2.2: "the floor is a floor,
|
|
242
|
+
* including mid-animation"): light ends at 243 (4.54:1 on white), dark
|
|
243
|
+
* at 246 (5.50:1 on #1e1e1e). Measured, not assumed. */
|
|
244
|
+
const BREATH_LIGHT = [232, 236, 240, 243, 240, 236, 232];
|
|
245
|
+
const BREATH_DARK = [255, 251, 248, 246, 248, 251, 255];
|
|
246
|
+
/** The breath's frame: `●` at the step's grey, for the CURRENT ground.
|
|
247
|
+
* With no ground — or under NO_COLOR — it freezes to a static `●`,
|
|
248
|
+
* because a brightness ramp needs a background to be a ramp against and
|
|
249
|
+
* §3.1 forbids guessing one. The glyph never changes, so the freeze
|
|
250
|
+
* degrades the motion and never the meaning. */
|
|
251
|
+
export function breathFrame(step) {
|
|
252
|
+
const p = palette();
|
|
253
|
+
const ramp = currentGround() === "light" ? BREATH_LIGHT : currentGround() === "dark" ? BREATH_DARK : null;
|
|
254
|
+
if (ramp === null || p.bold === "")
|
|
255
|
+
return "\u25cf";
|
|
256
|
+
return `\x1b[38;5;${ramp[step % ramp.length]}m\u25cf${p.reset}`;
|
|
257
|
+
}
|
|
258
|
+
/** The twinkle's frame — pure glyph, no palette involved. */
|
|
259
|
+
export function twinkleFrame(step) {
|
|
260
|
+
return TWINKLE[step % TWINKLE.length];
|
|
261
|
+
}
|
|
262
|
+
/** Both cycles are seven frames, so ONE counter walks them and the two
|
|
263
|
+
* marks stay in step on a screen showing both. */
|
|
264
|
+
export const MOTION_FRAMES = 7;
|
|
265
|
+
/** DC-18: the display-width prefix of PLAIN text. `widthCut` lives in
|
|
266
|
+
* components.ts, which imports this module — the dependency runs one
|
|
267
|
+
* way, so the four lines live here rather than inverting it. */
|
|
268
|
+
function plainCut(text, max) {
|
|
269
|
+
let w = 0;
|
|
270
|
+
let i = 0;
|
|
271
|
+
for (; i < text.length; i += 1) {
|
|
272
|
+
const cw = charWidth(text.codePointAt(i));
|
|
273
|
+
if (w + cw > max)
|
|
274
|
+
break;
|
|
275
|
+
w += cw;
|
|
276
|
+
}
|
|
277
|
+
return text.slice(0, i);
|
|
278
|
+
}
|
|
193
279
|
export const TAGLINE = "the coding agent that survives kill -9";
|
|
194
280
|
/**
|
|
195
281
|
* R2 — the wordmark is retired (2026-08-27, the nineteen-screen review).
|
|
@@ -210,7 +296,11 @@ export const TAGLINE = "the coding agent that survives kill -9";
|
|
|
210
296
|
/** R2 — the keys a first screen teaches. One dim row, and deliberately
|
|
211
297
|
* NOT derived from KEY_BINDINGS: the sheet is the complete list and
|
|
212
298
|
* this is the opening's five, chosen rather than generated. */
|
|
213
|
-
|
|
299
|
+
// R2: the keys row names bindings the product ACTUALLY has. The first
|
|
300
|
+
// draft advertised `! bash` — there is no bang passthrough in kiso and
|
|
301
|
+
// KEY_BINDINGS never had one, so the opening screen was teaching a key
|
|
302
|
+
// that does nothing. A first screen that lies is worse than a short one.
|
|
303
|
+
const BANNER_KEYS = "esc interrupt · ctrl+c exit · / commands · @ files · ? keys";
|
|
214
304
|
/** R2 — the labels. Uppercase mono, dim, letter-spaced by the column
|
|
215
305
|
* rather than by SGR: they mark sections and are never content. */
|
|
216
306
|
const BANNER_LABELS = ["MODEL", "WORKSPACE", "EXTENSIONS"];
|
|
@@ -225,6 +315,13 @@ export function truncateRow(row, width) {
|
|
|
225
315
|
const total = displayWidth(row);
|
|
226
316
|
if (total <= width)
|
|
227
317
|
return row;
|
|
318
|
+
// DC-18: a width too narrow to HOLD the marker gets a hard cut. The
|
|
319
|
+
// fixpoint below floors `cut` at 0 and then appends a 6-cell marker
|
|
320
|
+
// regardless, so every width ≤ 6 returned a row WIDER than the
|
|
321
|
+
// terminal — and invariant ① throws rather than truncating. A marker
|
|
322
|
+
// wider than the row it marks is not a marker.
|
|
323
|
+
if (width < 7)
|
|
324
|
+
return plainCut(row, Math.max(0, width));
|
|
228
325
|
// iterate the marker to a fixpoint: the marker's width changes the
|
|
229
326
|
// cut, the cut changes the hidden count the marker reports
|
|
230
327
|
let marker = " (+0)";
|
|
@@ -256,7 +353,24 @@ export function truncateRow(row, width) {
|
|
|
256
353
|
* resume list (BIG only, W5). Every row truncates at the terminal width
|
|
257
354
|
* with a " (+N)" marker. Pure. */
|
|
258
355
|
export function bannerLines(W, H, version, extensionsText, resume = [], now = Date.now(), meta) {
|
|
259
|
-
const
|
|
356
|
+
const p = palette();
|
|
357
|
+
// R2: the banner styles itself per span. It used to be wrapped in one
|
|
358
|
+
// blanket dim by its component, which made the answers as faint as the
|
|
359
|
+
// labels asking the questions — the labels are the quiet half, the
|
|
360
|
+
// values are what a human came to read.
|
|
361
|
+
//
|
|
362
|
+
// Every width decision below is taken on PLAIN text and the styling is
|
|
363
|
+
// applied after, because truncateRow measures with displayWidth, which
|
|
364
|
+
// counts SGR bytes as columns. Style then measure is a bug waiting.
|
|
365
|
+
// DC-18: the name row is CUT like every other row here. It was the one
|
|
366
|
+
// row in this function pushed unguarded, so at W ≤ 10 `kiso 0.16.4`
|
|
367
|
+
// measured 11 cells and invariant ① threw AT STARTUP — the function
|
|
368
|
+
// whose own comment preaches "invariant ① holds at every width".
|
|
369
|
+
// The cut is taken on the plain text, per the note above.
|
|
370
|
+
const namePlain = plainCut(`kiso ${version}`, Math.max(1, W));
|
|
371
|
+
const nameCut = namePlain.slice(0, 4); // "kiso", or its surviving prefix
|
|
372
|
+
const verCut = namePlain.slice(5); // the version, if the width left room for it
|
|
373
|
+
const rows = [`${p.bold}${nameCut}${p.reset}${verCut === "" ? "" : `${p.dim} ${verCut}${p.reset}`}`];
|
|
260
374
|
const facts = [];
|
|
261
375
|
if (meta !== undefined) {
|
|
262
376
|
facts.push([BANNER_LABELS[0], `${meta.model}${meta.mode === "" ? "" : ` · ${meta.mode}`}`], [BANNER_LABELS[1], meta.cwd]);
|
|
@@ -268,32 +382,50 @@ export function bannerLines(W, H, version, extensionsText, resume = [], now = Da
|
|
|
268
382
|
// The value column HANGS rather than truncating. The label costs
|
|
269
383
|
// columns the value used to have, and an extension list cut at the
|
|
270
384
|
// width would hide which extensions loaded — on the one screen whose
|
|
271
|
-
// job is to say what is loaded.
|
|
272
|
-
|
|
273
|
-
|
|
385
|
+
// job is to say what is loaded.
|
|
386
|
+
const indent = 2 + LABEL_STOP;
|
|
387
|
+
// a terminal too narrow to hold the label column at all: the room is
|
|
388
|
+
// what is left, floored at one column, and the assembled row is
|
|
389
|
+
// truncated as a unit so invariant ① holds at every width.
|
|
390
|
+
const room = Math.max(1, W - indent);
|
|
274
391
|
for (const [label, value] of facts) {
|
|
275
|
-
const lead = ` ${label}${" ".repeat(LABEL_STOP - label.length)}`;
|
|
276
|
-
const hang = " ".repeat(
|
|
392
|
+
const lead = ` ${p.dim}${label}${p.reset}${" ".repeat(LABEL_STOP - label.length)}`;
|
|
393
|
+
const hang = " ".repeat(indent);
|
|
394
|
+
const lines = [];
|
|
277
395
|
let line = "";
|
|
278
|
-
const out = [];
|
|
279
396
|
for (const word of value.split(" ")) {
|
|
280
397
|
if (line === "")
|
|
281
398
|
line = word;
|
|
282
399
|
else if (displayWidth(`${line} ${word}`) <= room)
|
|
283
400
|
line += ` ${word}`;
|
|
284
401
|
else {
|
|
285
|
-
|
|
402
|
+
lines.push(line);
|
|
286
403
|
line = word;
|
|
287
404
|
}
|
|
288
405
|
}
|
|
289
406
|
if (line !== "")
|
|
290
|
-
|
|
291
|
-
for (const [i, l] of
|
|
292
|
-
|
|
407
|
+
lines.push(line);
|
|
408
|
+
for (const [i, l] of lines.entries()) {
|
|
409
|
+
const styled = `${i === 0 ? lead : hang}${truncateRow(l, room)}`;
|
|
410
|
+
rows.push(displayWidth(styled) - (i === 0 ? p.dim.length + p.reset.length : 0) <= W ? styled : truncateRow(`${hang}${l}`, W));
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (meta !== undefined && W >= 40) {
|
|
415
|
+
// R2/DC-2's device: the keys row is a list of independent clauses,
|
|
416
|
+
// so a narrow terminal drops whole clauses from the end rather than
|
|
417
|
+
// cutting one in half. `ctrl+r ex (+8)` teaches nothing.
|
|
418
|
+
const clauses = BANNER_KEYS.split(" \u00b7 ");
|
|
419
|
+
let keys = clauses[0];
|
|
420
|
+
for (let n = clauses.length; n > 1; n -= 1) {
|
|
421
|
+
const row = clauses.slice(0, n).join(" \u00b7 ");
|
|
422
|
+
if (displayWidth(row) <= W - 2) {
|
|
423
|
+
keys = row;
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
293
426
|
}
|
|
427
|
+
rows.push("", ` ${p.dim}${truncateRow(keys, W - 2)}${p.reset}`);
|
|
294
428
|
}
|
|
295
|
-
if (meta !== undefined && W >= 40)
|
|
296
|
-
rows.push("", truncateRow(` ${BANNER_KEYS}`, W));
|
|
297
429
|
if (W >= 40 && H >= 20 && resume.length > 0) {
|
|
298
430
|
rows.push("", ...renderResumeList(resume, W, now));
|
|
299
431
|
}
|
|
@@ -333,7 +465,7 @@ function titleCut(text, max) {
|
|
|
333
465
|
export function renderResumeList(metas, W, now) {
|
|
334
466
|
if (metas.length === 0)
|
|
335
467
|
return [];
|
|
336
|
-
const rows = ["
|
|
468
|
+
const rows = [" ✦ resume"]; // R2: the ONE fold/segment mark (§4.2)
|
|
337
469
|
const whens = metas.map((m) => relativeTime(m.updatedAt, now));
|
|
338
470
|
const metaTexts = metas.map((m) => `${m.events} events · ${m.runs} runs`);
|
|
339
471
|
const metaW = Math.max(...metaTexts.map((t) => t.length));
|
package/dist/strings.js
CHANGED
|
@@ -51,7 +51,7 @@ export function projectTrustView(root, files) {
|
|
|
51
51
|
name: "project trust",
|
|
52
52
|
title: root,
|
|
53
53
|
speaker: "kiso",
|
|
54
|
-
statusText: "
|
|
54
|
+
statusText: "⏸ project trust",
|
|
55
55
|
args: { kind: "text", lines: projectTrustRows(files) },
|
|
56
56
|
ruleOverride: "trust this project's .kiso?",
|
|
57
57
|
fallbackQuestion: `trust this project's .kiso? (y/n) `,
|
|
@@ -96,7 +96,7 @@ export function verifyOfferView() {
|
|
|
96
96
|
name: "verification",
|
|
97
97
|
title: "finish the checklist?",
|
|
98
98
|
speaker: "kiso",
|
|
99
|
-
statusText: "\
|
|
99
|
+
statusText: "\u23f8 run paused",
|
|
100
100
|
args: { kind: "text", lines: [] },
|
|
101
101
|
ruleOverride: "every item is marked done \u2014 run a check?",
|
|
102
102
|
simpleOptions: ["run a verification pass", "not now"],
|
|
@@ -109,7 +109,7 @@ export function uncertainView(name, executionId) {
|
|
|
109
109
|
name: "uncertain execution",
|
|
110
110
|
title: `${name} (${executionId})`,
|
|
111
111
|
speaker: "kiso",
|
|
112
|
-
statusText: "
|
|
112
|
+
statusText: "⏸ uncertain execution",
|
|
113
113
|
args: { kind: "text", lines: [executionId] },
|
|
114
114
|
ruleOverride: "an interrupted execution may have applied — rerun it?",
|
|
115
115
|
simpleOptions: ["rerun it", "abandon it"],
|
|
@@ -136,7 +136,7 @@ export function unansweredAskView(executionId) {
|
|
|
136
136
|
name: "unanswered question",
|
|
137
137
|
title: `ask_user (${executionId})`,
|
|
138
138
|
speaker: "kiso",
|
|
139
|
-
statusText: "
|
|
139
|
+
statusText: "⏸ unanswered question",
|
|
140
140
|
args: { kind: "text", lines: [executionId] },
|
|
141
141
|
ruleOverride: "an unanswered question was interrupted — ask it again?",
|
|
142
142
|
simpleOptions: ["ask it again", "drop it"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-tui-cells",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.5",
|
|
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",
|