@oh-my-pi/pi-tui 15.1.8 → 15.1.9

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
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [15.1.9] - 2026-05-21
6
+
7
+ ### Fixed
8
+
9
+ - Fixed terminal probe responses (DA1, kitty keyboard, Mode 2031) leaking into the prompt as keystrokes when the response is split across stdin reads. `ProcessTerminal` now reassembles `\x1b[?<digits>...` private CSI fragments and dispatches the complete response through the existing pattern handlers. ([#1238](https://github.com/can1357/oh-my-pi/issues/1238))
10
+
5
11
  ## [15.1.4] - 2026-05-19
6
12
 
7
13
  ### Fixed
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-tui",
4
- "version": "15.1.8",
4
+ "version": "15.1.9",
5
5
  "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -37,8 +37,8 @@
37
37
  "fmt": "biome format --write ."
38
38
  },
39
39
  "dependencies": {
40
- "@oh-my-pi/pi-natives": "15.1.8",
41
- "@oh-my-pi/pi-utils": "15.1.8",
40
+ "@oh-my-pi/pi-natives": "15.1.9",
41
+ "@oh-my-pi/pi-utils": "15.1.9",
42
42
  "lru-cache": "11.3.6",
43
43
  "marked": "^18.0.3"
44
44
  },
package/src/terminal.ts CHANGED
@@ -127,6 +127,7 @@ export class ProcessTerminal implements Terminal {
127
127
  #osc11Pending = false;
128
128
  #osc11QueryQueued = false;
129
129
  #osc11ResponseBuffer = "";
130
+ #privateCsiResponseBuffer = "";
130
131
  #pendingDa1Sentinels = 0;
131
132
  #osc11PollTimer?: Timer;
132
133
  #mode2031DebounceTimer?: Timer;
@@ -278,8 +279,51 @@ export class ProcessTerminal implements Terminal {
278
279
  // DA1 (Primary Device Attributes) response: \x1b[?...c
279
280
  const da1ResponsePattern = /^\x1b\[\?[\d;]*c$/;
280
281
 
282
+ // Private CSI partial: \x1b[?<digits/semicolons>... — incomplete probe response
283
+ // that the StdinBuffer flushed before the terminator arrived (split across
284
+ // stdin reads). Used to reassemble DA1, kitty, and Mode 2031 replies.
285
+ const privateCsiPartialPattern = /^\x1b\[\?[\d;]*$/;
286
+
281
287
  // Forward individual sequences to the input handler
282
288
  this.#stdinBuffer.on("data", (sequence: string) => {
289
+ // Reassemble split private CSI responses (DA1, kitty keyboard, Mode 2031).
290
+ // When the terminal writes the response slowly enough that the StdinBuffer's
291
+ // flush timeout elapses mid-sequence, the prefix `\x1b[?<digits>` arrives as
292
+ // one event and the tail `;...<terminator>` arrives as individual character
293
+ // events that would otherwise leak into the prompt as keystrokes. See #1238.
294
+ if (
295
+ this.#privateCsiResponseBuffer ||
296
+ (privateCsiPartialPattern.test(sequence) && this.#pendingDa1Sentinels > 0)
297
+ ) {
298
+ if (this.#privateCsiResponseBuffer && sequence.startsWith("\x1b")) {
299
+ // New escape arrived mid-reassembly — abandon partial and re-process the new sequence.
300
+ this.#privateCsiResponseBuffer = "";
301
+ } else {
302
+ this.#privateCsiResponseBuffer += sequence;
303
+ // Cap accumulator to defend against runaway partials if the terminator never arrives.
304
+ if (this.#privateCsiResponseBuffer.length > 256) {
305
+ this.#privateCsiResponseBuffer = "";
306
+ return;
307
+ }
308
+ const lastChar = this.#privateCsiResponseBuffer.at(-1)!;
309
+ const lastCode = lastChar.charCodeAt(0);
310
+ if (lastCode >= 0x40 && lastCode <= 0x7e) {
311
+ // Terminator byte arrived. Fall through to the pattern checks with the
312
+ // reassembled sequence so the existing DA1/kitty/Mode 2031 handlers run.
313
+ sequence = this.#privateCsiResponseBuffer;
314
+ this.#privateCsiResponseBuffer = "";
315
+ } else if (!privateCsiPartialPattern.test(this.#privateCsiResponseBuffer)) {
316
+ // Diverged from a valid private CSI prefix (unexpected byte). Drop the
317
+ // probe noise we ate; do not forward to the input handler.
318
+ this.#privateCsiResponseBuffer = "";
319
+ return;
320
+ } else {
321
+ // Still accumulating.
322
+ return;
323
+ }
324
+ }
325
+ }
326
+
283
327
  // Check for Kitty protocol response (only if not already enabled)
284
328
  if (!this.#kittyProtocolActive) {
285
329
  const match = sequence.match(kittyResponsePattern);
@@ -531,6 +575,7 @@ export class ProcessTerminal implements Terminal {
531
575
  this.#osc11Pending = false;
532
576
  this.#osc11QueryQueued = false;
533
577
  this.#osc11ResponseBuffer = "";
578
+ this.#privateCsiResponseBuffer = "";
534
579
  this.#pendingDa1Sentinels = 0;
535
580
 
536
581
  // Disable Kitty keyboard protocol if not already done by drainInput()