@vincemakes/kiso-tui-cells 0.40.0 → 0.40.2

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.
@@ -458,10 +458,42 @@ export declare function panelAffordance(view: PanelView, phase: PanelPhase, curs
458
458
  * height is its row count (the W20 rule the #checked throw demands).
459
459
  */
460
460
  export declare function pickBlockRows(view: PanelView, state: PickRuntime, W: number, maxRows: number): string[];
461
- /** The digits are the keys, so the list the panel offers is bounded by
462
- * the digits there are. Beyond it, `/model <name>` still takes any
463
- * profile — the panel says so rather than paginating. */
461
+ /** The digits are the keys, so what one SCREEN offers is still bounded by
462
+ * the digits there are — but the list itself is no longer bounded by it.
463
+ *
464
+ * Finding DC-58 (the owner, 2026-09-21): this constant used to cap what the
465
+ * KEYBOARD could reach (`/model` on a 60-profile config offered nine and
466
+ * said `/model <name>` for the rest). The window now follows the cursor —
467
+ * `pickWindow` — so PICK_MAX is only how many rows one screen shows, and
468
+ * ↑↓ walks every option.
469
+ */
464
470
  export declare const PICK_MAX = 9;
471
+ /** The option window the pick panel draws and the digits name: `size` rows
472
+ * starting at `first`, with `first` derived from the cursor so it cannot
473
+ * drift from it. Edge-following (the window starts moving only when the
474
+ * cursor leaves it) and clamped at both ends, so the last page is full
475
+ * rather than short.
476
+ *
477
+ * Pure, and the ONE copy: the renderer, the digit keys and the click
478
+ * hit-test all ask this function rather than each deriving an offset.
479
+ */
480
+ /** The pick panel's window for THIS frame's budget — the one derivation the
481
+ * renderer and the input layer share (review of this round).
482
+ *
483
+ * The renderer draws it; `Dock.visiblePickWindow` hands the SAME value to the
484
+ * digit keys, because a size derived twice is a size that can disagree — and
485
+ * on a short terminal it did (two rows drawn, nine assumed, so `1` could mean
486
+ * a row nobody could see). `phase` is compared as a string so this module
487
+ * keeps its zero-dependency contract.
488
+ */
489
+ export declare function pickWindowOf(view: PanelView, cursor: number, phase: string, maxRows: number): {
490
+ first: number;
491
+ size: number;
492
+ };
493
+ export declare function pickWindow(cursor: number, count: number, size: number): {
494
+ first: number;
495
+ size: number;
496
+ };
465
497
  /** The input row's lead: the digit range while picking, the named
466
498
  * prompt while typing one out. */
467
499
  export declare function pickLeadPlain(view: PanelView, state: PickRuntime): string;
@@ -575,20 +575,37 @@ export function pickBlockRows(view, state, W, maxRows) {
575
575
  // which it already spends on `profile: <name>`. A second axis that
576
576
  // truncates on a normal terminal is not a second axis.
577
577
  const strip = spec.options[state.cursor]?.levels !== undefined && state.phase === "options" ? 1 : 0;
578
- const chrome = 5 + strip + (spec.options.length > Math.min(Math.max(1, maxRows - 5 - strip), PICK_MAX) ? 1 : 0);
579
- const budget = Math.max(1, maxRows - chrome);
580
- const shown = spec.options.slice(0, Math.min(budget, PICK_MAX));
578
+ // 2026-09-21 (finding DC-58, the owner's report): the window FOLLOWS the
579
+ // cursor instead of being pinned to the top. It used to be
580
+ // `slice(0, min(budget, PICK_MAX))`, so with a 60-profile config the
581
+ // keyboard reached the first nine and the rest were reachable only by
582
+ // typing the name.
583
+ //
584
+ // REVIEW of this round: the SIZE depends on the frame's budget, so the
585
+ // renderer and the digit keys used to disagree on a short terminal
586
+ // (drawn rows 1-2, keys computing against nine). `pickWindowOf` is the
587
+ // one derivation both sides use — the renderer draws it, the input layer
588
+ // is HANDED it (`visiblePickWindow`).
589
+ const win = pickWindowOf(view, state.cursor, state.phase, maxRows);
590
+ const shown = spec.options.slice(win.first, win.first + win.size);
581
591
  // R2: the note takes a COLUMN, not three spaces after a label of
582
592
  // whatever length this row happened to have, and the cursor row
583
593
  // wears the bar and the arrow like every other list in the
584
594
  // product. This panel was the last one still saying "selected"
585
595
  // with bold alone.
586
- const lead = (o, i, cursor) => `${cursor ? "\u2192" : " "} ${i + 1} ${escapeTerminal(o.label)}`;
596
+ // 0.40.1 (the owner's dogfood, 2026-09-21): the row NUMBER is gone. The
597
+ // digits still pick by visible position (the keys sheet documents them),
598
+ // but a number the person reads as decoration is noise the label pays
599
+ // for at every width — and the owner picks with ↑↓ and the mouse.
600
+ const lead = (o, _i, cursor) => `${cursor ? "\u2192" : " "} ${escapeTerminal(o.label)}`;
587
601
  const widest = Math.max(...shown.map((o, i) => visibleWidth(lead(o, i, false))));
588
602
  const stop = shown.some((o) => o.note !== undefined) && widest + 2 <= Math.floor(room / 2) && room - widest - 2 >= 18 ? widest + 2 : 0;
589
603
  for (let i = 0; i < shown.length; i += 1) {
590
604
  const o = shown[i];
591
- const mark = i === state.cursor && state.phase === "options";
605
+ // DC-58: `i` is the row's position in the WINDOW, the cursor is an
606
+ // option index — the mark lands on the cursor's own row after any
607
+ // scroll only when the window's origin is added back.
608
+ const mark = win.first + i === state.cursor && state.phase === "options";
592
609
  const plain = lead(o, i, mark);
593
610
  const head = `${mark ? p.bold : ""}${plain}${mark ? p.reset : ""}`;
594
611
  const note = o.note === undefined
@@ -605,9 +622,10 @@ export function pickBlockRows(view, state, W, maxRows) {
605
622
  rows.push(mark ? selectionBar(text, visibleWidth(text), W) : ` ${text}`);
606
623
  }
607
624
  if (spec.options.length > shown.length) {
608
- rows.push(` ${cutLine(`${p.dim} \u2514 +${spec.options.length - shown.length} more \u2014 /model <name> takes any of them${p.reset}`, room)}`);
625
+ const to = win.first + shown.length;
626
+ rows.push(` ${cutLine(`${p.dim} \u2195 ${win.first + 1}-${to} / ${spec.options.length} \u2014 \u2191\u2193 scrolls${p.reset}`, room)}`);
609
627
  }
610
- const axis = shown[state.cursor];
628
+ const axis = shown[state.cursor - win.first];
611
629
  if (strip === 1 && axis?.levels !== undefined) {
612
630
  const off = new Set(axis.disabled ?? []);
613
631
  // the RUNTIME cursor, not the option's starting index: the option
@@ -639,10 +657,48 @@ export function pickBlockRows(view, state, W, maxRows) {
639
657
  rows.push(`${p.dim}${"\u2500".repeat(Math.max(0, W))}${p.reset}`);
640
658
  return rows;
641
659
  }
642
- /** The digits are the keys, so the list the panel offers is bounded by
643
- * the digits there are. Beyond it, `/model <name>` still takes any
644
- * profile — the panel says so rather than paginating. */
660
+ /** The digits are the keys, so what one SCREEN offers is still bounded by
661
+ * the digits there are — but the list itself is no longer bounded by it.
662
+ *
663
+ * Finding DC-58 (the owner, 2026-09-21): this constant used to cap what the
664
+ * KEYBOARD could reach (`/model` on a 60-profile config offered nine and
665
+ * said `/model <name>` for the rest). The window now follows the cursor —
666
+ * `pickWindow` — so PICK_MAX is only how many rows one screen shows, and
667
+ * ↑↓ walks every option.
668
+ */
645
669
  export const PICK_MAX = 9;
670
+ /** The option window the pick panel draws and the digits name: `size` rows
671
+ * starting at `first`, with `first` derived from the cursor so it cannot
672
+ * drift from it. Edge-following (the window starts moving only when the
673
+ * cursor leaves it) and clamped at both ends, so the last page is full
674
+ * rather than short.
675
+ *
676
+ * Pure, and the ONE copy: the renderer, the digit keys and the click
677
+ * hit-test all ask this function rather than each deriving an offset.
678
+ */
679
+ /** The pick panel's window for THIS frame's budget — the one derivation the
680
+ * renderer and the input layer share (review of this round).
681
+ *
682
+ * The renderer draws it; `Dock.visiblePickWindow` hands the SAME value to the
683
+ * digit keys, because a size derived twice is a size that can disagree — and
684
+ * on a short terminal it did (two rows drawn, nine assumed, so `1` could mean
685
+ * a row nobody could see). `phase` is compared as a string so this module
686
+ * keeps its zero-dependency contract.
687
+ */
688
+ export function pickWindowOf(view, cursor, phase, maxRows) {
689
+ const count = view.pick?.options.length ?? 0;
690
+ const strip = view.pick?.options[cursor]?.levels !== undefined && phase === "options" ? 1 : 0;
691
+ const chrome = 5 + strip + (count > Math.min(Math.max(1, maxRows - 5 - strip), PICK_MAX) ? 1 : 0);
692
+ const budget = Math.max(1, maxRows - chrome);
693
+ return pickWindow(cursor, count, Math.min(budget, PICK_MAX));
694
+ }
695
+ export function pickWindow(cursor, count, size) {
696
+ const win = Math.max(1, Math.min(size, count));
697
+ if (count <= win)
698
+ return { first: 0, size: win };
699
+ const first = Math.max(0, Math.min(cursor - win + 1, count - win));
700
+ return { first, size: win };
701
+ }
646
702
  /** The input row's lead: the digit range while picking, the named
647
703
  * prompt while typing one out. */
648
704
  export function pickLeadPlain(view, state) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-tui-cells",
3
- "version": "0.40.0",
3
+ "version": "0.40.2",
4
4
  "description": "kiso tui-cells \u2014 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",