@vincemakes/kiso-tui-cells 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/render.d.ts CHANGED
@@ -43,6 +43,16 @@ export interface Palette {
43
43
  * missing member, not a fourth colour. */
44
44
  readonly warn: string;
45
45
  readonly code: string;
46
+ /** TUI2-MD (MD-1, the owner's circle) — the markdown round's ONE new
47
+ * member. `*italic*` needs a rendering, and under the mono discipline
48
+ * the answer cannot be a colour: SGR 3 is an ATTRIBUTE, it costs the
49
+ * alphabet nothing chromatic, and a terminal without italics simply
50
+ * draws the text — a harmless degradation rather than a lie.
51
+ * It ships with its own close (23) for the same reason `rv` does: an
52
+ * italic span inside a bold heading must be able to end WITHOUT the
53
+ * SGR-0 that would strand the heading's own style. */
54
+ readonly italic: string;
55
+ readonly italicEnd: string;
46
56
  readonly rv: string;
47
57
  readonly rvEnd: string;
48
58
  readonly reset: string;
package/dist/render.js CHANGED
@@ -6,8 +6,8 @@
6
6
  * dependencies (the tui-cells package has none).
7
7
  */
8
8
  import { charWidth, displayWidth } from "./width.js";
9
- export const COLOR_ON = { bold: "\x1b[1m", dim: "\x1b[2m", red: "\x1b[31m", green: "\x1b[32m", warn: "\x1b[33m", code: "\x1b[38;5;252m", rv: "\x1b[7m", rvEnd: "\x1b[27m", reset: "\x1b[0m" };
10
- export const COLOR_OFF = { bold: "", dim: "", red: "", green: "", warn: "", code: "", rv: "", rvEnd: "", reset: "" };
9
+ export const COLOR_ON = { bold: "\x1b[1m", dim: "\x1b[2m", red: "\x1b[31m", green: "\x1b[32m", warn: "\x1b[33m", code: "\x1b[38;5;252m", italic: "\x1b[3m", italicEnd: "\x1b[23m", rv: "\x1b[7m", rvEnd: "\x1b[27m", reset: "\x1b[0m" };
10
+ export const COLOR_OFF = { bold: "", dim: "", red: "", green: "", warn: "", code: "", italic: "", italicEnd: "", rv: "", rvEnd: "", reset: "" };
11
11
  export function palette() {
12
12
  return process.env.NO_COLOR === undefined && process.stdout.isTTY ? COLOR_ON : COLOR_OFF;
13
13
  }
package/dist/strings.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
- * tui-cells — the human-facing STRINGS (KC3 slice 1, the ADR-0041
3
- * escape hatch: extraction, never a raise): the readline prompt, the
2
+ * tui-cells — the human-facing STRINGS (KC3 slice 1, the escape hatch
3
+ * of ADR-0043, which supersedes ADR-0041): the readline prompt, the
4
4
  * project-trust listing rows and its panel view, the uncertain
5
5
  * execution's panel view, and the non-TTY not-trusted note. All five
6
6
  * were built inline in the CLI's trust-ui.ts before the move.
@@ -53,10 +53,14 @@ export declare function projectTrustView(root: string, files: readonly TrustArti
53
53
  * stderr by the caller; never silent. */
54
54
  export declare function projectUntrustedNote(count: number, root: string): string;
55
55
  /** rounds 8/10 — the uncertain execution's panel: the SIMPLE flavor
56
- * again, with the options re-labelled in the rule line (1 rerun · 3
57
- * abandon). The tool name is escaped for the dock-less fallback
58
- * question because it reaches the terminal as raw text there; the
59
- * panel's own rows are escaped by the panel renderer. */
56
+ * again. TUI2-R3v2 ①: the option labels used to be smuggled into the
57
+ * rule line ("— 1 rerun · 3 abandon") because the panel only ever
58
+ * rendered "Yes" and "No"; they ride `simpleOptions` now and land on the
59
+ * rows themselves, which is both where they belong and the only way they
60
+ * stay true — the digits moved, and copy naming a digit it does not own
61
+ * goes stale silently. The tool name is escaped for the dock-less
62
+ * fallback question because it reaches the terminal as raw text there;
63
+ * the panel's own rows are escaped by the panel renderer. */
60
64
  export declare function uncertainView(name: string, executionId: string): PanelView;
61
65
  /**
62
66
  * KC3.5 — the SAME uncertainty gate, said honestly for an ask_user call.
@@ -70,7 +74,7 @@ export declare function uncertainView(name: string, executionId: string): PanelV
70
74
  * (The round's ① probe pinned why this surface exists at all: the
71
75
  * shipped recovery blocks on a started-unreported execution regardless
72
76
  * of idempotency, so an interrupted ask meets this gate on the way
73
- * back. Re-asking is safe — that is what "1 re-ask" says out loud.)
77
+ * back. Re-asking is safe — that is what the first option says out loud.)
74
78
  */
75
79
  export declare function unansweredAskView(executionId: string): PanelView;
76
80
  /** An extension as the banner names it — the live `connecting` flag is
@@ -119,24 +123,20 @@ export declare function displayVerb(name: string): string;
119
123
  * one dim line rather than four table rows, because they apply only
120
124
  * while a panel is up. */
121
125
  /**
122
- * TUI2-R1.5 pin 6 — this row has to be true of BOTH panel flavors, and
123
- * "digits select" was wrong in both directions at once.
126
+ * TUI2-R1.5 pin 6 — this row has to be true of BOTH panel flavors.
124
127
  *
125
- * On an APPROVAL a digit only moves the selection (editor's #panelSelect
126
- * sets `sel` and renders); ENTER is what resolves it. A reader who
127
- * pressed 1 and walked away had approved nothing — the worst kind of
128
- * false affordance, on the surface where the stakes are a side effect.
128
+ * The R1.5 wording ("digits pick · ⏎ confirms") was the sentence that
129
+ * covered an approval where a digit only SELECTED and an ask where a
130
+ * digit ANSWERED. TUI2-R3v2 ① removed the disagreement it was papering
131
+ * over: every panel is a list with a bar on it, ↑↓ move the bar, ⏎ (or a
132
+ * click) takes the row under it, and a digit takes its row outright.
133
+ * One sentence, now true of every flavor for the same reason rather than
134
+ * by careful omission.
129
135
  *
130
- * On an ASK the opposite: a digit on a SINGLE-choice question answers it
131
- * and advances the walk (ask-panel's askKey → advance), so "select"
132
- * undersold it. A multi-select question toggles and waits for enter,
133
- * like the approval.
134
- *
135
- * "digits pick · ⏎ confirms" is the sentence both flavors satisfy. The
136
- * single-choice fast path — where the confirm is implicit — is the one
137
- * thing a single row cannot also carry; it is an omission, never a lie.
136
+ * The two ask-only gestures keep their clauses because the ask is the
137
+ * only flavor with a set to build and an answer to write.
138
138
  */
139
- export declare const PANEL_KEYS_ROW = "panels: digits pick \u00B7 \u23CE confirms \u00B7 space toggles \u00B7 t types an answer";
139
+ export declare const PANEL_KEYS_ROW = "panels: \u2191\u2193 move \u00B7 \u23CE or click confirms \u00B7 1-4 instant \u00B7 space toggles \u00B7 t types";
140
140
  /**
141
141
  * TUI2-R1 (D) — the sheet, one screen, static.
142
142
  *
package/dist/strings.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
- * tui-cells — the human-facing STRINGS (KC3 slice 1, the ADR-0041
3
- * escape hatch: extraction, never a raise): the readline prompt, the
2
+ * tui-cells — the human-facing STRINGS (KC3 slice 1, the escape hatch
3
+ * of ADR-0043, which supersedes ADR-0041): the readline prompt, the
4
4
  * project-trust listing rows and its panel view, the uncertain
5
5
  * execution's panel view, and the non-TTY not-trusted note. All five
6
6
  * were built inline in the CLI's trust-ui.ts before the move.
@@ -64,10 +64,14 @@ export function projectUntrustedNote(count, root) {
64
64
  return `[project .kiso] found ${count} artifact(s) in ${root} — not trusted, not loaded (run kiso interactively once to decide)`;
65
65
  }
66
66
  /** rounds 8/10 — the uncertain execution's panel: the SIMPLE flavor
67
- * again, with the options re-labelled in the rule line (1 rerun · 3
68
- * abandon). The tool name is escaped for the dock-less fallback
69
- * question because it reaches the terminal as raw text there; the
70
- * panel's own rows are escaped by the panel renderer. */
67
+ * again. TUI2-R3v2 ①: the option labels used to be smuggled into the
68
+ * rule line ("— 1 rerun · 3 abandon") because the panel only ever
69
+ * rendered "Yes" and "No"; they ride `simpleOptions` now and land on the
70
+ * rows themselves, which is both where they belong and the only way they
71
+ * stay true — the digits moved, and copy naming a digit it does not own
72
+ * goes stale silently. The tool name is escaped for the dock-less
73
+ * fallback question because it reaches the terminal as raw text there;
74
+ * the panel's own rows are escaped by the panel renderer. */
71
75
  export function uncertainView(name, executionId) {
72
76
  return {
73
77
  flavor: "simple",
@@ -76,7 +80,8 @@ export function uncertainView(name, executionId) {
76
80
  speaker: "kiso",
77
81
  statusText: "▸ uncertain execution",
78
82
  args: { kind: "text", lines: [executionId] },
79
- ruleOverride: "did the interrupted execution apply? — 1 rerun · 3 abandon",
83
+ ruleOverride: "did the interrupted execution apply?",
84
+ simpleOptions: ["rerun it", "abandon it"],
80
85
  fallbackQuestion: `⚠ interrupted execution: ${escapeTerminal(name)} (${executionId}) — did it apply? (y)es / (n)o `,
81
86
  };
82
87
  }
@@ -92,7 +97,7 @@ export function uncertainView(name, executionId) {
92
97
  * (The round's ① probe pinned why this surface exists at all: the
93
98
  * shipped recovery blocks on a started-unreported execution regardless
94
99
  * of idempotency, so an interrupted ask meets this gate on the way
95
- * back. Re-asking is safe — that is what "1 re-ask" says out loud.)
100
+ * back. Re-asking is safe — that is what the first option says out loud.)
96
101
  */
97
102
  export function unansweredAskView(executionId) {
98
103
  return {
@@ -102,7 +107,8 @@ export function unansweredAskView(executionId) {
102
107
  speaker: "kiso",
103
108
  statusText: "▸ unanswered question",
104
109
  args: { kind: "text", lines: [executionId] },
105
- ruleOverride: "an unanswered question was interrupted — ask it again? — 1 re-ask · 3 drop",
110
+ ruleOverride: "an unanswered question was interrupted — ask it again?",
111
+ simpleOptions: ["ask it again", "drop it"],
106
112
  fallbackQuestion: `⚠ an unanswered question was interrupted (${executionId}) — ask it again? (y)es / (n)o `,
107
113
  };
108
114
  }
@@ -193,24 +199,20 @@ export function displayVerb(name) {
193
199
  * one dim line rather than four table rows, because they apply only
194
200
  * while a panel is up. */
195
201
  /**
196
- * TUI2-R1.5 pin 6 — this row has to be true of BOTH panel flavors, and
197
- * "digits select" was wrong in both directions at once.
202
+ * TUI2-R1.5 pin 6 — this row has to be true of BOTH panel flavors.
198
203
  *
199
- * On an APPROVAL a digit only moves the selection (editor's #panelSelect
200
- * sets `sel` and renders); ENTER is what resolves it. A reader who
201
- * pressed 1 and walked away had approved nothing — the worst kind of
202
- * false affordance, on the surface where the stakes are a side effect.
204
+ * The R1.5 wording ("digits pick · ⏎ confirms") was the sentence that
205
+ * covered an approval where a digit only SELECTED and an ask where a
206
+ * digit ANSWERED. TUI2-R3v2 ① removed the disagreement it was papering
207
+ * over: every panel is a list with a bar on it, ↑↓ move the bar, ⏎ (or a
208
+ * click) takes the row under it, and a digit takes its row outright.
209
+ * One sentence, now true of every flavor for the same reason rather than
210
+ * by careful omission.
203
211
  *
204
- * On an ASK the opposite: a digit on a SINGLE-choice question answers it
205
- * and advances the walk (ask-panel's askKey → advance), so "select"
206
- * undersold it. A multi-select question toggles and waits for enter,
207
- * like the approval.
208
- *
209
- * "digits pick · ⏎ confirms" is the sentence both flavors satisfy. The
210
- * single-choice fast path — where the confirm is implicit — is the one
211
- * thing a single row cannot also carry; it is an omission, never a lie.
212
+ * The two ask-only gestures keep their clauses because the ask is the
213
+ * only flavor with a set to build and an answer to write.
212
214
  */
213
- export const PANEL_KEYS_ROW = "panels: digits pick · ⏎ confirms · space toggles · t types an answer";
215
+ export const PANEL_KEYS_ROW = "panels: ↑↓ move · ⏎ or click confirms · 1-4 instant · space toggles · t types";
214
216
  /** The sheet's grid: the first six bindings in two 3-column rows, the
215
217
  * last four in two 2-column rows (the wide entries get the room). The
216
218
  * COLUMN STOPS are the prototype's absolute positions, floored by the
package/dist/width.d.ts CHANGED
@@ -19,10 +19,24 @@
19
19
  */
20
20
  /** A code point's display width: 2 for the wide ranges, 1 otherwise. */
21
21
  export declare function charWidth(cp: number): number;
22
+ /** TUI2-MD ③ — may a row break immediately before/after this code
23
+ * point? True for the CJK scripts (they break between any two
24
+ * characters), false for everything else INCLUDING the wide
25
+ * pictographs. The wrapper asks this; the width table answers it. */
26
+ export declare function breakable(cp: number): boolean;
22
27
  /** Display width of a code-point array (cursor math, scrolling). */
23
28
  export declare function widthOf(chars: readonly number[]): number;
24
29
  /** Display width of a string. */
25
30
  export declare function displayWidth(text: string): number;
31
+ /** The visible width of a RENDERED line — the same table, asked with
32
+ * the SGR/CSI sequences skipped. The compositor's invariant ① measures
33
+ * with this, so every producer of a screen row must measure with it
34
+ * too. TUI2-MD ⑤: moved here verbatim from components.ts, where it had
35
+ * lived since the extraction. The markdown renderer needs it and
36
+ * components.ts needs the markdown renderer — and a width question
37
+ * belongs to the width authority anyway. components.ts re-exports it,
38
+ * so every existing importer and the barrel are untouched. */
39
+ export declare function visibleWidth(line: string): number;
26
40
  /** A LEAD's display width — the prompt / the panel's phase lead,
27
41
  * ANSI-stripped. W23: the ONE width authority shared by the editor
28
42
  * (selfRender, #reflow), the compositor's #inputRow, and editCol — a
package/dist/width.js CHANGED
@@ -58,62 +58,84 @@ const EMOJI_PRESENTATION = [
58
58
  [0x2b50, 0x2b50],
59
59
  [0x2b55, 0x2b55],
60
60
  ];
61
- /** A code point's display width: 2 for the wide ranges, 1 otherwise. */
62
- export function charWidth(cp) {
61
+ /** TUI2-MD ③ — the CJK half of the wide table, split out so the SAME
62
+ * ranges answer a SECOND question: may a line break here? The width
63
+ * authority stays one table; the break class is a view of it, never a
64
+ * fork (a second copy would drift, and a drifted width table is the
65
+ * composer clobber all over again).
66
+ *
67
+ * These scripts break between any two characters, which is why a
68
+ * space-free CJK run must not be treated as one unbreakable word: a
69
+ * whitespace-only wrapper cannot place it at all. */
70
+ function cjkWide(cp) {
63
71
  if (cp >= 0x1100 && cp <= 0x115f)
64
- return 2; // hangul jamo
72
+ return true; // hangul jamo
65
73
  if (cp >= 0x2e80 && cp <= 0x303e)
66
- return 2; // radicals .. CJK punctuation
74
+ return true; // radicals .. CJK punctuation
67
75
  if (cp >= 0x3041 && cp <= 0x33ff)
68
- return 2; // kana, CJK compat
76
+ return true; // kana, CJK compat
69
77
  if (cp >= 0x3400 && cp <= 0x4dbf)
70
- return 2; // CJK ext A
78
+ return true; // CJK ext A
71
79
  if (cp >= 0x4e00 && cp <= 0x9fff)
72
- return 2; // CJK unified
80
+ return true; // CJK unified
73
81
  if (cp >= 0xa000 && cp <= 0xa4cf)
74
- return 2; // yi
82
+ return true; // yi
75
83
  if (cp >= 0xa960 && cp <= 0xa97f)
76
- return 2; // hangul jamo ext
84
+ return true; // hangul jamo ext
77
85
  if (cp >= 0xac00 && cp <= 0xd7a3)
78
- return 2; // hangul syllables
86
+ return true; // hangul syllables
79
87
  if (cp >= 0xf900 && cp <= 0xfaff)
80
- return 2; // CJK compat ideographs
88
+ return true; // CJK compat ideographs
81
89
  if (cp >= 0xfe10 && cp <= 0xfe19)
82
- return 2; // vertical forms
90
+ return true; // vertical forms
83
91
  if (cp >= 0xfe30 && cp <= 0xfe6f)
84
- return 2; // CJK compat forms
92
+ return true; // CJK compat forms
85
93
  if (cp >= 0xff00 && cp <= 0xff60)
86
- return 2; // fullwidth forms
94
+ return true; // fullwidth forms
87
95
  if (cp >= 0xffe0 && cp <= 0xffe6)
88
- return 2; // fullwidth signs
96
+ return true; // fullwidth signs
97
+ return cp >= 0x20000 && cp <= 0x3fffd; // CJK ext B..G
98
+ }
99
+ /** The PICTOGRAPHIC half: wide, and deliberately NOT breakable — a
100
+ * ZWJ/variation sequence must survive a line break whole. */
101
+ function emojiWide(cp) {
89
102
  // TUI2-R1.5 shipped only two of the pictographic ranges; the holes
90
103
  // (transport, mahjong/cards, enclosed, colored shapes, the extended
91
104
  // block) were scored ONE column while every terminal draws them in
92
105
  // two — the composer clobber of the owner's field report (①).
93
106
  if (cp === 0x1f004 || cp === 0x1f0cf)
94
- return 2; // mahjong red dragon, joker
107
+ return true; // mahjong red dragon, joker
95
108
  if (cp >= 0x1f18e && cp <= 0x1f19a)
96
- return 2; // enclosed alphanumerics
109
+ return true; // enclosed alphanumerics
97
110
  if (cp >= 0x1f200 && cp <= 0x1f251)
98
- return 2; // enclosed ideographic
111
+ return true; // enclosed ideographic
99
112
  if (cp >= 0x1f300 && cp <= 0x1f64f)
100
- return 2; // emoji (misc + emoticons)
113
+ return true; // emoji (misc + emoticons)
101
114
  if (cp >= 0x1f680 && cp <= 0x1f6ff)
102
- return 2; // transport + map
115
+ return true; // transport + map
103
116
  if (cp >= 0x1f7e0 && cp <= 0x1f7eb)
104
- return 2; // colored circles + squares
117
+ return true; // colored circles + squares
105
118
  if (cp >= 0x1f900 && cp <= 0x1f9ff)
106
- return 2; // supplemental emoji
119
+ return true; // supplemental emoji
107
120
  if (cp >= 0x1fa70 && cp <= 0x1faff)
108
- return 2; // symbols + pictographs ext-A
109
- if (cp >= 0x20000 && cp <= 0x3fffd)
110
- return 2; // CJK ext B..G
121
+ return true; // symbols + pictographs ext-A
111
122
  if (cp >= 0x231a && cp <= 0x2b55) {
112
123
  for (const [lo, hi] of EMOJI_PRESENTATION)
113
124
  if (cp >= lo && cp <= hi)
114
- return 2;
125
+ return true;
115
126
  }
116
- return 1;
127
+ return false;
128
+ }
129
+ /** A code point's display width: 2 for the wide ranges, 1 otherwise. */
130
+ export function charWidth(cp) {
131
+ return cjkWide(cp) || emojiWide(cp) ? 2 : 1;
132
+ }
133
+ /** TUI2-MD ③ — may a row break immediately before/after this code
134
+ * point? True for the CJK scripts (they break between any two
135
+ * characters), false for everything else INCLUDING the wide
136
+ * pictographs. The wrapper asks this; the width table answers it. */
137
+ export function breakable(cp) {
138
+ return cjkWide(cp);
117
139
  }
118
140
  /** Display width of a code-point array (cursor math, scrolling). */
119
141
  export function widthOf(chars) {
@@ -129,6 +151,31 @@ export function displayWidth(text) {
129
151
  w += charWidth(ch.codePointAt(0));
130
152
  return w;
131
153
  }
154
+ /** The visible width of a RENDERED line — the same table, asked with
155
+ * the SGR/CSI sequences skipped. The compositor's invariant ① measures
156
+ * with this, so every producer of a screen row must measure with it
157
+ * too. TUI2-MD ⑤: moved here verbatim from components.ts, where it had
158
+ * lived since the extraction. The markdown renderer needs it and
159
+ * components.ts needs the markdown renderer — and a width question
160
+ * belongs to the width authority anyway. components.ts re-exports it,
161
+ * so every existing importer and the barrel are untouched. */
162
+ export function visibleWidth(line) {
163
+ let w = 0;
164
+ for (let i = 0; i < line.length;) {
165
+ if (line[i] === "\x1b") {
166
+ const m = /^\x1b\[[0-9;?]*[A-Za-z]/.exec(line.slice(i));
167
+ if (m !== null) {
168
+ i += m[0].length;
169
+ continue;
170
+ }
171
+ i += 1;
172
+ continue;
173
+ }
174
+ w += displayWidth(line[i]);
175
+ i += 1;
176
+ }
177
+ return w;
178
+ }
132
179
  /** A LEAD's display width — the prompt / the panel's phase lead,
133
180
  * ANSI-stripped. W23: the ONE width authority shared by the editor
134
181
  * (selfRender, #reflow), the compositor's #inputRow, and editCol — a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-tui-cells",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "kiso tui-cells — the components cell renderer (components, diff, width, the render slice). Zero runtime dependencies: input is data, output is bytes.",
5
5
  "type": "module",
6
6
  "license": "MIT",