@vincemakes/kiso-tui 0.20.0 → 0.20.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.
@@ -12,6 +12,7 @@
12
12
  * lexical order of the path (never localeCompare, whose result depends
13
13
  * on the machine's locale).
14
14
  */
15
+ import { bandHeader } from "@vincemakes/kiso-tui-cells/strings";
15
16
  /** The bound source's item — a repo-relative path and nothing else.
16
17
  * Structural: the CLI passes whatever it likes as long as it has a
17
18
  * path (slice 5 passes exactly this). */
@@ -168,4 +169,4 @@ export declare function atPanelRows(state: {
168
169
  * composer and every panel now share, so a band opens the way everything
169
170
  * else does and the label tells you WHICH band in the same row.
170
171
  */
171
- export declare function bandHeader(label: string, W: number): string;
172
+ export { bandHeader };
package/dist/at-picker.js CHANGED
@@ -14,6 +14,7 @@
14
14
  */
15
15
  import { escapeTerminal, palette } from "./render.js";
16
16
  import { selectionBar, visibleWidth, widthCut } from "./components.js";
17
+ import { bandHeader } from "@vincemakes/kiso-tui-cells/strings";
17
18
  /**
18
19
  * KC3 §5 — the ONE cap. The file list is computed per open with no
19
20
  * index and no watcher, so its cost is bounded here rather than
@@ -263,8 +264,8 @@ export function atPanelRows(state, W) {
263
264
  * composer and every panel now share, so a band opens the way everything
264
265
  * else does and the label tells you WHICH band in the same row.
265
266
  */
266
- export function bandHeader(label, W) {
267
- const p = palette();
268
- const head = `\u2500\u2500\u2500 ${label} `;
269
- return `${p.dim}${widthCut(`${head}${"\u2500".repeat(Math.max(1, W - head.length))}`, Math.max(1, W))}${p.reset}`;
270
- }
267
+ // R8b: bandHeader MOVED to tui-cells/strings.ts — the keys sheet needs
268
+ // it and lives there, and `components.ts` already imports that module,
269
+ // so the dependency only runs one way. Re-exported here so every
270
+ // existing import site is untouched.
271
+ export { bandHeader };
@@ -43,7 +43,7 @@
43
43
  * line-mode bytes byte-for-byte (the e2e guards them).
44
44
  */
45
45
  import { truncateDiff } from "./diff.js";
46
- import { displayWidth } from "./editor.js";
46
+ import { MENU_ITEMS, displayWidth } from "./editor.js";
47
47
  import { leadWidth } from "./width.js"; // W23: the ONE width authority (the editor, #inputRow, and editCol share it)
48
48
  // KC3.5: the panel-slot reads come from the DISPATCHERS — one source
49
49
  // for four reads, so an ask can never render half as an approval.
@@ -52,7 +52,7 @@ import { MOUSE_OFF } from "./editor.js";
52
52
  import { atPanelRows, bandHeader } from "./at-picker.js";
53
53
  // TUI2-R2 ②: the session picker's rows — the band's third occupant.
54
54
  import { sessionPickerRows } from "./session-picker.js";
55
- import { ACT_SLOT_ROWS, Container, ROLLUP_NOUN, MOTION_FRAMES, MdStream, bodySpacing, boxBottom, boxTop, cellComponent, exploreCounts, foldCountsObjects, foldTerms, focusToken, exploreRows, foldLine, cutLine, isExploreTool, moreRunningRow, pendingQueueRows, slotPad, slotTail, statusLine, stretchLine, turnFold, visibleWidth, breathFrame, } from "./components.js";
55
+ import { ACT_SLOT_ROWS, Container, ROLLUP_NOUN, MOTION_FRAMES, MdStream, bodySpacing, boxBottom, boxTop, cellComponent, exploreCounts, foldCountsObjects, foldTerms, focusToken, exploreRows, foldLine, gutterCut, cutLine, isExploreTool, moreRunningRow, pendingQueueRows, slotPad, slotTail, statusLine, stretchLine, turnFold, visibleWidth, breathFrame, } from "./components.js";
56
56
  import { bannerLines, escapeTerminal, foldResult, foldThinking, palette, renderTerminalGap, renderToolSummary, toolTarget } from "./render.js";
57
57
  import { displayVerb, keysSheetRows } from "./strings.js";
58
58
  // R5 — the transcript viewer's PURE projection. The compositor supplies
@@ -77,6 +77,9 @@ const CHROME_ROWS = 4; // box top + input + box bottom + status — the design
77
77
  * same unbounded height the projection exists to remove; the rest are
78
78
  * COUNTED, never dropped silently. */
79
79
  const LIVE_ACT_HEADS = 3;
80
+ /** R8 — the command band's window: five rows plus a counter, the same
81
+ * budget the composer's own ceiling can afford above it. */
82
+ const MENU_WINDOW = 5;
80
83
  /** W13 / TUI2-R1 (B) — a rolled run's TITLE: the exploration sentence on
81
84
  * a mixed run, W13's verb+count on a single-name one. */
82
85
  function rolledTitle(cell) {
@@ -1426,7 +1429,9 @@ export class Body {
1426
1429
  // always the part a reader could use.
1427
1430
  `${p.bold}✦${p.reset} expanded · ${escapeTerminal(head.length === 0 ? "thinking" : head.join(" · "))} · ${back}`,
1428
1431
  ...body,
1429
- ` ${p.dim}└ end of expansion · ctrl+r opens the one before it${p.reset}`,
1432
+ // R8a: an in-block note takes the block's indent, not a
1433
+ // second corner — the corner opens the body above it.
1434
+ `${p.dim} end of expansion · ctrl+r opens the one before it${p.reset}`,
1430
1435
  ],
1431
1436
  };
1432
1437
  }
@@ -3261,14 +3266,40 @@ export class Body {
3261
3266
  // picker's does. Both render frameless directly above the composer,
3262
3267
  // so with scrollback behind them there was nothing to say where the
3263
3268
  // surface began — the rows read as more history.
3269
+ // R8 — THE BAND IS A WINDOW, and the rows are a table.
3270
+ //
3271
+ // It used to draw every match and fold each long description over
3272
+ // as many rows as it took, which is why a bare `/` could not open
3273
+ // it: eleven commands plus wraps is most of a short terminal. A
3274
+ // fixed window is what lets the trigger be the `/` the banner
3275
+ // advertises (see the editor's #menuFiltered).
3276
+ //
3277
+ // Three shape rules, all of them §1.3 or §1.2:
3278
+ // - the leading `/` comes off the rows. It is already on the
3279
+ // input line directly below, so printing it eleven more times
3280
+ // is a mark carrying no fact the screen does not have.
3281
+ // - the name column is padded to the longest command in the
3282
+ // WHOLE list, not the visible slice, so the descriptions do
3283
+ // not shift sideways as the window scrolls.
3284
+ // - a description is CUT, never folded — a folded row would
3285
+ // break the window's height, which is the thing being bought.
3286
+ const items = menu.items;
3287
+ const col = MENU_ITEMS.reduce((n, m) => Math.max(n, m.name.length - 1), 0);
3288
+ const windowed = items.length > MENU_WINDOW;
3289
+ // the window's top is derived from the selection alone (this
3290
+ // method is re-entered per frame and keeps no state): centre it,
3291
+ // clamped to the ends.
3292
+ const top = windowed ? Math.max(0, Math.min(menu.selected - ((MENU_WINDOW - 1) >> 1), items.length - MENU_WINDOW)) : 0;
3264
3293
  const rows = [bandHeader("commands", W)];
3265
- for (let i = 0; i < menu.items.length; i += 1) {
3266
- const item = menu.items[i];
3267
- const text = i === menu.selected
3268
- ? `${p.bold} ${item.name}${p.reset} ${item.desc}`
3269
- : `${p.dim} ${item.name} ${item.desc}${p.reset}`;
3270
- rows.push(...foldLine(text, W));
3271
- }
3294
+ for (let i = top; i < Math.min(items.length, top + MENU_WINDOW); i += 1) {
3295
+ const item = items[i];
3296
+ const label = `${item.name.slice(1).padEnd(col)} ${item.desc}`;
3297
+ rows.push(...(i === menu.selected ? gutterCut(`${p.bold}▸${p.reset} `, `${p.bold}${label}${p.reset}`, W) : gutterCut(" ", `${p.dim}${label}${p.reset}`, W)));
3298
+ }
3299
+ // the counter earns its row only when the list is CUT — over a
3300
+ // list you can see all of, it says nothing the rows do not.
3301
+ if (windowed)
3302
+ rows.push(` ${p.dim}(${menu.selected + 1}/${items.length})${p.reset}`);
3272
3303
  return rows;
3273
3304
  }
3274
3305
  /** ONE input row's bytes — the marker embedded at `embedAt` (the
package/dist/editor.js CHANGED
@@ -542,11 +542,20 @@ export class Editor {
542
542
  return null;
543
543
  return { items: this.#menuFiltered(), selected: this.#menuSel };
544
544
  }
545
- /** v3 §04: the filtered command list for the current buffer — open
546
- * only while the line is "/" + something (a bare "/" waits). */
545
+ /** v3 §04: the filtered command list for the current buffer.
546
+ *
547
+ * A BARE `/` OPENS IT (owner-ruled 2026-09-01). It used to wait for
548
+ * a second character, which made the key the banner advertises —
549
+ * `/ commands` — a thing you had to already know the answer to: the
550
+ * list that tells you the commands appeared only once you had typed
551
+ * one. The reason for the wait was real and is fixed on the other
552
+ * side: the band drew EVERY match with no window, so a bare `/`
553
+ * would have piled eleven rows plus wraps above the composer. The
554
+ * band windows now (see the compositor's #menuRows), so the trigger
555
+ * no longer has to do the rationing. */
547
556
  #menuFiltered() {
548
557
  const line = this.line();
549
- if (!line.startsWith("/") || line === "/")
558
+ if (!line.startsWith("/"))
550
559
  return [];
551
560
  return MENU_ITEMS.filter((m) => m.name.startsWith(line));
552
561
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-tui",
3
- "version": "0.20.0",
3
+ "version": "0.20.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.20.0"
38
+ "@vincemakes/kiso-tui-cells": "0.20.1"
39
39
  }
40
40
  }