@gonrocca/zero-pi 0.1.63 → 0.1.64

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.
@@ -346,6 +346,49 @@ export function navigate(state: PickerState, dir: -1 | 1): PickerState {
346
346
  return state;
347
347
  }
348
348
 
349
+ // ---------------------------------------------------------------------------
350
+ // decodeKey — keystroke decoding
351
+ // ---------------------------------------------------------------------------
352
+
353
+ /** The picker's five meaningful keys, as decoded from a raw stdin sequence. */
354
+ export type PickerKey = "up" | "down" | "enter" | "esc" | "backspace";
355
+
356
+ /**
357
+ * Kitty-keyboard-protocol matcher for one functional key: `CSI <code>` +
358
+ * optional `;1` (no modifiers) + optional `:1` (press) / `:2` (repeat) +
359
+ * `<final>`. A `:3` (release) or any real modifier does NOT match — releases
360
+ * must be ignored and modified keys are not picker keys.
361
+ */
362
+ function kittyPressOrRepeat(code: string, final: string): RegExp {
363
+ return new RegExp(`^\\x1b\\[${code}(?:;1(?::[12])?)?${final}$`);
364
+ }
365
+
366
+ const KITTY_UP = kittyPressOrRepeat("1", "A");
367
+ const KITTY_DOWN = kittyPressOrRepeat("1", "B");
368
+ const KITTY_ENTER = kittyPressOrRepeat("13", "u");
369
+ const KITTY_ESC = kittyPressOrRepeat("27", "u");
370
+ const KITTY_BACKSPACE = kittyPressOrRepeat("127", "u");
371
+
372
+ /**
373
+ * Decode one raw stdin sequence into a picker key, or `null` when it is not
374
+ * one (printable characters, releases, modified keys, unknown sequences).
375
+ *
376
+ * pi-tui negotiates kitty keyboard protocol flags 7 with the terminal, and a
377
+ * granting terminal (Ghostty, kitty, …) then encodes arrows as
378
+ * `CSI 1;1:1 A/B` and Esc as `CSI 27 u` — never the legacy `\x1b[A` / bare
379
+ * `\x1b` forms. A non-granting terminal keeps the legacy (or SS3
380
+ * application-cursor-mode) forms. Both worlds are accepted here; kitty
381
+ * repeats (`:2`) navigate too so holding an arrow scrolls the list.
382
+ */
383
+ export function decodeKey(data: string): PickerKey | null {
384
+ if (data === "\x1b[A" || data === "\x1bOA" || KITTY_UP.test(data)) return "up";
385
+ if (data === "\x1b[B" || data === "\x1bOB" || KITTY_DOWN.test(data)) return "down";
386
+ if (data === "\r" || data === "\n" || data === "\r\n" || KITTY_ENTER.test(data)) return "enter";
387
+ if (data === "\x1b" || KITTY_ESC.test(data)) return "esc";
388
+ if (data === "\x7f" || data === "\x08" || KITTY_BACKSPACE.test(data)) return "backspace";
389
+ return null;
390
+ }
391
+
349
392
  // ---------------------------------------------------------------------------
350
393
  // enter / back — Enter/Esc dispatch
351
394
  // ---------------------------------------------------------------------------
@@ -44,6 +44,7 @@ import type { AutotunePending } from "./autotune-extension.ts";
44
44
  import {
45
45
  back,
46
46
  createPickerState,
47
+ decodeKey,
47
48
  enter,
48
49
  navigate,
49
50
  submitText,
@@ -521,14 +522,6 @@ const PICKER_TITLE = "zero · modelos SDD";
521
522
  /** The dim help line shown at the foot of the boxed panel. */
522
523
  const PICKER_HELP = "↑↓ navegar · enter elegir · esc volver";
523
524
 
524
- /** ANSI arrow-key escape sequences. */
525
- const KEY_UP = "\x1b[A";
526
- const KEY_DOWN = "\x1b[B";
527
- const KEY_ESC = "\x1b";
528
- /** Enter — CR or LF, depending on the terminal. */
529
- const KEY_ENTER = new Set(["\r", "\n", "\r\n"]);
530
- /** Backspace — DEL or BS. */
531
- const KEY_BACKSPACE = new Set(["\x7f", "\x08"]);
532
525
 
533
526
  /**
534
527
  * Clamp a rendered line to `width` columns so it never overflows the box
@@ -678,7 +671,8 @@ function createPickerComponent(
678
671
 
679
672
  /** Route a keystroke while the inline text buffer is open. */
680
673
  function handleTextInput(data: string): void {
681
- if (data === KEY_ESC) {
674
+ const key = decodeKey(data);
675
+ if (key === "esc") {
682
676
  // Esc abandons the typed value and returns to the current list screen
683
677
  // unchanged. `submitText` with an empty string is exactly that no-op:
684
678
  // it clears `textPrompt` and rebuilds the list without committing.
@@ -687,13 +681,13 @@ function createPickerComponent(
687
681
  tui.requestRender();
688
682
  return;
689
683
  }
690
- if (KEY_ENTER.has(data)) {
684
+ if (key === "enter") {
691
685
  state = submitText(state, buffer ?? "");
692
686
  buffer = null;
693
687
  tui.requestRender();
694
688
  return;
695
689
  }
696
- if (KEY_BACKSPACE.has(data)) {
690
+ if (key === "backspace") {
697
691
  buffer = (buffer ?? "").slice(0, -1);
698
692
  tui.requestRender();
699
693
  return;
@@ -714,24 +708,25 @@ function createPickerComponent(
714
708
  return;
715
709
  }
716
710
 
717
- if (data === KEY_UP) {
711
+ const key = decodeKey(data);
712
+ if (key === "up") {
718
713
  state = navigate(state, -1);
719
714
  tui.requestRender();
720
715
  return;
721
716
  }
722
- if (data === KEY_DOWN) {
717
+ if (key === "down") {
723
718
  state = navigate(state, 1);
724
719
  tui.requestRender();
725
720
  return;
726
721
  }
727
- if (KEY_ENTER.has(data)) {
722
+ if (key === "enter") {
728
723
  const result = enter(state);
729
724
  // `enter` on a custom-* row opens `textPrompt`; arm the buffer.
730
725
  if (result.type === "state" && result.state.textPrompt) buffer = "";
731
726
  applyResult(result);
732
727
  return;
733
728
  }
734
- if (data === KEY_ESC) {
729
+ if (key === "esc") {
735
730
  applyResult(back(state));
736
731
  return;
737
732
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gonrocca/zero-pi",
3
- "version": "0.1.63",
3
+ "version": "0.1.64",
4
4
  "description": "zero-pi — an installable layer for pi (pi.dev): the zero spec-driven development workflow (clarify → explore → plan → analyze → build → veredicto) with automatic clarify/analyze gates, per-phase model autotune, and token-efficient batched builds. Adds capability to pi without modifying pi.",
5
5
  "type": "module",
6
6
  "keywords": [