@vincemakes/kiso-tui-cells 0.20.4 → 0.21.1

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.
@@ -266,7 +266,7 @@ export interface PanelView {
266
266
  /** The fix hint per speaker (the v8 design §3.5 table). */
267
267
  readonly hint?: string;
268
268
  /** The options-phase status-left text — the CLI knows the context
269
- * (" run paused", the trust gate's line). */
269
+ * (" run paused", the trust gate's line). */
270
270
  readonly statusText: string;
271
271
  /** The ALWAYS-verbose args — the full command/content/diff. */
272
272
  readonly args: PanelArgs;
@@ -395,7 +395,7 @@ export declare function panelLeadPlain(view: PanelView, phase: PanelPhase, curso
395
395
  export declare function panelLeadWidth(view: PanelView, phase: PanelPhase, cursor: number): number;
396
396
  /** The status row's left text while the panel is up — the phase, not
397
397
  * the CLI's painting status (the compositor derives it from the panel
398
- * state; the " run paused" etc. ride the options phase). */
398
+ * state; the " run paused" etc. ride the options phase). */
399
399
  export declare function panelStatus(view: PanelView, phase: PanelPhase, cursor: number): string;
400
400
  /**
401
401
  * The status row's right-aligned hint — the v4 frame's line, verbatim.
@@ -140,7 +140,7 @@ function segmentRisk(segment) {
140
140
  if (!letters.includes("r") || !letters.includes("f"))
141
141
  return null;
142
142
  const targets = words.slice(1).filter((w) => !/^-/.test(w));
143
- return targets.length === 0 ? "deletes files permanently" : `⚠ deletes files permanently (${targets.join(", ")})`;
143
+ return targets.length === 0 ? "deletes files permanently" : `deletes files permanently (${targets.join(", ")})`;
144
144
  }
145
145
  if (verb !== "git")
146
146
  return null;
@@ -149,9 +149,9 @@ function segmentRisk(segment) {
149
149
  // and conflating them would put a red line on the most ordinary command
150
150
  // in the product.
151
151
  if (sub === "checkout" && words.includes("--"))
152
- return "discards your uncommitted changes — unrecoverable";
152
+ return "discards your uncommitted changes — unrecoverable";
153
153
  if (sub === "reset" && words.includes("--hard"))
154
- return "throws away commits and working changes";
154
+ return "throws away commits and working changes";
155
155
  // `git clean -n` is a DRY RUN and is the reason this checks for the f
156
156
  // rather than for the command.
157
157
  if (sub === "clean") {
@@ -160,7 +160,7 @@ function segmentRisk(segment) {
160
160
  .filter((w) => /^-[a-zA-Z]+$/.test(w))
161
161
  .join("");
162
162
  if (letters.includes("f"))
163
- return "deletes untracked files permanently";
163
+ return "deletes untracked files permanently";
164
164
  }
165
165
  return null;
166
166
  }
@@ -321,8 +321,15 @@ export function panelBlockLayout(view, phase, cursor, W, maxRows, note, safer) {
321
321
  rows.push(...shown);
322
322
  // TUI2-R3v2 ④: the risk hint sits directly under the args, because it
323
323
  // is a sentence ABOUT those args — the v4 frame's placement. The warn
324
- // tint is the palette's existing functional yellow (no new colour),
325
- // and under NO_COLOR the ⚠ still carries it.
324
+ // tint is the palette's existing functional yellow (no new colour).
325
+ //
326
+ // DC-42: it used to open with a warning mark, on the argument that
327
+ // the mark carried the warning once NO_COLOR took the tint away. It does not
328
+ // carry anything the sentence does not — "deletes files permanently"
329
+ // IS the warning, and §1.3 gives a symbol its cell only for a fact
330
+ // the words lack. What separates kiso's sentence from the command's
331
+ // own lines survives the strip either way: the args wear the `│`
332
+ // gutter and this row wears the plain two-space indent.
326
333
  const risk = view.riskHint;
327
334
  if (risk !== undefined && risk !== "")
328
335
  rows.push(`${gutter}${cutLine(`${p.warn}${escapeTerminal(risk)}${p.reset}`, Math.max(1, W - 2))}`);
@@ -432,8 +439,8 @@ export function panelLeadWidth(view, phase, cursor) {
432
439
  }
433
440
  /** The status row's left text while the panel is up — the phase, not
434
441
  * the CLI's painting status (the compositor derives it from the panel
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
442
+ * state; the " run paused" etc. ride the options phase). */
443
+ // R2 (design §4, the ruling): a panel that is WAITING ON A HUMAN says
437
444
  // so with the one mark that means it. `▸` is the checklist's "the
438
445
  // current one" — a mark meaning two things is worse than two marks
439
446
  // (law 4.2), and the thing this row has to convey is not "here" but
@@ -450,7 +457,7 @@ export function panelStatus(view, phase, cursor) {
450
457
  // typing needs to know the model will read this and answer with a new
451
458
  // call — which is what the v4 frame says, in those words.
452
459
  if (phase === "amend")
453
- return " your note goes to the model — it will propose a new call";
460
+ return " your note goes to the model — it will propose a new call";
454
461
  return view.statusText;
455
462
  }
456
463
  /**
@@ -151,7 +151,7 @@ export type BodyCell = {
151
151
  * settled head row aggregates name + status + decidedBy in ONE
152
152
  * row: a denied call's pinned row gains `· by <decidedBy>`; an
153
153
  * extension-approved call's settled row gains `· approved by
154
- * <decidedBy>` (the human approval needs no marker — the
154
+ * <decidedBy>` (the human approval needs no marker — the
155
155
  * spinner → ✓ sequence told the story). Null until a decision
156
156
  * lands (the auto-allowed calls never have one). */
157
157
  verdict: {
@@ -207,7 +207,7 @@ export type BodyCell = {
207
207
  * live region); true once SETTLED — endTurn committed it as the
208
208
  * turn's one recap block. */
209
209
  done: boolean;
210
- /** W20: the LIVE block's ctrl+r toggle (W15) — the capped form
210
+ /** W20: the LIVE block's ctrl+o toggle (W15) — the capped form
211
211
  * flips to the full list in place. The settled render ignores
212
212
  * it (already full). */
213
213
  expanded: boolean;
@@ -224,8 +224,9 @@ declare const TOOL_SUMMARY_MAX = 60;
224
224
  * compositor stays a pure writer. */
225
225
  export declare function cellComponent(cell: BodyCell): Component;
226
226
  /** W22: the pending-queue chips — queued user lines pre-render above
227
- * the input row as the SAME UserMessage chip (undimmed: reverse video
228
- * inverts the CURRENT colours), the dim `□` gutter marking the queued
227
+ * the input row as the SAME UserMessage chip (undimmed: §2.1 forbids
228
+ * dim ON the wash, and on rung 4 reverse video would invert it into a
229
+ * dimmed block), the dim `□` gutter marking the queued
229
230
  * state (the gutter rides EVERY row — the gutterFold precedent: the
230
231
  * left edge alone distinguishes the states). Each chip folds at W−3
231
232
  * (the gutter's 2 cells), so a long line hard-folds INSIDE the chip
@@ -245,7 +246,7 @@ export declare function expandSuffix(lines: number | null, room: number): string
245
246
  /**
246
247
  * TUI2-R2 ⑤ (D, candidate 1) — the FOCUS tint.
247
248
  *
248
- * The cell the next ctrl+r will act on brightens its own `ctrl+r` token
249
+ * The cell the next ctrl+o will act on brightens its own `ctrl+o` token
249
250
  * to the code tint; the rest of the suffix — the separator, the count —
250
251
  * stays dim, because what is being marked is the KEY's target, not the
251
252
  * row. Zero new rows, zero new columns: the affordance the cell already
@@ -253,7 +254,7 @@ export declare function expandSuffix(lines: number | null, room: number): string
253
254
  *
254
255
  * Applied to a row rather than composed into it on purpose. The token is
255
256
  * emitted from several places (the settled suffix, the renderer's own
256
- * `└ +N … · ctrl+r` cut rows) and threading a flag through all of them
257
+ * `└ +N … · ctrl+o` cut rows) and threading a flag through all of them
257
258
  * would put the invariant "exactly one bright token" in as many hands as
258
259
  * there are emitters. Here it has exactly one.
259
260
  *
@@ -307,7 +308,7 @@ export declare function foldTerms(reads: number, edits: number, others: readonly
307
308
  *
308
309
  * thinking ✧ thinking 4s
309
310
  * acting ✶ reading 6 files · running 4 shell commands
310
- * settled ✦ thought 9s · read 6 files · ran 4 shell commands · ctrl+r
311
+ * settled ✦ thought 9s · read 6 files · ran 4 shell commands · ctrl+o
311
312
  *
312
313
  * THE GIVE-WAY LADDER, in order, because at some width everything
313
314
  * cannot fit:
@@ -384,7 +385,7 @@ export interface StretchTerms {
384
385
  /**
385
386
  * R4 (C1) — the fold NAMES ITS OWN TARGET.
386
387
  *
387
- * `ctrl+r` used to be printed identically on every fold on the screen,
388
+ * `ctrl+o` used to be printed identically on every fold on the screen,
388
389
  * and the key walked a ring whose order nothing on screen expressed —
389
390
  * so the owner's report was exact: "there is no way to know which
390
391
  * stretch it opens". The tint that marks the next target can only be
@@ -508,7 +509,7 @@ export declare function formatDuration(totalSeconds: number): string;
508
509
  * W21: the question param is gone — the old question slot retires; a
509
510
  * pending approval's status IS the panel's (the compositor derives
510
511
  * it from the bound panel state). */
511
- /** R8b — THE IDLE HINT GIVES WAY IN ORDER, and `ctrl+o` is on it.
512
+ /** R8b — THE IDLE HINT GIVES WAY IN ORDER, and `ctrl+r` is on it.
512
513
  *
513
514
  * The transcript viewer shipped in 0.19.0 and was reachable only from
514
515
  * the `?` sheet: not on the banner's key line, not here. A feature
@@ -519,9 +520,9 @@ export declare function formatDuration(totalSeconds: number): string;
519
520
  * when it does not fit — a longer string would take `/ commands` down
520
521
  * with it on a narrow terminal. So the forms are a ladder, and the
521
522
  * order says which affordance is least replaceable: `/ commands`
522
- * survives longest because it is the door to everything; `ctrl+o`
523
+ * survives longest because it is the door to everything; `ctrl+r`
523
524
  * outranks `↑ history` because pressing up is how a person finds the
524
- * history by accident, and nothing finds ctrl+o by accident. */
525
+ * history by accident, and nothing finds ctrl+r by accident. */
525
526
  export declare function idleHint(room: number): string;
526
527
  export declare function statusLine(status: string, tail: string, W: number, hint?: string): string;
527
528
  /** The display-width prefix of a plain (SGR-free) text. W21: exported
@@ -185,10 +185,20 @@ export function cellComponent(cell) {
185
185
  *
186
186
  * The padding is by cells (charWidth is the width authority), so a CJK
187
187
  * row pads by width, never by chars, and the chip never overruns.
188
- * SGR 7 closed with SGR 27 — never SGR 0, the chip composes with a
189
- * surrounding spanand NEVER dim: reverse video inverts the CURRENT
190
- * colours, so dimmed text would invert into a dimmed block with no
191
- * contrast.
188
+ *
189
+ * R9 P1the surface is the WASH (§1.6, §7.9), closed with 49 rather
190
+ * than SGR 0 so the chip composes with a surrounding span. It drew with
191
+ * SGR 7 on every ground until now, which §7.9 never asked for: on a
192
+ * light terminal that is a full-width black band, on a dark one a white
193
+ * one, and every turn has exactly one. §11 settles which side was
194
+ * stale — the design said washed, the code predated DC-3's ground-
195
+ * resolved wash and was never moved onto it. The ladder makes the move
196
+ * safe: with no ground `wash` IS reverse video (§3 rung 4), so the
197
+ * unknown-ground chip is byte-identical to what shipped.
198
+ *
199
+ * NEVER dim inside it, on either surface: §2.1 on a resolved wash, and
200
+ * on rung 4 because reverse video inverts the CURRENT colours, so
201
+ * dimmed text inverts into a dimmed block with no contrast.
192
202
  */
193
203
  /**
194
204
  * REL-0152-D13 — how much of a turn the chip shows.
@@ -226,7 +236,13 @@ class UserMessage {
226
236
  truncated = true;
227
237
  break;
228
238
  }
229
- for (const row of foldLine(escapeTerminal(para), chipW)) {
239
+ // R9 Q3: by WORD. The char fold was defended as lossless, and
240
+ // that argument does not survive CJK — a run with no spaces has
241
+ // nothing to lose — while every other prose surface in the
242
+ // product already folds by word (ErrorLine, VD-10). foldWords
243
+ // falls through to foldLine's hard break for a word wider than
244
+ // the row, so invariant ① still outranks the word.
245
+ for (const row of foldWords(escapeTerminal(para), chipW)) {
230
246
  if (content.length >= USER_CHIP_ROWS) {
231
247
  truncated = true;
232
248
  break;
@@ -245,7 +261,7 @@ class UserMessage {
245
261
  // row is two cells per character and pads by cells.
246
262
  const inner = chipW;
247
263
  for (const row of content) {
248
- rows.push(`${p.rv} ${row}${" ".repeat(Math.max(0, inner - displayWidth(row)))} ${p.rvEnd}`);
264
+ rows.push(`${p.wash} ${row}${" ".repeat(Math.max(0, inner - displayWidth(row)))} ${p.washEnd}`);
249
265
  }
250
266
  if (!truncated)
251
267
  return rows;
@@ -262,8 +278,9 @@ class UserMessage {
262
278
  }
263
279
  }
264
280
  /** W22: the pending-queue chips — queued user lines pre-render above
265
- * the input row as the SAME UserMessage chip (undimmed: reverse video
266
- * inverts the CURRENT colours), the dim `□` gutter marking the queued
281
+ * the input row as the SAME UserMessage chip (undimmed: §2.1 forbids
282
+ * dim ON the wash, and on rung 4 reverse video would invert it into a
283
+ * dimmed block), the dim `□` gutter marking the queued
267
284
  * state (the gutter rides EVERY row — the gutterFold precedent: the
268
285
  * left edge alone distinguishes the states). Each chip folds at W−3
269
286
  * (the gutter's 2 cells), so a long line hard-folds INSIDE the chip
@@ -353,7 +370,7 @@ class ThinkingFold {
353
370
  // opens with a numbered plan ("1. …\n2. …") produces exactly it.
354
371
  // Invariant ①b now catches the class at the emit; this stops
355
372
  // producing it. Whitespace collapses because the row is a
356
- // SUMMARY — the full text is one ctrl+r away, unchanged.
373
+ // SUMMARY — the full text is one ctrl+o away, unchanged.
357
374
  const trimmed = escapeTerminal(block.trim()).replace(/\s+/g, " ");
358
375
  // R2 (owner, 2026-08-27) — three changes, each independent.
359
376
  //
@@ -587,7 +604,7 @@ function toolTargetOf(c) {
587
604
  * own render; the lines fold (the summary gives way first). W7 (the
588
605
  * flow contract): the block's BODY (the rows below the header) is
589
606
  * capped in SCREEN rows AFTER the fold, at the current width — the
590
- * renderer-cut row (`└ +N … · ctrl+r`) sits INSIDE the cap (a
607
+ * renderer-cut row (`└ +N … · ctrl+o`) sits INSIDE the cap (a
591
608
  * truncated block is cap−1 output rows + the cut row); the TOOL-cut
592
609
  * row (`└ capped by …` — the tool's OWN truncation note, W10) is a
593
610
  * DIFFERENT fact, never counted in the output cap. W3: the verb is
@@ -626,7 +643,7 @@ class ToolExecution {
626
643
  const head = ` explored ${p.bold}${counts}${p.reset}`; // R2: no tick
627
644
  const tail = ` (${r.elapsed}s)`;
628
645
  const room = W - visibleWidth(head) - tail.length;
629
- const affordance = " · ctrl+r lists them";
646
+ const affordance = " · ctrl+o lists them";
630
647
  return [cutLine(`${head}${p.dim}${tail}${affordance.length <= room ? affordance : ""}${p.reset}`, W)];
631
648
  }
632
649
  if (c.rolled !== null) {
@@ -634,7 +651,7 @@ class ToolExecution {
634
651
  // the work order's claimed shape, verbatim — the verbCol's
635
652
  // 5-char pad reproduces the "read 5 files" double space, the
636
653
  // children are the first 3 basename targets, the overflow row
637
- // carries the ctrl+r affordance (its "└ … ctrl+r" joins the
654
+ // carries the ctrl+o affordance (its "└ … ctrl+o" joins the
638
655
  // W15 expand history — the head's commit captures it).
639
656
  const r = c.rolled;
640
657
  const noun = ROLLUP_NOUN[c.name] ?? "calls";
@@ -643,7 +660,7 @@ class ToolExecution {
643
660
  if (shown.length > 0)
644
661
  out.push(` ${p.dim}${CUT_ROW}${escapeTerminal(shown.join(" · "))}${p.reset}`);
645
662
  if (r.targets.length > 3)
646
- out.push(` ${p.dim}${CUT_ROW}+${r.targets.length - 3} more — ctrl+r expands${p.reset}`);
663
+ out.push(` ${p.dim}${CUT_ROW}+${r.targets.length - 3} more — ctrl+o expands${p.reset}`);
647
664
  return out;
648
665
  }
649
666
  if (c.state === "done") {
@@ -674,7 +691,7 @@ class ToolExecution {
674
691
  }
675
692
  const elapsed = c.startedAt !== null && c.doneAt !== null ? ((c.doneAt - c.startedAt) / 1000).toFixed(1) : "?";
676
693
  // TUI2-R1.5 ⑤ (VD-6): the line count is stated EXACTLY ONCE. Every
677
- // read card carried it twice — `(2 lines, 0.0s) · 2 lines · ctrl+r
694
+ // read card carried it twice — `(2 lines, 0.0s) · 2 lines · ctrl+o
678
695
  // expands` — because the parens and the suffix were written by
679
696
  // different rounds, each unaware the other was counting. The
680
697
  // SUFFIX keeps it (it is the one that also names the key), so a
@@ -718,8 +735,8 @@ class ToolExecution {
718
735
  return out;
719
736
  }
720
737
  if (c.state === "approval") {
721
- // W2: the is the GUTTER (the left edge), never the line's tail
722
- const out = gutterCut(`${p.bold}⏸${p.reset} `, `${verbCol} ${liveTarget(c)}`, W);
738
+ // W2: the is the GUTTER (the left edge), never the line's tail
739
+ const out = gutterCut(`${p.bold}❯${p.reset} `, `${verbCol} ${liveTarget(c)}`, W);
723
740
  out.push(...toolBlockBody(c, W));
724
741
  return out;
725
742
  }
@@ -760,7 +777,7 @@ class ToolExecution {
760
777
  * The affordance is a statement about hidden content: a cell whose body
761
778
  * is already whole on screen must not advertise a key that would show it
762
779
  * the same thing, and a cell that already carries its own renderer cut
763
- * (`└ +N earlier rows · ctrl+r`, `└ +N more · ctrl+r`) already teaches
780
+ * (`└ +N earlier rows · ctrl+o`, `└ +N more · ctrl+o`) already teaches
764
781
  * the key at the place the content stops. What is LEFT — and it is the
765
782
  * common case — is every settled non-shell call, whose collapsed body is
766
783
  * empty: the whole result sits behind the key with nothing on screen
@@ -799,12 +816,12 @@ function hiddenLines(c, W) {
799
816
  * against the room the row actually has).
800
817
  */
801
818
  /** TUI2-R1.5 ⑤ — the cells a settled head row reserves for its
802
- * affordance: exactly the shortest tier, " · ctrl+r". The suffix used
819
+ * affordance: exactly the shortest tier, " · ctrl+o". The suffix used
803
820
  * to take whatever width happened to be left, so a long target spent it
804
821
  * all and the card said nothing about the lines behind the key. Every
805
822
  * row that fitted its head before still fits it; only a head that would
806
823
  * have eaten the whole row gives up its last nine cells. */
807
- const SUFFIX_MIN = " · ctrl+r".length;
824
+ const SUFFIX_MIN = " · ctrl+o".length;
808
825
  /**
809
826
  * TUI2-R1.5 pin 4 — the settled head row's text, with a PINNED cut
810
827
  * order.
@@ -814,8 +831,8 @@ const SUFFIX_MIN = " · ctrl+r".length;
814
831
  * came last, so the parens were what got cut. The walkthrough caught
815
832
  * both consequences —
816
833
  *
817
- * ✓ shell printf '…' 1 2 … 12 (exit 0 · approv… · ctrl+r
818
- * ✓ shell for i in 1 2 3 … … · ctrl+r
834
+ * ✓ shell printf '…' 1 2 … 12 (exit 0 · approv… · ctrl+o
835
+ * ✓ shell for i in 1 2 3 … … · ctrl+o
819
836
  *
820
837
  * — an UNCLOSED parenthesis, and a row that lost the exit code and the
821
838
  * duration to a command string that had no claim on them. A cut that
@@ -880,7 +897,7 @@ export function expandSuffix(lines, room) {
880
897
  if (lines === null)
881
898
  return "";
882
899
  const count = `${lines} line${lines === 1 ? "" : "s"}`;
883
- for (const tier of [` · ${count} · ctrl+r expands`, ` · ${count} · ctrl+r`, " · ctrl+r"]) {
900
+ for (const tier of [` · ${count} · ctrl+o expands`, ` · ${count} · ctrl+o`, " · ctrl+o"]) {
884
901
  if (tier.length <= room)
885
902
  return tier;
886
903
  }
@@ -897,7 +914,7 @@ function appendSuffix(row, suffix) {
897
914
  /**
898
915
  * TUI2-R2 ⑤ (D, candidate 1) — the FOCUS tint.
899
916
  *
900
- * The cell the next ctrl+r will act on brightens its own `ctrl+r` token
917
+ * The cell the next ctrl+o will act on brightens its own `ctrl+o` token
901
918
  * to the code tint; the rest of the suffix — the separator, the count —
902
919
  * stays dim, because what is being marked is the KEY's target, not the
903
920
  * row. Zero new rows, zero new columns: the affordance the cell already
@@ -905,7 +922,7 @@ function appendSuffix(row, suffix) {
905
922
  *
906
923
  * Applied to a row rather than composed into it on purpose. The token is
907
924
  * emitted from several places (the settled suffix, the renderer's own
908
- * `└ +N … · ctrl+r` cut rows) and threading a flag through all of them
925
+ * `└ +N … · ctrl+o` cut rows) and threading a flag through all of them
909
926
  * would put the invariant "exactly one bright token" in as many hands as
910
927
  * there are emitters. Here it has exactly one.
911
928
  *
@@ -913,7 +930,7 @@ function appendSuffix(row, suffix) {
913
930
  */
914
931
  export function focusToken(row, W) {
915
932
  const p = palette();
916
- const at = row.lastIndexOf(CTRL_R);
933
+ const at = row.lastIndexOf(EXPAND_KEY);
917
934
  if (at !== -1) {
918
935
  // the row already names the key — brighten the token in place, and
919
936
  // leave every other span exactly as it was
@@ -927,10 +944,10 @@ export function focusToken(row, W) {
927
944
  // attribute like bold is spent everywhere. It closes with washEnd
928
945
  // rather than a reset, so the surrounding dim survives instead of
929
946
  // having to be re-applied.
930
- return `${row.slice(0, at)}${p.lift}${CTRL_R}${p.dim}${row.slice(at + CTRL_R.length)}`;
947
+ return `${row.slice(0, at)}${p.lift}${EXPAND_KEY}${p.dim}${row.slice(at + EXPAND_KEY.length)}`;
931
948
  }
932
949
  // A LIVE row does not carry the affordance today, and the live cell is
933
- // the one ctrl+r takes FIRST (expandNext scans the live tail before
950
+ // the one ctrl+o takes FIRST (expandNext scans the live tail before
934
951
  // the committed ring) — so the row the key is aimed at was the one row
935
952
  // that never said the key existed. The affordance IS the marker here:
936
953
  // it appears on the focused row and nowhere else, which is why no
@@ -939,13 +956,15 @@ export function focusToken(row, W) {
939
956
  const room = W - visibleWidth(row);
940
957
  if (room < SUFFIX_MIN)
941
958
  return row; // never at the cost of invariant ①
942
- return `${row}${p.dim} · ${p.lift}${CTRL_R}${p.reset}`;
959
+ return `${row}${p.dim} · ${p.lift}${EXPAND_KEY}${p.reset}`;
943
960
  }
944
- const CTRL_R = "ctrl+r";
961
+ /** DC-41 the label in ONE place. The key has moved once now, and
962
+ * a constant named after its binding is a comment that lies. */
963
+ const EXPAND_KEY = "ctrl+o";
945
964
  /** TUI2-R1 (A) — the expanded block's last row: the way back. The
946
965
  * rollup's expanded list carries a second clause (its members' full
947
966
  * outputs live in /last, which the group row cannot show). */
948
- const COLLAPSE_ROW = "ctrl+r collapses";
967
+ const COLLAPSE_ROW = "ctrl+o collapses";
949
968
  /** W13 — the rollup opt-in table: which tools collapse, and the count
950
969
  * NOUN (read_file calls → "5 files", list_dir → "5 dirs", search_text
951
970
  * → "5 matches"). Only these tools opt in — a shell burst is never
@@ -1049,7 +1068,7 @@ function countTerm(n, singular, plural) {
1049
1068
  * DECLARED SUPERSESSION (R3g, 2026-08-28) — A9 also ruled that "the
1050
1069
  * metadata terms give way LAST". They do not any more: the KEY does.
1051
1070
  * A9 was taken when this line carried no key, and at a width where the
1052
- * chip, the full metadata and " · ctrl+r" cannot coexist, a fold with
1071
+ * chip, the full metadata and " · ctrl+o" cannot coexist, a fold with
1053
1072
  * no key is the turn's work behind a line with no way back to it. So
1054
1073
  * the order is now words, then metadata, then — never — the key. The
1055
1074
  * glyph is ✦ and the zero terms are dropped (R3b), so the example above
@@ -1153,7 +1172,7 @@ export function foldTerms(reads, edits, others) {
1153
1172
  *
1154
1173
  * thinking ✧ thinking 4s
1155
1174
  * acting ✶ reading 6 files · running 4 shell commands
1156
- * settled ✦ thought 9s · read 6 files · ran 4 shell commands · ctrl+r
1175
+ * settled ✦ thought 9s · read 6 files · ran 4 shell commands · ctrl+o
1157
1176
  *
1158
1177
  * THE GIVE-WAY LADDER, in order, because at some width everything
1159
1178
  * cannot fit:
@@ -1283,7 +1302,7 @@ function troubleClause(t) {
1283
1302
  /**
1284
1303
  * R4 (C1) — the fold NAMES ITS OWN TARGET.
1285
1304
  *
1286
- * `ctrl+r` used to be printed identically on every fold on the screen,
1305
+ * `ctrl+o` used to be printed identically on every fold on the screen,
1287
1306
  * and the key walked a ring whose order nothing on screen expressed —
1288
1307
  * so the owner's report was exact: "there is no way to know which
1289
1308
  * stretch it opens". The tint that marks the next target can only be
@@ -1340,7 +1359,7 @@ export function stretchLine(t, W) {
1340
1359
  const mark = t.mark ?? " ";
1341
1360
  // R4a (owner ruling, 2026-08-30) — the fold row prints NO key.
1342
1361
  //
1343
- // R4 printed `· ctrl+r 3` so the row could name its own target. The
1362
+ // R4 printed `· ctrl+o 3` so the row could name its own target. The
1344
1363
  // owner's objection is the right one: a number you cannot type is not
1345
1364
  // a selector, it is decoration that costs a column — and the
1346
1365
  // reference implementation, checked rather than assumed, prints
@@ -1349,7 +1368,7 @@ export function stretchLine(t, W) {
1349
1368
  // scrolled away; the row itself stays clean.
1350
1369
  //
1351
1370
  // So the affordance is retired here and owed to that mode. Until it
1352
- // exists, `ctrl+r` still opens the most recent fold — it is simply no
1371
+ // exists, `ctrl+o` still opens the most recent fold — it is simply no
1353
1372
  // longer advertised on a row that cannot say which one it means.
1354
1373
  const key = "";
1355
1374
  const lead = t.phase === "thinking" ? [`thinking ${t.thoughtSeconds}s`] : t.phase === "settled" && t.thoughtSeconds > 0 ? [`thought ${t.thoughtSeconds}s`] : [];
@@ -1441,7 +1460,7 @@ export function turnFold(t, W) {
1441
1460
  out = out.replaceAll(long, short);
1442
1461
  return out;
1443
1462
  };
1444
- const KEY = " · ctrl+r";
1463
+ const KEY = " · ctrl+o";
1445
1464
  const keyW = KEY.length;
1446
1465
  const key = `${p.dim}${KEY}${p.reset}`;
1447
1466
  if (words === "") {
@@ -1585,11 +1604,11 @@ function toolBlockBody(c, W) {
1585
1604
  ? delegateSettled(c, W)
1586
1605
  : // TUI2-R1.5 ④(c) (VD-5): a settled shell collapses like
1587
1606
  // every other settled call. It used to keep its last
1588
- // rows plus a "+N earlier rows · ctrl+r" cut FOREVER —
1607
+ // rows plus a "+N earlier rows · ctrl+o" cut FOREVER —
1589
1608
  // six rows per call, so three shells owned a screen. The
1590
1609
  // approved R1 prototype's state 2 is one line; the head
1591
1610
  // row's own suffix already names the count and the key,
1592
- // and ctrl+r shows the whole block, not a five-row window
1611
+ // and ctrl+o shows the whole block, not a five-row window
1593
1612
  // of it.
1594
1613
  []
1595
1614
  : c.state === "running"
@@ -1639,7 +1658,7 @@ function shellTail(text, W) {
1639
1658
  if (rows.length <= CAP_SHELL_SETTLED)
1640
1659
  return rows;
1641
1660
  const kept = CAP_SHELL_SETTLED - 1;
1642
- const cut = foldLine(`${p.dim}${NOTE_ROW}+${rows.length - kept} earlier rows · ctrl+r${p.reset}`, W);
1661
+ const cut = foldLine(`${p.dim}${NOTE_ROW}+${rows.length - kept} earlier rows · ctrl+o${p.reset}`, W);
1643
1662
  return [...rows.slice(rows.length - kept), ...cut];
1644
1663
  }
1645
1664
  /** The error text head: the FIRST rows, capped at 3 — the answer is at
@@ -1659,7 +1678,7 @@ function errorBody(c, W) {
1659
1678
  const rows = blockRows(c.resultText.split("\n").slice(skipFirst).join("\n"), W);
1660
1679
  if (rows.length <= CAP_ERROR)
1661
1680
  return rows;
1662
- const cut = foldLine(`${p.dim}${NOTE_ROW}+${rows.length - (CAP_ERROR - 1)} more · ctrl+r${p.reset}`, W);
1681
+ const cut = foldLine(`${p.dim}${NOTE_ROW}+${rows.length - (CAP_ERROR - 1)} more · ctrl+o${p.reset}`, W);
1663
1682
  return [...rows.slice(0, CAP_ERROR - 1), ...cut];
1664
1683
  }
1665
1684
  /** The running tool's FIXED-height window (W8): exactly 3 rows from
@@ -1686,7 +1705,7 @@ function liveWindow(text, W) {
1686
1705
  rows.push(""); // R7a: blank, not a bar
1687
1706
  return rows;
1688
1707
  }
1689
- const cut = foldLine(`${p.dim}${NOTE_ROW}+${rows.length - (CAP_LIVE_WINDOW - 1)} earlier rows · ctrl+r${p.reset}`, W);
1708
+ const cut = foldLine(`${p.dim}${NOTE_ROW}+${rows.length - (CAP_LIVE_WINDOW - 1)} earlier rows · ctrl+o${p.reset}`, W);
1690
1709
  return [...rows.slice(rows.length - (CAP_LIVE_WINDOW - 1)), ...cut];
1691
1710
  }
1692
1711
  /**
@@ -1891,7 +1910,7 @@ export function diffBody(diff, W, expanded = false) {
1891
1910
  // W17: the └ cut is ONE row at every width — the count leads, the
1892
1911
  // expand affordances are cuttable (the same one-line shape as W12's
1893
1912
  // delegate row and W18's status row).
1894
- const cut = (n) => oneLineRow(p, `+${n} rows · ctrl+r to expand · /last for the full diff`, W);
1913
+ const cut = (n) => oneLineRow(p, `+${n} rows · ctrl+o to expand · /last for the full diff`, W);
1895
1914
  // W17: the floor — the head window shows the lines whose fold starts
1896
1915
  // before `head` rows; the tail window the lines whose fold ENDS after
1897
1916
  // `rows.length - tail` (starts[i+1] is line i's end). When the pair
@@ -1940,7 +1959,7 @@ class MarkdownBlock {
1940
1959
  return renderBlock(this.cell.block, W);
1941
1960
  }
1942
1961
  }
1943
- /** The ⚠ / notice lines — the error surface. */
1962
+ /** The notice lines — the error surface. */
1944
1963
  class ErrorLine {
1945
1964
  cell;
1946
1965
  constructor(cell) {
@@ -2062,9 +2081,9 @@ export function formatDuration(totalSeconds) {
2062
2081
  * the compositor-derived counts (the model tail rides AFTER — never
2063
2082
  * model-controlled), the active item first with ▸ (the menu's "the
2064
2083
  * current one"), pending next (≤2), the done items COLLAPSED behind the
2065
- * W10 cut family `└ +N done · ctrl+r`, overflow pending behind
2066
- * `└ +N more · ctrl+r` — every row cut at W so the cap holds at every
2067
- * width. ctrl+r (W15) toggles the full list in place (expanded). SETTLED
2084
+ * W10 cut family `└ +N done · ctrl+o`, overflow pending behind
2085
+ * `└ +N more · ctrl+o` — every row cut at W so the cap holds at every
2086
+ * width. ctrl+o (W15) toggles the full list in place (expanded). SETTLED
2068
2087
  * (done:true): the recap idiom `task done · N items · <duration>` + the
2069
2088
  * FULL final item list in the checklist's existing shape (▖/□/▣ —
2070
2089
  * indented two, the glyph leads, no │ gutter).
@@ -2087,7 +2106,7 @@ class Checklist {
2087
2106
  : `task · ${plural(items.length, "item")} · ${active.length} active · ${doneCount} done`;
2088
2107
  const header = `${p.bold}✦${p.reset} ${escapeTerminal(fixed + tail)}`;
2089
2108
  // the FULL-list forms: SETTLED — the durable record (the fold is
2090
- // fine — committed content wraps naturally) — and the LIVE ctrl+r
2109
+ // fine — committed content wraps naturally) — and the LIVE ctrl+o
2091
2110
  // toggle (the header CUTS — the block stays one window high; the
2092
2111
  // expanded rows show the ▣ the collapse hid). The live flag picks
2093
2112
  // the glyphs: the settled list keeps the durable ▖, the expanded
@@ -2105,7 +2124,7 @@ class Checklist {
2105
2124
  // LIVE — the fixed window: the header + the item rows CUT at W
2106
2125
  // (one screen row each — the block's height is its row count,
2107
2126
  // CAP_TASK_LIVE, at every width). The cut is the momentary view;
2108
- // the settle (and the ctrl+r toggle) show everything.
2127
+ // the settle (and the ctrl+o toggle) show everything.
2109
2128
  const itemRows = [];
2110
2129
  if (active.length > 0)
2111
2130
  itemRows.push(` ${p.bold}▸${p.reset} ${escapeTerminal(active[0].text)}`);
@@ -2113,9 +2132,9 @@ class Checklist {
2113
2132
  itemRows.push(` □ ${escapeTerminal(item.text)}`);
2114
2133
  const more = pending.length - 2;
2115
2134
  if (more > 0)
2116
- itemRows.push(` ${p.dim}└ +${more} more · ctrl+r${p.reset}`);
2135
+ itemRows.push(` ${p.dim}└ +${more} more · ctrl+o${p.reset}`);
2117
2136
  if (doneCount > 0)
2118
- itemRows.push(` ${p.dim}└ +${doneCount} done · ctrl+r${p.reset}`);
2137
+ itemRows.push(` ${p.dim}└ +${doneCount} done · ctrl+o${p.reset}`);
2119
2138
  return [cutLine(header, W), ...itemRows.map((r) => cutLine(r, W))];
2120
2139
  }
2121
2140
  }
@@ -2128,7 +2147,7 @@ class Checklist {
2128
2147
  * W21: the question param is gone — the old question slot retires; a
2129
2148
  * pending approval's status IS the panel's (the compositor derives
2130
2149
  * it from the bound panel state). */
2131
- /** R8b — THE IDLE HINT GIVES WAY IN ORDER, and `ctrl+o` is on it.
2150
+ /** R8b — THE IDLE HINT GIVES WAY IN ORDER, and `ctrl+r` is on it.
2132
2151
  *
2133
2152
  * The transcript viewer shipped in 0.19.0 and was reachable only from
2134
2153
  * the `?` sheet: not on the banner's key line, not here. A feature
@@ -2139,17 +2158,17 @@ class Checklist {
2139
2158
  * when it does not fit — a longer string would take `/ commands` down
2140
2159
  * with it on a narrow terminal. So the forms are a ladder, and the
2141
2160
  * order says which affordance is least replaceable: `/ commands`
2142
- * survives longest because it is the door to everything; `ctrl+o`
2161
+ * survives longest because it is the door to everything; `ctrl+r`
2143
2162
  * outranks `↑ history` because pressing up is how a person finds the
2144
- * history by accident, and nothing finds ctrl+o by accident. */
2163
+ * history by accident, and nothing finds ctrl+r by accident. */
2145
2164
  export function idleHint(room) {
2146
2165
  // The third rung is today's hint, kept so that NO width loses
2147
2166
  // something that used to fit: without it, a room of 24-30 columns
2148
2167
  // fell all the way to `/ commands` even though the old form fitted.
2149
2168
  // So the ladder is not a strict ranking of the three affordances —
2150
- // it is the widest honest form at each room, and ctrl+o is on the
2169
+ // it is the widest honest form at each room, and ctrl+r is on the
2151
2170
  // first two rungs rather than on all of them.
2152
- for (const form of [" / commands · ↑ history · ctrl+o transcript", " / commands · ctrl+o transcript", " / commands · ↑ history", " / commands"]) {
2171
+ for (const form of [" / commands · ↑ history · ctrl+r transcript", " / commands · ctrl+r transcript", " / commands · ↑ history", " / commands"]) {
2153
2172
  if (visibleWidth(form) <= room)
2154
2173
  return form;
2155
2174
  }
package/dist/render.d.ts CHANGED
@@ -79,7 +79,7 @@ export interface Palette {
79
79
  readonly washEnd: string;
80
80
  /** R7a — THE FOCUS MARKER'S EMPHASIS, and it is not a background.
81
81
  *
82
- * DC-3 gave the `ctrl+r` token the wash, which is a BACKGROUND once
82
+ * DC-3 gave the `ctrl+o` token the wash, which is a BACKGROUND once
83
83
  * a ground is resolved: `48;5;236` on dark reads as a black block
84
84
  * behind the key, on a row that is otherwise plain text. The owner
85
85
  * asked for it gone. The invariant DC-3 was serving — exactly one
@@ -94,6 +94,31 @@ export interface Palette {
94
94
  * is the emphasis. It closes by re-opening the palette's own dim,
95
95
  * like `washEnd`, so the surrounding span survives. */
96
96
  readonly lift: string;
97
+ /** R9 P3 — THE ONE GREY ALLOWED ON THE WASH.
98
+ *
99
+ * §2.1 bars `dim` from the wash and the measurement is why: `#767676`
100
+ * on `#EEEEEE` is 3.91:1, under the 4.5:1 floor. A washed surface
101
+ * carrying metadata rows — a slab's `… N earlier lines`, its outcome
102
+ * line — still wants them quieter than the output they annotate, so
103
+ * the palette gains a grey chosen FOR the wash rather than against
104
+ * the ground:
105
+ *
106
+ * light 241 `#626262` 5.26:1 on the wash, 6.10:1 on the ground
107
+ * dark 247 `#9E9E9E` 4.93:1 on the wash, 6.22:1 on the ground
108
+ *
109
+ * §2.1 is untouched — `dim` still may not sit on the wash. This is a
110
+ * different token with a different job, the way `warn` was the mono
111
+ * ruling's own set gaining its missing member.
112
+ *
113
+ * With NO ground it is NOTHING: §3.1 forbids an absolute foreground
114
+ * in a palette that has not established a background, and rung 4's
115
+ * wash is reverse video, where any foreground grey inverts into a
116
+ * grey block. Body text on the surface is the correct degradation.
117
+ * It closes with 39 (the default foreground) rather than SGR 0, for
118
+ * the reason `washEnd` closes with 49: the wash underneath it must
119
+ * survive the close. */
120
+ readonly washDim: string;
121
+ readonly washDimEnd: string;
97
122
  readonly reset: string;
98
123
  }
99
124
  /**
package/dist/render.js CHANGED
@@ -28,7 +28,20 @@ const BASE = { bold: "\x1b[1m", dim: "\x1b[2m", red: "\x1b[31m", green: "\x1b[32
28
28
  * 4's principle exactly: when the ground is unknown, use the thing that
29
29
  * is correct on any ground rather than guessing one.
30
30
  */
31
- const withWash = (wash, washEnd, red = BASE.red, dim = BASE.dim) => ({ ...BASE, red, dim, wash, washEnd, code: wash, lift: "\x1b[22m\x1b[39m\x1b[1m" });
31
+ /** `washDimEnd` is DERIVED, never passed: a grey that cannot be closed
32
+ * without taking the wash with it is not a usable token, and deriving
33
+ * the close makes the pair impossible to mis-wire at a call site. */
34
+ const withWash = (wash, washEnd, red = BASE.red, dim = BASE.dim, washDim = "") => ({
35
+ ...BASE,
36
+ red,
37
+ dim,
38
+ wash,
39
+ washEnd,
40
+ washDim,
41
+ washDimEnd: washDim === "" ? "" : "\x1b[39m",
42
+ code: wash,
43
+ lift: "\x1b[22m\x1b[39m\x1b[1m",
44
+ });
32
45
  /**
33
46
  * DC-3 — one table per ground.
34
47
  *
@@ -50,13 +63,13 @@ const withWash = (wash, washEnd, red = BASE.red, dim = BASE.dim) => ({ ...BASE,
50
63
  * attribute is exactly the "correct on any ground" degradation there.
51
64
  */
52
65
  export const COLOR_NEUTRAL = withWash("\x1b[7m", "\x1b[27m");
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");
66
+ export const COLOR_LIGHT = withWash("\x1b[48;5;255m", "\x1b[49m", "\x1b[38;5;124m", "\x1b[38;5;243m", "\x1b[38;5;241m");
67
+ export const COLOR_DARK = withWash("\x1b[48;5;236m", "\x1b[49m", "\x1b[38;5;173m", "\x1b[38;5;246m", "\x1b[38;5;247m");
55
68
  /** The historical name — the palette for a colour TTY whose ground has
56
69
  * not been established. Unchanged in every byte except `code`, which
57
70
  * was the defect. */
58
71
  export const COLOR_ON = COLOR_NEUTRAL;
59
- export const COLOR_OFF = { bold: "", dim: "", red: "", green: "", warn: "", code: "", italic: "", italicEnd: "", underline: "", underlineEnd: "", rv: "", rvEnd: "", wash: "", washEnd: "", lift: "", reset: "" };
72
+ export const COLOR_OFF = { bold: "", dim: "", red: "", green: "", warn: "", code: "", italic: "", italicEnd: "", underline: "", underlineEnd: "", rv: "", rvEnd: "", wash: "", washEnd: "", washDim: "", washDimEnd: "", lift: "", reset: "" };
60
73
  /** DC-3 — the resolved ground, set once at startup when the terminal
61
74
  * answers (see `ground.ts`). It starts UNKNOWN and may stay that way
62
75
  * forever; that is a supported state, not a failure. */
@@ -414,7 +427,7 @@ export function bannerLines(W, H, version, extensionsText, resume = [], now = Da
414
427
  if (meta !== undefined && W >= 40) {
415
428
  // R2/DC-2's device: the keys row is a list of independent clauses,
416
429
  // so a narrow terminal drops whole clauses from the end rather than
417
- // cutting one in half. `ctrl+r ex (+8)` teaches nothing.
430
+ // cutting one in half. `ctrl+o ex (+8)` teaches nothing.
418
431
  const clauses = BANNER_KEYS.split(" \u00b7 ");
419
432
  let keys = clauses[0];
420
433
  for (let n = clauses.length; n > 1; n -= 1) {
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: " project trust",
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) `,
@@ -109,11 +109,11 @@ export function uncertainView(name, executionId) {
109
109
  name: "uncertain execution",
110
110
  title: `${name} (${executionId})`,
111
111
  speaker: "kiso",
112
- statusText: " uncertain execution",
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"],
116
- fallbackQuestion: `⚠ interrupted execution: ${escapeTerminal(name)} (${executionId}) — rerun it? (y)es / (n)o `,
116
+ fallbackQuestion: `interrupted execution: ${escapeTerminal(name)} (${executionId}) — rerun it? (y)es / (n)o `,
117
117
  };
118
118
  }
119
119
  /**
@@ -136,11 +136,11 @@ export function unansweredAskView(executionId) {
136
136
  name: "unanswered question",
137
137
  title: `ask_user (${executionId})`,
138
138
  speaker: "kiso",
139
- statusText: " unanswered question",
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"],
143
- fallbackQuestion: `⚠ an unanswered question was interrupted (${executionId}) — ask it again? (y)es / (n)o `,
143
+ fallbackQuestion: `an unanswered question was interrupted (${executionId}) — ask it again? (y)es / (n)o `,
144
144
  };
145
145
  }
146
146
  /**
@@ -188,12 +188,12 @@ export const KEY_BINDINGS = [
188
188
  { keys: "alt+⏎ / ctrl+⏎", what: "redirect" },
189
189
  { keys: "/", what: "commands" },
190
190
  { keys: "↑↓", what: "history / queue pop" },
191
- { keys: "ctrl+r", what: "expand cells" },
191
+ { keys: "ctrl+o", what: "expand cells" },
192
192
  // R5 — the transcript viewer. It has to be HERE or it does not exist:
193
193
  // R4a retired the printed key from the fold row on the ground that a
194
194
  // row cannot say which fold a key opens, and the sheet is where the
195
195
  // discoverability moved. A surface nobody can find is not a feature.
196
- { keys: "ctrl+o", what: "transcript" },
196
+ { keys: "ctrl+r", what: "transcript" },
197
197
  { keys: "tab", what: "complete (menu / @)" },
198
198
  { keys: "?", what: "this sheet" },
199
199
  { keys: "ctrl+z / ctrl+y", what: "undo / redo" },
@@ -271,7 +271,7 @@ const SHEET_GRID = [
271
271
  [0, 1, 2],
272
272
  [3, 4, 5],
273
273
  [6, 7],
274
- // R5: ctrl+o joins at index 8, so the tail shifts by one. The grid is
274
+ // R5: ctrl+r joins at index 8, so the tail shifts by one. The grid is
275
275
  // a SECOND table that must agree with KEY_BINDINGS and nothing makes
276
276
  // it — adding a binding without touching this silently drops the LAST
277
277
  // one off the sheet, which is exactly what happened on the first
package/dist/width.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * #16e: "charWidth is the width authority"). The table covers the East
4
4
  * Asian Wide/Fullwidth ranges and Emoji_Presentation=Yes; everything
5
5
  * else is one column, the box-drawing/brick glyphs █▀▄▞▸ and the text-
6
- * presentation marks ✓ ✗ included.
6
+ * presentation marks ✓ ✗ included.
7
7
  *
8
8
  * This is the compositor's FLOOR, not a cosmetic detail: a glyph scored
9
9
  * one column that a terminal draws in two makes a line whose measured
package/dist/width.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * #16e: "charWidth is the width authority"). The table covers the East
4
4
  * Asian Wide/Fullwidth ranges and Emoji_Presentation=Yes; everything
5
5
  * else is one column, the box-drawing/brick glyphs █▀▄▞▸ and the text-
6
- * presentation marks ✓ ✗ included.
6
+ * presentation marks ✓ ✗ included.
7
7
  *
8
8
  * This is the compositor's FLOOR, not a cosmetic detail: a glyph scored
9
9
  * one column that a terminal draws in two makes a line whose measured
@@ -19,7 +19,7 @@
19
19
  */
20
20
  /** TUI2-R2pre ① — the Emoji_Presentation=Yes code points inside
21
21
  * U+2000..U+2BFF. The rest of that span is TEXT presentation and stays
22
- * one column: ✓ ✗ ▞ ▸ and the box-drawing rails are all narrow, and
22
+ * one column: ✓ ✗ ▞ ▸ and the box-drawing rails are all narrow, and
23
23
  * widening any of them would move every card head on the screen. Listed
24
24
  * as ranges because that is what the property is — the singles are
25
25
  * singles in Unicode too. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-tui-cells",
3
- "version": "0.20.4",
3
+ "version": "0.21.1",
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",