@step-forge/step-forge 0.0.25-alpha.1 → 0.0.25-alpha.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.
package/RUNTIME.md CHANGED
@@ -166,8 +166,11 @@ population):
166
166
  every subsequent file change. Pressing Enter again forces a re-run.
167
167
  - **Editing the query** suspends auto-runs until you press Enter again — so you
168
168
  can retarget without a half-typed selection firing.
169
- - **↑ / ↓** move the selection highlight; **PgUp / PgDn** scroll the failures
170
- pane when a run has more failures than fit on screen (the top stays pinned).
169
+ - **↑ / ↓** move the selection highlight. The **mouse wheel** (or **PgUp /
170
+ PgDn**) scrolls the failures pane when a run has more failures than fit on
171
+ screen — the prompt, selection, and stats stay pinned. (While the dashboard is
172
+ up the wheel drives the results pane, so hold **Shift** for native terminal
173
+ text selection.)
171
174
  - **Esc** disarms and returns to browsing.
172
175
  - **Ctrl-C** quits and restores the terminal.
173
176
 
package/dist/cli.cjs CHANGED
@@ -12,6 +12,7 @@ let node_util = require("node:util");
12
12
  let node_child_process = require("node:child_process");
13
13
  let node_readline = require("node:readline");
14
14
  node_readline = require_gherkinParser.__toESM(node_readline, 1);
15
+ let node_stream = require("node:stream");
15
16
  //#region src/runtime/config.ts
16
17
  const DEFAULT_FEATURES = "**/*.feature";
17
18
  const DEFAULT_STEPS = "**/*.steps.ts";
@@ -528,9 +529,14 @@ var Prompt = class {
528
529
  results = freshResults();
529
530
  failScroll = 0;
530
531
  started = false;
532
+ /** Filtered key stream: raw stdin minus the mouse sequences we handle. */
533
+ input;
531
534
  keyListener;
535
+ dataListener;
532
536
  resizeListener;
533
537
  signalCleanup;
538
+ /** Carry an incomplete mouse sequence split across stdin chunks. */
539
+ mousePending = "";
534
540
  constructor(options) {
535
541
  this.handlers = options.handlers;
536
542
  this.prefix = options.prefix ?? "› ";
@@ -585,11 +591,14 @@ var Prompt = class {
585
591
  start() {
586
592
  if (this.started) return;
587
593
  this.started = true;
588
- node_readline.emitKeypressEvents(process.stdin);
589
594
  if (process.stdin.isTTY) process.stdin.setRawMode(true);
590
- write("\x1B[?1049h");
595
+ write("\x1B[?1049h\x1B[?1000h\x1B[?1006h");
596
+ this.input = new node_stream.PassThrough();
597
+ node_readline.emitKeypressEvents(this.input);
591
598
  this.keyListener = (str, key) => this.onKey(str, key ?? {});
592
- process.stdin.on("keypress", this.keyListener);
599
+ this.input.on("keypress", this.keyListener);
600
+ this.dataListener = (chunk) => this.onStdinData(chunk);
601
+ process.stdin.on("data", this.dataListener);
593
602
  process.stdin.resume();
594
603
  this.resizeListener = () => this.render();
595
604
  process.stdout.on("resize", this.resizeListener);
@@ -600,11 +609,14 @@ var Prompt = class {
600
609
  stop() {
601
610
  if (!this.started) return;
602
611
  this.started = false;
603
- if (this.keyListener) process.stdin.off("keypress", this.keyListener);
612
+ if (this.dataListener) process.stdin.off("data", this.dataListener);
613
+ if (this.keyListener) this.input?.off("keypress", this.keyListener);
604
614
  if (this.resizeListener) process.stdout.off("resize", this.resizeListener);
605
615
  this.signalCleanup?.();
606
616
  this.restoreTerminal();
607
617
  process.stdin.pause();
618
+ this.input = void 0;
619
+ this.mousePending = "";
608
620
  }
609
621
  /** Force a repaint (after mutating `status`, say). No-op until started. */
610
622
  redraw() {
@@ -617,6 +629,7 @@ var Prompt = class {
617
629
  */
618
630
  restoreTerminal() {
619
631
  if (process.stdin.isTTY) process.stdin.setRawMode(false);
632
+ write("\x1B[?1006l\x1B[?1000l");
620
633
  write("\x1B[?25h");
621
634
  write("\x1B[?1049l");
622
635
  }
@@ -720,12 +733,57 @@ var Prompt = class {
720
733
  this.reWindow();
721
734
  this.render();
722
735
  }
723
- /** Scroll the failures pane by a page (clamped in {@link render}). */
736
+ /** Scroll the failures pane by whole pages (PgUp/PgDn). */
724
737
  scrollFailures(pages) {
725
- this.failScroll = Math.max(0, this.failScroll + pages * this.failPage);
726
- this.render();
738
+ this.adjustFailScroll(pages * this.failPage);
727
739
  }
728
740
  failPage = 1;
741
+ /** Move the failures viewport by `deltaLines` (clamped in {@link render}). */
742
+ adjustFailScroll(deltaLines) {
743
+ this.failScroll = Math.max(0, this.failScroll + deltaLines);
744
+ this.render();
745
+ }
746
+ /**
747
+ * Split a raw stdin chunk: decode SGR/legacy mouse sequences here (only the
748
+ * wheel is acted on — it scrolls the failures pane by a few lines) and forward
749
+ * everything else to readline. An incomplete trailing mouse sequence is held
750
+ * in {@link mousePending} for the next chunk. `latin1` keeps every byte 1:1 so
751
+ * the forwarded bytes reassemble exactly (including multibyte input).
752
+ */
753
+ onStdinData(chunk) {
754
+ const buf = this.mousePending + chunk.toString("latin1");
755
+ let out = "";
756
+ let i = 0;
757
+ while (i < buf.length) {
758
+ if (buf.startsWith("\x1B[<", i)) {
759
+ const rel = /[Mm]/.exec(buf.slice(i + 3));
760
+ if (!rel) break;
761
+ const end = i + 3 + rel.index + 1;
762
+ this.handleMouse(buf.slice(i, end));
763
+ i = end;
764
+ continue;
765
+ }
766
+ if (buf.startsWith("\x1B[M", i)) {
767
+ if (i + 6 > buf.length) break;
768
+ this.handleMouse(buf.slice(i, i + 6));
769
+ i += 6;
770
+ continue;
771
+ }
772
+ out += buf[i];
773
+ i++;
774
+ }
775
+ this.mousePending = buf.slice(i);
776
+ if (out) this.input?.write(Buffer.from(out, "latin1"));
777
+ }
778
+ /** Act on a decoded mouse sequence — wheel up/down scrolls failures; else ignore. */
779
+ handleMouse(seq) {
780
+ let button = null;
781
+ const sgr = /^\x1b\[<(\d+);\d+;\d+[Mm]$/.exec(seq);
782
+ if (sgr) button = parseInt(sgr[1], 10);
783
+ else if (seq.length === 6) button = seq.charCodeAt(3) - 32;
784
+ if (button === null || (button & 64) === 0) return;
785
+ this.adjustFailScroll((button & 1) === 0 ? -3 : 3);
786
+ }
729
787
  refilter() {
730
788
  const q = this.query.trim().toLowerCase();
731
789
  this.matches = this.all.map((s, index) => ({