@vincemakes/kiso-tui 0.31.0 → 0.31.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.
package/dist/ask-panel.js CHANGED
@@ -460,7 +460,7 @@ export function panelStatus(view, phase, cursor, ask, pick) {
460
460
  }
461
461
  export function panelAffordance(view, phase, cursor, ask, pick, safer) {
462
462
  if (view.pick !== undefined && pick !== undefined)
463
- return pickAffordance(pick);
463
+ return pickAffordance(pick, view.pick.options[pick.cursor]?.levels !== undefined);
464
464
  if (view.ask !== undefined && ask !== undefined)
465
465
  return askAffordance(ask, view.ask.questions[ask.qIndex]);
466
466
  return basePanelAffordance(view, phase, cursor, safer);
@@ -58,6 +58,10 @@ import { sessionPickerRows } from "./session-picker.js";
58
58
  import { CAP_PREVIEW, thinkingRow, Container, MOTION_FRAMES, MdStream, bodySpacing, boxBottom, boxTop, cellComponent, gutterCut, cutLine, pendingQueueRows, statusLine, visibleWidth, breathFrame, } from "./components.js";
59
59
  import { bannerLines, escapeTerminal, foldResult, foldThinking, palette, renderTerminalGap, renderToolSummary } from "./lines.js";
60
60
  import { displayVerb, keysSheetRows } from "./strings.js";
61
+ /** DC-56: does a rendered row carry any visible text once its SGR is
62
+ * stripped? A washed pad row is spaces under a background colour — width
63
+ * without words. */
64
+ const saysSomething = (row) => row.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, "").trim() !== "";
61
65
  // R5 — the transcript viewer's PURE projection. The compositor supplies
62
66
  // the entries (it holds the cells); the arrangement lives there.
63
67
  import { VIEWER_GUTTER, viewerFlat, viewerHint, viewerInit, viewerMove, viewerRows, viewerScroll, viewerTitle, viewerToggle, viewerToggleAll, } from "./transcript.js";
@@ -936,7 +940,15 @@ export class Body {
936
940
  cell.expanded = true;
937
941
  const rows = cellComponent(cell).render(inner, ctx);
938
942
  cell.expanded = saved;
939
- out.push({ head: rows[0] ?? "", body: rows.slice(1) });
943
+ // DC-56 (owner, 2026-09-09): the head is the first row that SAYS
944
+ // something. R13's card wears a washed PAD row above its head on
945
+ // the palettes with a real background (light, dark) and none under
946
+ // the neutral reverse-video wash — so `rows[0]` was the head on
947
+ // one palette and a blank washed row on the others, and ctrl+r on
948
+ // a light terminal showed "5 folds" over five empty grey rows.
949
+ const first = rows.findIndex((r) => saysSomething(r));
950
+ const headAt = first < 0 ? 0 : first;
951
+ out.push({ head: rows[headAt] ?? "", body: rows.slice(headAt + 1) });
940
952
  }
941
953
  return out;
942
954
  }
package/dist/editor.js CHANGED
@@ -1462,7 +1462,10 @@ export class Editor {
1462
1462
  this.#move(-1);
1463
1463
  }
1464
1464
  else if (final === "C") {
1465
- this.#move(1);
1465
+ // OR-7: → walks the pick panel's level axis when one is up; the
1466
+ // composer keeps the key everywhere else, exactly as ← does.
1467
+ if (!this.#panelInput.right())
1468
+ this.#move(1);
1466
1469
  }
1467
1470
  else if (final === "H") {
1468
1471
  this.#cursor = this.#cursorBounds().start; // A3: Home follows Ctrl+A — line-local
@@ -97,8 +97,14 @@ export declare class PanelInput {
97
97
  * panel owned the key (the caller renders); false when no panel is
98
98
  * up. A phase without a list (asking) swallows the key. */
99
99
  arrow(dir: "up" | "down"): boolean;
100
- /** ← while an ask's option row is up walks the ask; otherwise the key is the composer's. */
100
+ /** ← while an ask's option row is up walks the ask; while a pick's is
101
+ * up it walks the highlighted option's level axis (OR-7); otherwise
102
+ * the key is the composer's. */
101
103
  left(): boolean;
104
+ /** → is the level axis's other direction. The ask never owned it (its
105
+ * own walk is ← back and enter forward), so this key was the
106
+ * composer's alone until OR-7 and still is when no axis is up. */
107
+ right(): boolean;
102
108
  /** TUI2-R3v2 ②: a left-button press on the option rows the
103
109
  * compositor placed. Outside the list — inert. */
104
110
  click(span: PanelRowSpan | null | undefined, row: number | undefined): void;
@@ -16,7 +16,7 @@
16
16
  * and the mouse hit. The bodies are the same; only the buffer access
17
17
  * goes through the host.
18
18
  */
19
- import { PICK_MAX, panelOptions, saferDegradedNote, } from "./approval-panel.js";
19
+ import { PICK_MAX, panelOptions, saferDegradedNote, startLevel, stepLevel, } from "./approval-panel.js";
20
20
  import { askCommitCustom, askKey, askOnCustomRow, askStart } from "./ask-panel.js";
21
21
  export class PanelInput {
22
22
  host;
@@ -49,7 +49,7 @@ export class PanelInput {
49
49
  safer: opts?.safer,
50
50
  saferRun: null,
51
51
  ask: view.ask === undefined ? null : askStart(view.ask),
52
- pick: view.pick === undefined ? null : { cursor: 0, phase: "options" },
52
+ pick: view.pick === undefined ? null : { cursor: 0, phase: "options", level: startLevel(view.pick.options[0]) },
53
53
  onCommit,
54
54
  stash: this.host.stash(),
55
55
  };
@@ -113,8 +113,10 @@ export class PanelInput {
113
113
  this.#pickPanelDigit(Number(c) - 1);
114
114
  return 1;
115
115
  }
116
+ if (!typing && (c === "h" || c === "l") && this.#levelStep(c === "h" ? -1 : 1))
117
+ return 1;
116
118
  if (!typing && (c === "t" || c === "T") && panel.view.pick?.typeHint !== undefined) {
117
- panel.pick = { cursor: panel.pick.cursor, phase: "custom" };
119
+ panel.pick = { cursor: panel.pick.cursor, phase: "custom", level: panel.pick.level };
118
120
  this.host.clear();
119
121
  this.host.render();
120
122
  return 1;
@@ -206,7 +208,12 @@ export class PanelInput {
206
208
  if (panel.pick !== null && panel.pick.phase === "options") {
207
209
  const n = Math.min(panel.view.pick.options.length, PICK_MAX);
208
210
  const cur = panel.pick.cursor;
209
- panel.pick = { cursor: dir === "up" ? Math.max(0, cur - 1) : Math.min(Math.max(0, n - 1), cur + 1), phase: "options" };
211
+ const next = dir === "up" ? Math.max(0, cur - 1) : Math.min(Math.max(0, n - 1), cur + 1);
212
+ // OR-7: the second axis belongs to the highlighted option, so it
213
+ // re-lands on THAT option's own level. Carrying the previous
214
+ // row's index across would point at a level this model may not
215
+ // have — the silent clamp the coordination note exists to end.
216
+ panel.pick = { cursor: next, phase: "options", level: startLevel(panel.view.pick.options[next]) };
210
217
  }
211
218
  else if (panel.ask !== null && panel.ask.phase === "options")
212
219
  this.#askStep(dir);
@@ -216,14 +223,37 @@ export class PanelInput {
216
223
  this.#panelMove(dir === "up" ? -1 : 1);
217
224
  return true;
218
225
  }
219
- /** ← while an ask's option row is up walks the ask; otherwise the key is the composer's. */
226
+ /** ← while an ask's option row is up walks the ask; while a pick's is
227
+ * up it walks the highlighted option's level axis (OR-7); otherwise
228
+ * the key is the composer's. */
220
229
  left() {
230
+ if (this.#levelStep(-1))
231
+ return true;
221
232
  const panel = this.#panel;
222
233
  if (panel?.ask == null || panel.ask.phase !== "options")
223
234
  return false;
224
235
  this.#askStep("left");
225
236
  return true;
226
237
  }
238
+ /** → is the level axis's other direction. The ask never owned it (its
239
+ * own walk is ← back and enter forward), so this key was the
240
+ * composer's alone until OR-7 and still is when no axis is up. */
241
+ right() {
242
+ return this.#levelStep(1);
243
+ }
244
+ /** One step along the highlighted option's levels. False when there is
245
+ * no axis to walk, which is what leaves the key to the composer. */
246
+ #levelStep(dir) {
247
+ const panel = this.#panel;
248
+ if (panel === null || panel.pick === null || panel.pick.phase !== "options")
249
+ return false;
250
+ const o = panel.view.pick?.options[panel.pick.cursor];
251
+ if (o?.levels === undefined)
252
+ return false;
253
+ panel.pick = { cursor: panel.pick.cursor, phase: "options", level: stepLevel(o, panel.pick.level, dir) };
254
+ this.host.render();
255
+ return true;
256
+ }
227
257
  /** TUI2-R3v2 ②: a left-button press on the option rows the
228
258
  * compositor placed. Outside the list — inert. */
229
259
  click(span, row) {
@@ -420,7 +450,7 @@ export class PanelInput {
420
450
  return;
421
451
  if (index < 0 || index >= Math.min(panel.view.pick.options.length, PICK_MAX))
422
452
  return;
423
- panel.pick = { cursor: index, phase: "options" };
453
+ panel.pick = { cursor: index, phase: "options", level: startLevel(panel.view.pick.options[index]) };
424
454
  this.host.render();
425
455
  }
426
456
  #pickPanelEnter() {
@@ -436,14 +466,15 @@ export class PanelInput {
436
466
  }
437
467
  if (panel.view.pick.options.length === 0)
438
468
  return; // nothing to take
439
- this.#close({ action: "picked", result: { index: panel.pick.cursor } });
469
+ const level = panel.pick.level;
470
+ this.#close({ action: "picked", result: { index: panel.pick.cursor }, ...(level === null ? {} : { level }) });
440
471
  }
441
472
  #pickPanelEsc() {
442
473
  const panel = this.#panel;
443
474
  if (panel === null || panel.pick === null)
444
475
  return;
445
476
  if (panel.pick.phase === "custom") {
446
- panel.pick = { cursor: panel.pick.cursor, phase: "options" };
477
+ panel.pick = { cursor: panel.pick.cursor, phase: "options", level: panel.pick.level };
447
478
  this.host.clear();
448
479
  this.host.render();
449
480
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-tui",
3
- "version": "0.31.0",
3
+ "version": "0.31.1",
4
4
  "description": "kiso tui — the pure terminal layer (cell renderer, dock, raw editor, diff, palette). Zero runtime dependencies: input is data, output is bytes.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -35,6 +35,6 @@
35
35
  },
36
36
  "homepage": "https://github.com/vincemakes/kiso/tree/main/packages/tui#readme",
37
37
  "dependencies": {
38
- "@vincemakes/kiso-tui-cells": "0.31.0"
38
+ "@vincemakes/kiso-tui-cells": "0.31.1"
39
39
  }
40
40
  }