termbeam 1.24.10 → 1.24.11

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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.24.11] - 2026-07-08
4
+
5
+ - fix(cli): handle split and coalesced key input in choose() arrow navigation (@dorlugasigal)
6
+
3
7
  ## [1.24.10] - 2026-07-07
4
8
 
5
9
  - feat(ci): autonomous Dependabot auto-fix and auto-merge workflows (#263) (@dorlugasigal)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "termbeam",
3
- "version": "1.24.10",
3
+ "version": "1.24.11",
4
4
  "description": "Beam your terminal to any device — mobile-optimized web terminal with multi-session support",
5
5
  "main": "src/server/index.js",
6
6
  "bin": {
@@ -74,24 +74,60 @@ function choose(rl, question, choices, defaultIndex = 0) {
74
74
  }
75
75
  process.stdin.resume();
76
76
 
77
+ // Bytes arrive as a raw stream: a single keypress may be split across
78
+ // multiple 'data' events, and several keypresses may be coalesced into one
79
+ // (common when keys are pressed quickly). Buffer the stream and consume one
80
+ // recognized token at a time so navigation never silently drops keys.
81
+ let pending = Buffer.alloc(0);
82
+
83
+ function moveUp() {
84
+ selected = (selected - 1 + items.length) % items.length;
85
+ render(true);
86
+ }
87
+ function moveDown() {
88
+ selected = (selected + 1) % items.length;
89
+ render(true);
90
+ }
91
+
77
92
  function onKey(buf) {
78
- const key = buf.toString();
79
-
80
- if (key === '\x1b[A' || key === 'k') {
81
- selected = (selected - 1 + items.length) % items.length;
82
- render(true);
83
- } else if (key === '\x1b[B' || key === 'j') {
84
- selected = (selected + 1) % items.length;
85
- render(true);
86
- } else if (key === '\r' || key === '\n') {
87
- cleanup();
88
- process.stdout.write('\r\x1b[K\n');
89
- console.log(dim(` Selected: ${items[selected].label}`));
90
- resolve({ index: selected, value: items[selected].label });
91
- } else if (key === '\x03') {
92
- cleanup();
93
- process.stdout.write('\x1b[?1049l');
94
- process.exit(0);
93
+ pending = pending.length ? Buffer.concat([pending, buf]) : buf;
94
+
95
+ while (pending.length > 0) {
96
+ const b = pending[0];
97
+
98
+ if (b === 0x1b) {
99
+ // Escape sequence need at least ESC '[' <code> to interpret.
100
+ if (pending.length < 2) return; // wait for more bytes
101
+ if (pending[1] === 0x5b /* [ */) {
102
+ if (pending.length < 3) return; // wait for the final byte
103
+ const code = pending[2];
104
+ if (code === 0x41 /* A */) moveUp();
105
+ else if (code === 0x42 /* B */) moveDown();
106
+ // Ignore other CSI sequences (left/right/home/etc.)
107
+ pending = pending.subarray(3);
108
+ } else {
109
+ // Lone ESC or unsupported sequence — drop the ESC and continue.
110
+ pending = pending.subarray(1);
111
+ }
112
+ continue;
113
+ }
114
+
115
+ if (b === 0x0d /* \r */ || b === 0x0a /* \n */) {
116
+ cleanup();
117
+ process.stdout.write('\r\x1b[K\n');
118
+ console.log(dim(` Selected: ${items[selected].label}`));
119
+ resolve({ index: selected, value: items[selected].label });
120
+ return;
121
+ }
122
+ if (b === 0x03 /* Ctrl-C */) {
123
+ cleanup();
124
+ process.stdout.write('\x1b[?1049l');
125
+ process.exit(0);
126
+ }
127
+ if (b === 0x6b /* k */) moveUp();
128
+ else if (b === 0x6a /* j */) moveDown();
129
+ // Any other byte is ignored.
130
+ pending = pending.subarray(1);
95
131
  }
96
132
  }
97
133