@vincemakes/kiso-tui-cells 0.14.0 → 0.15.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.
@@ -147,10 +147,6 @@ export type BodyCell = {
147
147
  decidedBy?: string;
148
148
  reason?: string;
149
149
  } | null;
150
- } | {
151
- kind: "text";
152
- text: string;
153
- done: boolean;
154
150
  }
155
151
  /** TUI2-MD ⑤ — ONE markdown block of assistant body text. The cell is
156
152
  * the commit unit the compositor already had, so block-freeze needs
@@ -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, colorInlineCode, renderTerminalGap, renderToolSummary, toolTarget, kUnit, palette, } from "./render.js";
25
+ import { bannerLines, 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
@@ -152,8 +152,6 @@ export function cellComponent(cell) {
152
152
  return new ThinkingFold(cell);
153
153
  case "tool":
154
154
  return new ToolExecution(cell);
155
- case "text":
156
- return new AssistantMessage(cell);
157
155
  case "md":
158
156
  return new MarkdownBlock(cell);
159
157
  case "notice":
@@ -1171,21 +1169,6 @@ function toolCutNote(name, resultText) {
1171
1169
  return `capped by ${escapeTerminal(displayVerb(name))} · /last for the rest`;
1172
1170
  return null;
1173
1171
  }
1174
- /** The assistant body text — wrapped at W, the inline-code tint per
1175
- * row (the #16e rule: a span never matches across rows). */
1176
- class AssistantMessage {
1177
- cell;
1178
- constructor(cell) {
1179
- this.cell = cell;
1180
- }
1181
- render(W, _ctx) {
1182
- // TUI2-R1.5 9 (VD-10): the model's prose is the clearest case of
1183
- // text a human reads — it wraps at word boundaries.
1184
- const text = escapeTerminal(this.cell.text);
1185
- const wrapped = foldWords(text, W);
1186
- return wrapped.length > 0 ? wrapped.map((l) => colorInlineCode(l)) : [""];
1187
- }
1188
- }
1189
1172
  /** TUI2-MD ⑤ — one markdown block. Pure in (block, W): the same source
1190
1173
  * and the same width give the same bytes, which is the freeze property
1191
1174
  * the commit path relies on. The block carries its own leading blank
package/dist/index.d.ts CHANGED
@@ -14,4 +14,4 @@ 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_ON, colorInlineCode, 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_ON, escapeTerminal, foldResult, foldThinking, kUnit, palette, relativeTime, renderResumeList, renderTerminalGap, renderToolSummary, TAGLINE, toolTarget, truncateRow, type Palette, type ResumeMeta, } from "./render.js";
package/dist/index.js CHANGED
@@ -28,4 +28,4 @@ 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_ON, colorInlineCode, escapeTerminal, foldResult, foldThinking, kUnit, palette, relativeTime, renderResumeList, renderTerminalGap, renderToolSummary, TAGLINE, toolTarget, truncateRow, } from "./render.js";
31
+ export { bannerLines, COLOR_OFF, COLOR_ON, escapeTerminal, foldResult, foldThinking, kUnit, palette, relativeTime, renderResumeList, renderTerminalGap, renderToolSummary, TAGLINE, toolTarget, truncateRow, } from "./render.js";
package/dist/render.d.ts CHANGED
@@ -67,15 +67,6 @@ export declare function palette(): Palette;
67
67
  * EVERY externally-sourced string must pass through this before any output.
68
68
  */
69
69
  export declare function escapeTerminal(text: string): string;
70
- /**
71
- * TUI v5 #16e: the inline-code tint — backtick spans in ONE line of
72
- * assistant body text get the `code` color. Deliberately NOT a markdown
73
- * engine: single level only (`[^`]*` cannot nest), a span never matches
74
- * across lines (the caller passes one line; an opener without a closer
75
- * on the same line stays plain). NO_COLOR / pipes → the codes are empty
76
- * strings → the line passes through byte-identical.
77
- */
78
- export declare function colorInlineCode(line: string): string;
79
70
  /**
80
71
  * v2b — one thinking BLOCK folds to ONE dim line: the first 100 chars, a
81
72
  * " (… /think shows full)" marker when the block is longer. The consumer
@@ -131,9 +122,8 @@ export declare const TAGLINE = "the coding agent that survives kill -9";
131
122
  export declare function truncateRow(row: string, width: number): string;
132
123
  /** v3 §01 (V6-2) + W1: the banner lines for a width W and height H —
133
124
  * the tier table (extends the existing "under 40 columns, skip the
134
- * logo" rule with a HEIGHT input):
135
- * W ≥ 40 and H ≥ 20BIG (the 36x6 wordmark, 2-column indent)
136
- * W ≥ 40 and 14–19 rows → COMPACT (v6's LOGO_ROWS, byte-identical)
125
+ * logo" rule with a HEIGHT input; VD-14 merged the two art tiers):
126
+ * W ≥ 40 and H ≥ 14 → the 2-row wordmark, 2-column indent
137
127
  * anything smaller → text rows only
138
128
  * then the blank, then "vX — tagline" — the art IS the wordmark, so the
139
129
  * text row does not repeat the name — then extensions — then the W5
@@ -153,6 +143,13 @@ export interface ResumeMeta {
153
143
  readonly events: number;
154
144
  readonly runs: number;
155
145
  readonly updatedAt: number;
146
+ /** TT-1B (W5 unification) — the ONE-CELL badge glyph from the
147
+ * picker's BADGE_GLYPH vocabulary, derived by the caller from the
148
+ * SAME projection the picker uses (session-cards — one source of
149
+ * truth about durability). Plain: the banner's uniform dim wrap
150
+ * styles the row; the picker keeps color on its own surface.
151
+ * Absent on every meta → the pre-TT-1B bytes, verbatim. */
152
+ readonly badge?: string;
156
153
  }
157
154
  export declare function relativeTime(updatedAt: number, now: number): string;
158
155
  export declare function renderResumeList(metas: readonly ResumeMeta[], W: number, now: number): string[];
package/dist/render.js CHANGED
@@ -9,7 +9,14 @@ import { charWidth, displayWidth } from "./width.js";
9
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
10
  export const COLOR_OFF = { bold: "", dim: "", red: "", green: "", warn: "", code: "", italic: "", italicEnd: "", rv: "", rvEnd: "", reset: "" };
11
11
  export function palette() {
12
- return process.env.NO_COLOR === undefined && process.stdout.isTTY ? COLOR_ON : COLOR_OFF;
12
+ // PH-1a (finding PH-F5): the no-color.org contract is "present AND
13
+ // non-empty" — the old `=== undefined` check let an EMPTY `NO_COLOR=`
14
+ // (a common shell-profile/CI shape) kill the colors, and through the
15
+ // dock's activation gate (`palette().bold !== ""`) the entire docked
16
+ // UI with them. The v4-round plan recorded this as debugging pitfall ①;
17
+ // it was a bug.
18
+ const noColor = process.env.NO_COLOR;
19
+ return (noColor === undefined || noColor === "") && process.stdout.isTTY ? COLOR_ON : COLOR_OFF;
13
20
  }
14
21
  /**
15
22
  * E group/round 8: strip terminal-injection vectors from MODEL/TOOL text before it
@@ -25,20 +32,6 @@ export function escapeTerminal(text) {
25
32
  .replace(/[\u0080-\u009f]/g, "") // C1
26
33
  .replace(/[\u202a-\u202e\u2066-\u2069]/g, ""); // bidi
27
34
  }
28
- /**
29
- * TUI v5 #16e: the inline-code tint — backtick spans in ONE line of
30
- * assistant body text get the `code` color. Deliberately NOT a markdown
31
- * engine: single level only (`[^`]*` cannot nest), a span never matches
32
- * across lines (the caller passes one line; an opener without a closer
33
- * on the same line stays plain). NO_COLOR / pipes → the codes are empty
34
- * strings → the line passes through byte-identical.
35
- */
36
- export function colorInlineCode(line) {
37
- const p = palette();
38
- if (p.code === "" || p.reset === "")
39
- return line;
40
- return line.replace(/`([^`]*)`/g, `${p.code}\`$1\`${p.reset}`);
41
- }
42
35
  /**
43
36
  * v2b — one thinking BLOCK folds to ONE dim line: the first 100 chars, a
44
37
  * " (… /think shows full)" marker when the block is longer. The consumer
@@ -169,20 +162,13 @@ export function renderTerminalGap(statusLine) {
169
162
  * Pure.
170
163
  */
171
164
  export const TAGLINE = "the coding agent that survives kill -9";
172
- /** v6's existing logo W1's COMPACT tier, byte-identical, no redraw. */
173
- const LOGO_ROWS = ["█ ▀█▀ █▀▀ █▀█", "█▀▄ █ ▀▀█ █", "▀ ▀ ▀▀▀ ▀▀▀ ▀▀▀"];
174
- /** W1's BIG tier (36x6, `█` and space only no half-blocks, so there is
175
- * no tile seam to lose in a font that renders ▀ ▄ at the wrong height).
176
- * Each pixel is two cells wide on purpose (a terminal cell is ~1:2);
177
- * the render indents two 38 columns total, clears 40. */
178
- const BIG_LOGO_ROWS = [
179
- "██ ██ ██████ ████████ ████████",
180
- "██ ██ ██ ██ ██ ██",
181
- "████ ██ ████████ ██ ██",
182
- "████ ██ ██ ██ ██",
183
- "██ ██ ██ ██ ██ ██",
184
- "██ ██ ██████ ████████ ████████",
185
- ];
165
+ /** TT-1B (VD-14)the ONE wordmark, 2 rows. The 36x6 pixel art and the
166
+ * 3-row compact logo both retire: a tall pixel banner's mid-scroll cut
167
+ * state renders as glyph garbage (VD-14frames 03/04/s2-05 of the
168
+ * 2026-08-17 walkthrough), inherent to the height. This is the compact
169
+ * font with its base row folded into lower half-blocks same alphabet,
170
+ * 15 columns, and the cut window shrinks to nothing a reader catches. */
171
+ const WORDMARK_ROWS = ["█ █ ▀█▀ █▀▀ █▀█", "█▀▄ ▄█▄ ▄▄█ █▄█"];
186
172
  /** v3 §01 (W1): truncate a row at `width`, marking the hidden span
187
173
  * " (+N)". W1: the width math is the charWidth authority (the banner's
188
174
  * brick glyphs are 1 cell — the art's 38 columns clear 40), and the
@@ -216,9 +202,8 @@ export function truncateRow(row, width) {
216
202
  }
217
203
  /** v3 §01 (V6-2) + W1: the banner lines for a width W and height H —
218
204
  * the tier table (extends the existing "under 40 columns, skip the
219
- * logo" rule with a HEIGHT input):
220
- * W ≥ 40 and H ≥ 20BIG (the 36x6 wordmark, 2-column indent)
221
- * W ≥ 40 and 14–19 rows → COMPACT (v6's LOGO_ROWS, byte-identical)
205
+ * logo" rule with a HEIGHT input; VD-14 merged the two art tiers):
206
+ * W ≥ 40 and H ≥ 14 → the 2-row wordmark, 2-column indent
222
207
  * anything smaller → text rows only
223
208
  * then the blank, then "vX — tagline" — the art IS the wordmark, so the
224
209
  * text row does not repeat the name — then extensions — then the W5
@@ -226,15 +211,9 @@ export function truncateRow(row, width) {
226
211
  * with a " (+N)" marker. Pure. */
227
212
  export function bannerLines(W, H, version, extensionsText, resume = [], now = Date.now()) {
228
213
  const rows = [];
229
- if (W >= 40) {
230
- if (H >= 20) {
231
- for (const r of BIG_LOGO_ROWS)
232
- rows.push(truncateRow(` ${r}`, W));
233
- }
234
- else if (H >= 14) {
235
- for (const r of LOGO_ROWS)
236
- rows.push(truncateRow(r, W));
237
- }
214
+ if (W >= 40 && H >= 14) {
215
+ for (const r of WORDMARK_ROWS)
216
+ rows.push(truncateRow(` ${r}`, W));
238
217
  }
239
218
  if (rows.length > 0)
240
219
  rows.push("");
@@ -284,12 +263,17 @@ export function renderResumeList(metas, W, now) {
284
263
  const whens = metas.map((m) => relativeTime(m.updatedAt, now));
285
264
  const metaTexts = metas.map((m) => `${m.events} events · ${m.runs} runs`);
286
265
  const metaW = Math.max(...metaTexts.map((t) => t.length));
287
- const titleW = Math.max(1, W - 13 - metaW);
266
+ // TT-1B (W5): the glyph column exists only when a badge is present —
267
+ // a badge-less list keeps its exact pre-TT-1B bytes; in a mixed list
268
+ // every row reserves the column so the when/title columns never shift.
269
+ const badged = metas.some((m) => m.badge !== undefined);
270
+ const titleW = Math.max(1, W - (badged ? 15 : 13) - metaW);
288
271
  for (let i = 0; i < metas.length; i += 1) {
289
272
  const title = escapeTerminal(metas[i].title);
290
273
  const shown = titleCut(title, titleW);
291
274
  const pad = titleW - displayWidth(shown);
292
- rows.push(` ${whens[i].padEnd(7)} ${shown}${" ".repeat(pad)} ${metaTexts[i].padStart(metaW)}`);
275
+ const glyph = badged ? `${metas[i].badge ?? " "} ` : "";
276
+ rows.push(` ${glyph}${whens[i].padEnd(7)} ${shown}${" ".repeat(pad)} ${metaTexts[i].padStart(metaW)}`);
293
277
  }
294
278
  return rows;
295
279
  }
package/dist/strings.js CHANGED
@@ -338,6 +338,8 @@ export function helpRows() {
338
338
  cmd("/mode", "show the approval tier; /mode <name> switches (manual/default/accept-edits/plan/bypass)"),
339
339
  cmd("/model", "list model profiles; /model <name|provider/model> switches"),
340
340
  cmd("/compact", "summarize the older conversation to free context"),
341
+ cmd("/clear", "start a fresh conversation (the old session stays resumable)"),
342
+ cmd("/resume", "switch to another session; /resume <id> goes directly"),
341
343
  // TUI2-R1 (D): DELIBERATELY UNCHANGED. Deriving this sentence from
342
344
  // KEY_BINDINGS would be an improvement and it would also move an
343
345
  // assertion outside the round's two declared supersession classes,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-tui-cells",
3
- "version": "0.14.0",
3
+ "version": "0.15.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",