@oh-my-pi/pi-tui 15.7.5 → 15.8.0

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,21 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [15.8.0] - 2026-06-02
6
+
7
+ ### Fixed
8
+
9
+ - Deferred eager live scrollback rebuilds on POSIX terminals where xterm ED3 (`CSI 3 J`, erase saved lines) can disturb scrolled-up readers during streaming, while keeping direct user-input and checkpoint rebuilds explicit ([#1682](https://github.com/can1357/oh-my-pi/issues/1682)).
10
+ - Fixed TUI shutdown placing the parent shell prompt one row below short rendered content instead of directly on the next line ([#1620](https://github.com/can1357/oh-my-pi/issues/1620)).
11
+ - Stopped painting inline color swatches for 4-digit hex runs in Markdown rendering. The `#RGBA` CSS form collides with hashline `#TAG` snapshot tags (4 hex digits, e.g. `#6C5E`), which were sprouting spurious RGB swatches in prose and codespans. Only `#RGB`, `#RRGGBB`, and `#RRGGBBAA` qualify now.
12
+
13
+ ## [15.7.6] - 2026-06-01
14
+
15
+ ### Fixed
16
+
17
+ - Fixed native Windows + Windows Terminal freezing the editor on the wrap keystroke, on `/plan`/`/resume`/model-switch/status-line toggles, and on any other offscreen structural mutation until the next prompt submit. The `15.7.5` `#1635` fix routed every viewport-saturating pure-append and structural mutation through `deferredMutation` (a literal no-op) whenever `isNativeViewportAtBottom()` returned `undefined` — which it always does under `WT_SESSION` because the kernel32 probe can't see WT host scrollback. The deferral was only ever meant for the *confirmed-scrolled* case; an unknown viewport now falls back to a non-destructive `viewportRepaint` instead, so the live UI keeps updating without emitting `\x1b[3J` and without yanking a possibly-scrolled reader. Confirmed-scrolled frames (probe returns `false`) still defer.
18
+ - Removed the hard-coded 20-result cap on `@`-prefixed fuzzy file completion in `CombinedAutocompleteProvider.#getFuzzyFileSuggestions`. The dropdown now honors the existing `maxResults: 100` ceiling already configured for `fuzzyFind`, so projects with many files sharing a common stem (e.g. `@controller`, `@test`) surface all relevant matches instead of being silently truncated. ([#1652](https://github.com/can1357/oh-my-pi/issues/1652))
19
+
5
20
  ## [15.7.5] - 2026-06-01
6
21
 
7
22
  ### Fixed
@@ -40,6 +55,7 @@
40
55
  - Fixed slash-command autocomplete repainting when a Windows Terminal session cannot report native scrollback position; live input renders can now bypass the unknown-viewport deferral without weakening background scrollback protection. ([#1550](https://github.com/can1357/oh-my-pi/issues/1550))
41
56
 
42
57
  ## [15.6.0] - 2026-05-30
58
+
43
59
  ### Added
44
60
 
45
61
  - Added autocomplete triggering for internal URL scheme tokens such as `local://` and `skill://` while typing in the editor
@@ -86,6 +102,7 @@
86
102
  - Fixed full TUI redraws clearing terminal scrollback with `CSI 3 J`, preserving manual scrollback inspection while active sessions continue updating. ([#1295](https://github.com/can1357/oh-my-pi/issues/1295))
87
103
 
88
104
  ## [15.2.3] - 2026-05-22
105
+
89
106
  ### Added
90
107
 
91
108
  - Added `SettingsList#setItems` to replace the entire settings list with a new items array while automatically clamping selection to a valid index
@@ -113,6 +130,7 @@
113
130
  - Restored the `Key` runtime helper on `@oh-my-pi/pi-tui` to mirror upstream `@mariozechner/pi-tui`'s surface. `Key.enter`, `Key.escape`, `Key.tab`, … return the canonical key-name strings; modifier methods (`Key.ctrl(k)`, `Key.shift(k)`, `Key.ctrlShift(k)`, etc.) build precisely-typed `KeyId` literals like `"ctrl+c"`. Pure runtime convenience for typed key-id construction — plugins built against the upstream package surface that import `Key` (e.g. `@plannotator/pi-extension`, `@juicesharp/rpiv-ask-user-question`) load again now that the specifier shim remaps them onto this package.
114
131
 
115
132
  ## [15.0.1] - 2026-05-14
133
+
116
134
  ### Breaking Changes
117
135
 
118
136
  - Increased the minimum required Bun version for the TUI package from >=1.3.7 to >=1.3.14
@@ -146,6 +164,10 @@
146
164
  - `SlashCommand.getArgumentCompletions()` may return a `Promise`; results are now awaited and non-array returns are ignored (ports pi-mono `a1e10789`)
147
165
  - Fuzzy `@` autocomplete now follows symlinked directories via `ScanOptions.follow_links` plumbed through the native walker (ports pi-mono `780d5367`)
148
166
  - Plain `@<query>` (no slash) fuzzy matches by basename only, so `@plan` no longer surfaces every file whose ancestor directories contain `plan` (ports pi-mono `968430f6`)
167
+ - Changed slash-command autocomplete list rendering to combine command hint and description in a single displayed suggestion text
168
+ - Changed render scheduling to throttle `requestRender` calls to roughly 60fps by batching updates
169
+ - Changed terminal input handling to process complete cell-size responses without buffering partial input
170
+ - Changed `KeyId` to accept super-modifier combinations and improve typed key-id validation
149
171
 
150
172
  ### Fixed
151
173
 
@@ -161,20 +183,11 @@
161
183
  - Allowed `SlashCommand.getArgumentCompletions` to return asynchronous results by accepting Promise-based completions
162
184
  - Added `argumentHint` support to slash command definitions and displayed it in command suggestion descriptions
163
185
  - Added support for xterm `modifyOtherKeys` printable key sequences by decoding `CSI 27;mod;key~` into text input
164
-
165
- ### Changed
166
-
167
- - Changed slash-command autocomplete list rendering to combine command hint and description in a single displayed suggestion text
168
- - Changed render scheduling to throttle `requestRender` calls to roughly 60fps by batching updates
169
- - Changed terminal input handling to process complete cell-size responses without buffering partial input
170
- - Changed `KeyId` to accept super-modifier combinations and improve typed key-id validation
171
-
172
- ### Fixed
173
-
174
186
  - Normalized line output during rendering to correct Thai/Lao AM glyph composition for displayed text
175
187
  - Fixed duplicated Kitty key input emissions by dropping the matching unmodified follow-up sequence after a Kitty CSI-u printable-key event
176
188
 
177
189
  ## [14.9.5] - 2026-05-12
190
+
178
191
  ### Fixed
179
192
 
180
193
  - Fixed rapidly blinking cursor artifact during task execution by consolidating cursor control sequences into the synchronized output buffer ([#992](https://github.com/can1357/oh-my-pi/issues/992))
@@ -228,6 +241,7 @@
228
241
  - Autocomplete fuzzy discovery now accepts optional SearchDb instance for faster searches
229
242
 
230
243
  ## [13.16.0] - 2026-03-27
244
+
231
245
  ### Changed
232
246
 
233
247
  - Updated tab replacement in editor text sanitization to respect configured tab width setting
@@ -243,6 +257,7 @@
243
257
  - Fixed editor consuming user-rebound copy keys, preventing custom keybindings from working in the editor
244
258
 
245
259
  ## [13.14.1] - 2026-03-21
260
+
246
261
  ### Added
247
262
 
248
263
  - Added Ctrl+_ as an additional default shortcut for undo
@@ -263,17 +278,20 @@
263
278
  - Fixed paste marker expansion to handle special regex replacement tokens ($1, $2, $&, $$, $`, $') literally in pasted content
264
279
 
265
280
  ## [13.11.0] - 2026-03-12
281
+
266
282
  ### Fixed
267
283
 
268
284
  - Fixed OSC 11 background color detection to correctly handle partial escape sequences that arrive mid-buffer, preventing user input from being swallowed
269
285
  - Fixed race condition where overlapping OSC 11 queries would be incorrectly cancelled by DA1 sentinels from previous queries
270
286
 
271
287
  ## [13.7.5] - 2026-03-04
288
+
272
289
  ### Changed
273
290
 
274
291
  - Extracted word navigation logic into reusable `moveWordLeft` and `moveWordRight` utility functions for consistent cursor movement across components
275
292
 
276
293
  ## [13.6.2] - 2026-03-03
294
+
277
295
  ### Fixed
278
296
 
279
297
  - Fixed cursor positioning when content shrinks to empty without clearOnShrink enabled
@@ -283,6 +301,7 @@
283
301
  ### Fixed
284
302
 
285
303
  - Fixed viewport repaint scrollback accounting during resize oscillation to avoid double-scrolling on height shrink and added exact-row scrollback assertions in overlay regression coverage ([#228](https://github.com/can1357/oh-my-pi/issues/228), [#234](https://github.com/can1357/oh-my-pi/issues/234))
304
+
286
305
  ## [13.5.3] - 2026-03-01
287
306
 
288
307
  ### Fixed
@@ -292,6 +311,7 @@
292
311
  - Fixed cursor positioning instability when appending content under external cursor relocation by using absolute screen addressing instead of relative cursor movement
293
312
 
294
313
  ## [13.5.2] - 2026-03-01
314
+
295
315
  ### Breaking Changes
296
316
 
297
317
  - Removed `getMermaidImage` callback from MarkdownTheme; replaced with `getMermaidAscii` that accepts ASCII string instead of image data
@@ -302,6 +322,7 @@
302
322
  - Mermaid diagrams now render as ASCII text instead of terminal graphics protocol images
303
323
 
304
324
  ## [13.5.1] - 2026-03-01
325
+
305
326
  ### Fixed
306
327
 
307
328
  - Fixed viewport shift handling to prevent stale content when mixed updates remap screen rows
@@ -329,6 +350,7 @@
329
350
 
330
351
  - Fixed stale/duplicated terminal cursor dedup state by synchronizing `#lastCursorSequence` in all render write paths (hard reset, viewport repaint, deleted-lines clear path, append fast path, and differential path).
331
352
  - Fixed scroll overshoot on `stop()` when content fills the viewport by clamping target row movement to valid screen rows.
353
+
332
354
  ## [13.4.0] - 2026-03-01
333
355
 
334
356
  ### Added
@@ -350,6 +372,7 @@
350
372
  - Restored terminal image protocol override and fallback detection for image rendering, including `PI_FORCE_IMAGE_PROTOCOL` support and Kitty fallback for screen/tmux/ghostty-style TERM environments.
351
373
 
352
374
  ## [13.3.8] - 2026-02-28
375
+
353
376
  ### Breaking Changes
354
377
 
355
378
  - Changed mermaid hash type from string to bigint in `getMermaidImage` callback and `extractMermaidBlocks` return type
@@ -371,6 +394,7 @@
371
394
  - Fixed stale viewport rows appearing when terminal height increases by triggering full re-render on height changes
372
395
 
373
396
  ## [12.18.0] - 2026-02-21
397
+
374
398
  ### Fixed
375
399
 
376
400
  - Fixed viewport synchronization issue by clearing scrollback when terminal state becomes desynced during full re-renders
@@ -399,18 +423,21 @@
399
423
  - Fixed incremental stale-row clearing to use erase-below semantics in synchronized output, reducing leftover-line artifacts after shrink operations.
400
424
 
401
425
  ## [12.9.0] - 2026-02-17
426
+
402
427
  ### Added
403
428
 
404
429
  - Exported `getTerminalId()` function to get a stable identifier for the current terminal, with support for TTY device paths and terminal multiplexers
405
430
  - Exported `getTtyPath()` function to resolve the TTY device path for stdin via POSIX `ttyname(3)`
406
431
 
407
432
  ## [12.5.0] - 2026-02-15
433
+
408
434
  ### Added
409
435
 
410
436
  - Added `cursorOverride` and `cursorOverrideWidth` properties to customize the end-of-text cursor glyph with ANSI-styled strings
411
437
  - Added `getUseTerminalCursor()` method to query the terminal cursor mode setting
412
438
 
413
439
  ## [11.10.0] - 2026-02-10
440
+
414
441
  ### Added
415
442
 
416
443
  - Added `hint` property to autocomplete items to display dim ghost text after cursor when item is selected
@@ -423,6 +450,7 @@
423
450
  - Updated editor to render inline hint text as dim ghost text after cursor when autocomplete suggestions are active or provider supplies hints
424
451
 
425
452
  ## [11.8.0] - 2026-02-10
453
+
426
454
  ### Added
427
455
 
428
456
  - Added Alt+Y keybinding to cycle through kill ring entries (yank-pop)
@@ -439,6 +467,7 @@
439
467
  - Changed undo coalescing in Input component to group consecutive word typing into single undo units
440
468
 
441
469
  ## [11.4.1] - 2026-02-06
470
+
442
471
  ### Fixed
443
472
 
444
473
  - Fixed terminal scrolling when displaying overlays after rendering large content, preventing hundreds of blank lines from being output
@@ -531,6 +560,7 @@
531
560
  - Fixed handling of private use Unicode codepoints (U+E000 to U+F8FF) in Kitty key decoding to prevent invalid character interpretation
532
561
 
533
562
  ## [9.7.0] - 2026-02-01
563
+
534
564
  ### Breaking Changes
535
565
 
536
566
  - Removed `Key` helper object from public API; use string literals like `"ctrl+c"` instead of `Key.ctrl("c")`
@@ -542,6 +572,7 @@
542
572
  - Simplified `isKeyRelease()` and `isKeyRepeat()` to use regex pattern matching instead of string inclusion checks
543
573
 
544
574
  ## [9.6.2] - 2026-02-01
575
+
545
576
  ### Changed
546
577
 
547
578
  - Renamed `EllipsisKind` enum to `Ellipsis` for clearer API naming
@@ -555,6 +586,7 @@
555
586
  - Removed `extractAnsiCode` function from public API
556
587
 
557
588
  ## [9.6.1] - 2026-02-01
589
+
558
590
  ### Changed
559
591
 
560
592
  - Improved performance of key ID parsing with optimized cache lookup strategy
@@ -565,12 +597,14 @@
565
597
  - Removed `visibleWidth` benchmark file in favor of Kitty sequence benchmarking
566
598
 
567
599
  ## [9.5.0] - 2026-02-01
600
+
568
601
  ### Changed
569
602
 
570
603
  - Improved fuzzy file search performance by using native implementation instead of spawning external process
571
604
  - Replaced external `fd` binary with native fuzzy path search for `@`-prefixed autocomplete
572
605
 
573
606
  ## [9.4.0] - 2026-01-31
607
+
574
608
  ### Added
575
609
 
576
610
  - Exported `padding` utility function for creating space-padded strings efficiently
@@ -582,59 +616,74 @@
582
616
  ## [9.2.2] - 2026-01-31
583
617
 
584
618
  ### Added
619
+
585
620
  - Added setAutocompleteMaxVisible() configuration (3-20 items)
586
621
  - Added image detection to terminal capabilities (containsImage method)
587
622
  - Added stdin monitoring to detect stalled input events and log warnings
588
623
 
589
624
  ### Changed
625
+
590
626
  - Improved blockquote rendering with text wrapping in Markdown component
591
627
  - Restructured terminal capabilities from interface-based to class-based model
592
628
  - Improved table column width calculation with word-aware wrapping
593
629
  - Refactored text utilities to use native WASM implementations for strings >256 chars with JS fast path
594
630
 
595
631
  ### Fixed
632
+
596
633
  - Simplified terminal write error handling to mark terminal as dead on any write failure
597
634
  - Fixed multi-line strings in renderOutputBlock causing width overflow
598
635
  - Fixed slash command autocomplete applying stale completion when typing quickly
599
636
 
600
637
  ### Removed
638
+
601
639
  - Removed TUI layout engine exports from public API (BoxNode, ColumnNode, LayoutNode, etc.)
602
640
 
603
641
  ## [8.12.7] - 2026-01-29
604
642
 
605
643
  ### Fixed
644
+
606
645
  - Fixed slash command autocomplete applying stale completion when typing quickly
607
646
 
608
647
  ## [8.4.1] - 2026-01-25
609
648
 
610
649
  ### Added
650
+
611
651
  - Added fuzzy match function for autocomplete suggestions
652
+
612
653
  ## [8.4.0] - 2026-01-25
613
654
 
614
655
  ### Changed
656
+
615
657
  - Added Ctrl+Backspace as a delete-word-backward keybinding and improved modified backspace matching
616
658
 
617
659
  ### Fixed
660
+
618
661
  - Terminal gracefully handles write failures by marking dead instead of exiting the process
619
662
  - Reserved cursor space for zero padding and corrected end-of-line cursor rendering to prevent wrap glitches
620
663
  - Corrected editor end-of-line cursor rendering assertion to use includes() instead of endsWith()
664
+
621
665
  ## [8.2.0] - 2026-01-24
622
666
 
623
667
  ### Added
668
+
624
669
  - Added mermaid diagram rendering engine (renderMermaidToPng) with mmdc CLI integration
625
670
  - Added terminal graphics encoding (iTerm2/Kitty) for mermaid diagrams with automatic width scaling
626
671
  - Added mermaid block extraction and deduplication utilities (extractMermaidBlocks)
627
672
 
628
673
  ### Changed
674
+
629
675
  - Updated TypeScript configuration for better publish-time configuration handling with tsconfig.publish.json
630
676
  - Migrated file system operations from synchronous to asynchronous APIs in autocomplete provider for non-blocking I/O
631
677
  - Migrated node module imports from named to namespace imports across all packages for consistency with project guidelines
632
678
 
633
679
  ### Fixed
680
+
634
681
  - Fixed crash when terminal becomes unavailable (EIO errors) by exiting gracefully instead of throwing
635
682
  - Fixed potential errors during emergency terminal restore when terminal is already dead
636
683
  - Fixed autocomplete race condition by tracking request ID to prevent stale suggestion results
684
+
637
685
  ## [6.8.3] - 2026-01-21
686
+
638
687
  ### Added
639
688
 
640
689
  - Added undo support in the editor via `Ctrl+-`
@@ -690,6 +739,7 @@
690
739
  - Fixed Alt+letter key combinations for better recognition
691
740
 
692
741
  ## [5.3.1] - 2026-01-15
742
+
693
743
  ### Fixed
694
744
 
695
745
  - Fixed rendering issues on Windows by preventing re-entrant renders
@@ -719,27 +769,32 @@
719
769
  ## [4.7.0] - 2026-01-12
720
770
 
721
771
  ### Fixed
772
+
722
773
  - Remove trailing space padding from Text, Markdown, and TruncatedText components when no background color is set (fixes copied text including unwanted whitespace)
723
774
 
724
775
  ## [4.6.0] - 2026-01-12
725
776
 
726
777
  ### Added
778
+
727
779
  - Add fuzzy matching module (`fuzzyMatch`, `fuzzyFilter`) for command autocomplete
728
780
  - Add `getExpandedText()` to editor for expanding paste markers
729
781
  - Add backslash+enter newline fallback for terminals without Kitty protocol
730
782
 
731
783
  ### Fixed
784
+
732
785
  - Remove Kitty protocol query timeout that caused shift+enter delays
733
786
  - Add bracketed paste check to prevent false key release/repeat detection
734
787
  - Rendering optimizations: only re-render changed lines
735
788
  - Refactor input component to use keybindings manager
736
789
 
737
790
  ## [4.4.4] - 2026-01-11
791
+
738
792
  ### Fixed
739
793
 
740
794
  - Fixed Ctrl+Enter sequences to insert new lines in the editor
741
795
 
742
796
  ## [4.2.1] - 2026-01-11
797
+
743
798
  ### Changed
744
799
 
745
800
  - Improved file autocomplete to show directory listing when typing `@` with no query, and fall back to prefix matching when fuzzy search returns no results
@@ -750,11 +805,13 @@
750
805
  - Fixed `fd` tool detection to automatically find `fd` or `fdfind` in PATH when not explicitly configured
751
806
 
752
807
  ## [4.1.0] - 2026-01-10
808
+
753
809
  ### Added
754
810
 
755
811
  - Added persistent prompt history storage support via `setHistoryStorage()` method, allowing history to be saved and restored across sessions
756
812
 
757
813
  ## [4.0.0] - 2026-01-10
814
+
758
815
  ### Added
759
816
 
760
817
  - `EditorComponent` interface for custom editor implementations
@@ -785,6 +842,7 @@
785
842
  - Fixed text wrapping allowing long whitespace tokens to exceed line width
786
843
 
787
844
  ## [3.20.0] - 2026-01-06
845
+
788
846
  ### Added
789
847
 
790
848
  - Added `isCapsLock` helper function for detecting Caps Lock key press via Kitty protocol
@@ -816,6 +874,7 @@
816
874
  - Added support for custom spinner frames in the Loader component
817
875
 
818
876
  ## [3.9.1337] - 2026-01-04
877
+
819
878
  ### Added
820
879
 
821
880
  - Added `setTopBorder()` method to Editor component for displaying custom status content in the top border
@@ -828,6 +887,7 @@
828
887
  - Changed cursor style from block to thin blinking bar (▏) at end of line
829
888
 
830
889
  ## [1.500.0] - 2026-01-03
890
+
831
891
  ### Added
832
892
 
833
893
  - Added `getText()` method to Text component for retrieving current text content
@@ -892,4 +952,4 @@ Initial release under @oh-my-pi scope. See previous releases at [badlogic/pi-mon
892
952
 
893
953
  ### Fixed
894
954
 
895
- - **Readline-style Ctrl+W**: Now skips trailing whitespace before deleting the preceding word, matching standard readline behavior. ([#306](https://github.com/badlogic/pi-mono/pull/306) by [@kim0](https://github.com/kim0))
955
+ - **Readline-style Ctrl+W**: Now skips trailing whitespace before deleting the preceding word, matching standard readline behavior. ([#306](https://github.com/badlogic/pi-mono/pull/306) by [@kim0](https://github.com/kim0))
@@ -56,6 +56,28 @@ export interface Terminal {
56
56
  export declare function shouldTrustNativeViewportProbe(env?: {
57
57
  WT_SESSION?: string | undefined;
58
58
  }, platform?: NodeJS.Platform): boolean;
59
+ /**
60
+ * Whether eager live-frame native scrollback rebuilds are unsafe for the
61
+ * current POSIX terminal when its viewport position is unobservable.
62
+ *
63
+ * A TUI history rebuild emits xterm ED3 (`CSI 3 J`, erase saved lines). On the
64
+ * terminals below, ED3 can disturb a reader parked in native scrollback during
65
+ * streaming: kitty/ghostty/alacritty clamp the scroll offset back to the active
66
+ * tail when saved lines are erased, and WezTerm is the reported POSIX host for
67
+ * #1682. Defer only the eager streaming opt-in on these hosts; direct
68
+ * user-input renders and explicit checkpoint rebuilds still pass their own
69
+ * `allowUnknownViewportMutation` / `allowUnknownViewport` flags.
70
+ *
71
+ * Pure helper for unit testing; the runtime call site reads `$env` /
72
+ * `process.platform`. See #1682.
73
+ */
74
+ export declare function terminalHasEagerEraseScrollbackRisk(env?: {
75
+ WEZTERM_PANE?: string | undefined;
76
+ KITTY_WINDOW_ID?: string | undefined;
77
+ GHOSTTY_RESOURCES_DIR?: string | undefined;
78
+ ALACRITTY_WINDOW_ID?: string | undefined;
79
+ TERM_PROGRAM?: string | undefined;
80
+ }, platform?: NodeJS.Platform): boolean;
59
81
  /**
60
82
  * Real terminal using process.stdin/stdout
61
83
  */
@@ -1,4 +1,4 @@
1
- import type { Terminal } from "./terminal";
1
+ import { type Terminal } from "./terminal";
2
2
  import { visibleWidth } from "./utils";
3
3
  type InputListenerResult = {
4
4
  consume?: boolean;
@@ -171,9 +171,11 @@ export declare class TUI extends Container {
171
171
  * non-destructive repaint. This trades the anti-yank guarantee for a clean,
172
172
  * duplicate-free history and is meant for windows where output above the fold
173
173
  * is actively re-rendering — e.g. a tool whose result is still streaming and
174
- * re-laying-out rows that have already scrolled into history. A snap to the tail
175
- * is acceptable there. A terminal that can report a *known*-scrolled viewport
176
- * (Windows) still defers; only the unknown case is forced to rebuild.
174
+ * re-laying-out rows that have already scrolled into history. A terminal that
175
+ * can report a *known*-scrolled viewport (Windows) still defers; only the
176
+ * unknown case is forced to rebuild. POSIX hosts known to disturb scrolled
177
+ * readers on xterm ED3 (`CSI 3 J`, erase saved lines) also defer the eager
178
+ * opt-in; checkpoint and direct user-input rebuilds are unaffected.
177
179
  */
178
180
  setEagerNativeScrollbackRebuild(enabled: boolean): void;
179
181
  setFocus(component: Component | null): void;
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.7.5",
4
+ "version": "15.8.0",
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.7.5",
41
- "@oh-my-pi/pi-utils": "15.7.5",
40
+ "@oh-my-pi/pi-natives": "15.8.0",
41
+ "@oh-my-pi/pi-utils": "15.8.0",
42
42
  "lru-cache": "11.5.1",
43
43
  "marked": "^18.0.4"
44
44
  },
@@ -736,7 +736,9 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
736
736
  }
737
737
  return lowerQuery.length === 0 || fuzzyMatch(lowerQuery, normalized.toLowerCase());
738
738
  });
739
- const topEntries = filteredMatches.slice(0, 20);
739
+ // `fuzzyFind` is already capped via `maxResults` in
740
+ // `buildAutocompleteFuzzyDiscoveryProfile`; no extra slice here.
741
+ const topEntries = filteredMatches;
740
742
  const suggestions: AutocompleteItem[] = [];
741
743
  for (const { path: entryPath, isDirectory } of topEntries) {
742
744
  const pathWithoutSlash = isDirectory ? entryPath.slice(0, -1) : entryPath;
@@ -146,22 +146,24 @@ const DEFAULT_COLOR_SWATCH_GLYPH = "■";
146
146
  // entities like &#9731; and paths like foo#fff) and not trailed by more hex
147
147
  // (so over-long runs never produce a misleading swatch). Length/letter rules
148
148
  // are enforced in classifyHexColor since the alternation can't express "exactly
149
- // 3, 4, 6, or 8".
149
+ // 3, 6, or 8".
150
150
  const HEX_COLOR_REGEX = /(?<![\w#&])#([0-9a-fA-F]{3,8})(?![0-9a-fA-F])/g;
151
151
  const HEX_COLOR_EXACT_REGEX = /^#([0-9a-fA-F]{3,8})$/;
152
152
 
153
153
  /**
154
154
  * Decide whether a run of hex digits denotes a renderable CSS color.
155
155
  *
156
- * Only the canonical CSS lengths (#RGB, #RGBA, #RRGGBB, #RRGGBBAA) qualify. In
157
- * `strict` mode (bare prose) a 3/4-digit run must contain a hex letter, so the
156
+ * Only the canonical CSS lengths (#RGB, #RRGGBB, #RRGGBBAA) qualify. The 4-digit
157
+ * #RGBA form is deliberately excluded: it collides with hashline `#TAG` snapshot
158
+ * tags (4 hex digits, e.g. #6C5E), which would otherwise sprout spurious swatches.
159
+ * In `strict` mode (bare prose) a 3-digit run must contain a hex letter, so the
158
160
  * far more common short issue/PR references (#123, #1011) don't sprout swatches.
159
161
  * Codespans opt out of strictness — the backticks already signal "this is a color".
160
162
  */
161
163
  function classifyHexColor(hex: string, strict: boolean): boolean {
162
164
  const n = hex.length;
163
- if (n !== 3 && n !== 4 && n !== 6 && n !== 8) return false;
164
- if (strict && n <= 4 && !/[a-fA-F]/.test(hex)) return false;
165
+ if (n !== 3 && n !== 6 && n !== 8) return false;
166
+ if (strict && n === 3 && !/[a-fA-F]/.test(hex)) return false;
165
167
  return true;
166
168
  }
167
169
 
package/src/terminal.ts CHANGED
@@ -134,6 +134,39 @@ export function shouldTrustNativeViewportProbe(
134
134
  return true;
135
135
  }
136
136
 
137
+ /**
138
+ * Whether eager live-frame native scrollback rebuilds are unsafe for the
139
+ * current POSIX terminal when its viewport position is unobservable.
140
+ *
141
+ * A TUI history rebuild emits xterm ED3 (`CSI 3 J`, erase saved lines). On the
142
+ * terminals below, ED3 can disturb a reader parked in native scrollback during
143
+ * streaming: kitty/ghostty/alacritty clamp the scroll offset back to the active
144
+ * tail when saved lines are erased, and WezTerm is the reported POSIX host for
145
+ * #1682. Defer only the eager streaming opt-in on these hosts; direct
146
+ * user-input renders and explicit checkpoint rebuilds still pass their own
147
+ * `allowUnknownViewportMutation` / `allowUnknownViewport` flags.
148
+ *
149
+ * Pure helper for unit testing; the runtime call site reads `$env` /
150
+ * `process.platform`. See #1682.
151
+ */
152
+ export function terminalHasEagerEraseScrollbackRisk(
153
+ env: {
154
+ WEZTERM_PANE?: string | undefined;
155
+ KITTY_WINDOW_ID?: string | undefined;
156
+ GHOSTTY_RESOURCES_DIR?: string | undefined;
157
+ ALACRITTY_WINDOW_ID?: string | undefined;
158
+ TERM_PROGRAM?: string | undefined;
159
+ } = $env,
160
+ platform: NodeJS.Platform = process.platform,
161
+ ): boolean {
162
+ if (platform === "win32") return false;
163
+ if (env.WEZTERM_PANE || env.KITTY_WINDOW_ID || env.GHOSTTY_RESOURCES_DIR || env.ALACRITTY_WINDOW_ID) {
164
+ return true;
165
+ }
166
+ const termProgram = env.TERM_PROGRAM?.toLowerCase();
167
+ return termProgram === "ghostty";
168
+ }
169
+
137
170
  /**
138
171
  * Real terminal using process.stdin/stdout
139
172
  */
package/src/tui.ts CHANGED
@@ -6,7 +6,7 @@ import * as path from "node:path";
6
6
  import { performance } from "node:perf_hooks";
7
7
  import { $flag, getDebugLogPath } from "@oh-my-pi/pi-utils";
8
8
  import { isKeyRelease, matchesKey } from "./keys";
9
- import type { Terminal } from "./terminal";
9
+ import { type Terminal, terminalHasEagerEraseScrollbackRisk } from "./terminal";
10
10
  import { ImageProtocol, setCellDimensions, setTerminalImageProtocol, TERMINAL } from "./terminal-capabilities";
11
11
  import {
12
12
  Ellipsis,
@@ -386,9 +386,11 @@ export class TUI extends Container {
386
386
  * non-destructive repaint. This trades the anti-yank guarantee for a clean,
387
387
  * duplicate-free history and is meant for windows where output above the fold
388
388
  * is actively re-rendering — e.g. a tool whose result is still streaming and
389
- * re-laying-out rows that have already scrolled into history. A snap to the tail
390
- * is acceptable there. A terminal that can report a *known*-scrolled viewport
391
- * (Windows) still defers; only the unknown case is forced to rebuild.
389
+ * re-laying-out rows that have already scrolled into history. A terminal that
390
+ * can report a *known*-scrolled viewport (Windows) still defers; only the
391
+ * unknown case is forced to rebuild. POSIX hosts known to disturb scrolled
392
+ * readers on xterm ED3 (`CSI 3 J`, erase saved lines) also defer the eager
393
+ * opt-in; checkpoint and direct user-input rebuilds are unaffected.
392
394
  */
393
395
  setEagerNativeScrollbackRebuild(enabled: boolean): void {
394
396
  this.#eagerNativeScrollbackRebuild = enabled;
@@ -664,16 +666,23 @@ export class TUI extends Container {
664
666
  clearTimeout(this.#renderTimer);
665
667
  this.#renderTimer = undefined;
666
668
  }
667
- // Move cursor to the end of the content to prevent overwriting/artifacts on exit
669
+ // Place the parent shell on the first line after the rendered content. When
670
+ // that line is still inside the viewport, moving there and writing `\r` is
671
+ // enough; emitting `\r\n` would create an extra blank row. If the content
672
+ // already reaches the viewport bottom, scroll exactly once so the prompt
673
+ // lands directly below the last visible TUI row.
668
674
  if (this.#previousLines.length > 0) {
669
- const targetRow = this.#previousLines.length; // Line after the last content
670
- const lineDiff = targetRow - this.#hardwareCursorRow;
675
+ const targetRow = this.#previousLines.length;
676
+ const viewportBottom = this.#viewportTopRow + this.terminal.rows - 1;
677
+ const clampedCursorRow = Math.max(this.#viewportTopRow, Math.min(this.#hardwareCursorRow, viewportBottom));
678
+ const moveTargetRow = Math.min(targetRow, viewportBottom);
679
+ const lineDiff = moveTargetRow - clampedCursorRow;
671
680
  if (lineDiff > 0) {
672
681
  this.terminal.write(`\x1b[${lineDiff}B`);
673
682
  } else if (lineDiff < 0) {
674
683
  this.terminal.write(`\x1b[${-lineDiff}A`);
675
684
  }
676
- this.terminal.write("\r\n");
685
+ this.terminal.write(targetRow <= viewportBottom ? "\r" : "\r\n");
677
686
  }
678
687
 
679
688
  this.terminal.showCursor();
@@ -1180,8 +1189,8 @@ export class TUI extends Container {
1180
1189
  const prevHardwareCursorRow = this.#hardwareCursorRow;
1181
1190
  const widthChanged = this.#previousWidth > 0 && this.#previousWidth !== width;
1182
1191
  const heightChanged = this.#previousHeight > 0 && this.#previousHeight !== height;
1183
- const allowUnknownViewportMutation =
1184
- this.#allowUnknownViewportMutationOnNextRender || this.#eagerNativeScrollbackRebuild;
1192
+ const eagerRebuildAllowed = this.#eagerNativeScrollbackRebuild && !terminalHasEagerEraseScrollbackRisk();
1193
+ const allowUnknownViewportMutation = this.#allowUnknownViewportMutationOnNextRender || eagerRebuildAllowed;
1185
1194
  this.#allowUnknownViewportMutationOnNextRender = false;
1186
1195
 
1187
1196
  // 3. Classify intent.
@@ -1337,8 +1346,8 @@ export class TUI extends Container {
1337
1346
  }
1338
1347
  // POSIX terminals — and Windows Terminal/ConPTY — that cannot report the
1339
1348
  // viewport position fall through here (`canRebuildNativeScrollbackLive` is
1340
- // false). A destructive rebuild emits `\x1b[3J`, which on modern terminals
1341
- // resets the viewport to the top of scrollback and yanks a scrolled-up
1349
+ // false). A destructive rebuild emits `\x1b[3J` (xterm erase saved lines),
1350
+ // which can clear or reposition native scrollback and yank a scrolled-up
1342
1351
  // reader (issue #1635), so it is unsafe while the probe is unavailable.
1343
1352
  //
1344
1353
  // When the shrunk transcript now fits entirely in the viewport there is no
@@ -1439,14 +1448,32 @@ export class TUI extends Container {
1439
1448
  const nativeViewportAtBottom = this.#readNativeViewportAtBottom();
1440
1449
  if (this.#nativeViewportIsScrolled(nativeViewportAtBottom, allowUnknownViewportMutation)) {
1441
1450
  this.#markNativeScrollbackDirty();
1442
- return { kind: "deferredMutation" };
1451
+ // Confirmed scrolled (probe returned `false`): the reader is parked in
1452
+ // scrollback and writing the live frame is wasted bytes — defer until
1453
+ // the next checkpoint reconciles. Unknown viewport (e.g. native Windows
1454
+ // Terminal where the probe cannot see WT host scrollback) is a
1455
+ // different case: a no-op there freezes the editor on the keystroke
1456
+ // that grows `lines.length` past the viewport (the wrap keystroke).
1457
+ // Fall through to a non-destructive viewport repaint instead so the
1458
+ // live UI keeps updating without yanking a possibly-scrolled reader.
1459
+ if (this.#nativeViewportIsKnownScrolled(nativeViewportAtBottom)) {
1460
+ return { kind: "deferredMutation" };
1461
+ }
1462
+ return { kind: "viewportRepaint" };
1443
1463
  }
1444
1464
  }
1445
1465
  if (!pureAppend && structuralMutation && !isMultiplexerSession()) {
1446
1466
  const nativeViewportAtBottom = this.#readNativeViewportAtBottom();
1447
1467
  if (this.#nativeViewportIsScrolled(nativeViewportAtBottom, allowUnknownViewportMutation)) {
1448
1468
  this.#markNativeScrollbackDirty();
1449
- return { kind: "deferredMutation" };
1469
+ // See the matching comment on the pure-append branch above: confirmed
1470
+ // scrolled stays a no-op; unknown viewport repaints the visible window
1471
+ // so slash-command transitions and offscreen chrome edits paint on the
1472
+ // same frame instead of stalling until the next prompt submit.
1473
+ if (this.#nativeViewportIsKnownScrolled(nativeViewportAtBottom)) {
1474
+ return { kind: "deferredMutation" };
1475
+ }
1476
+ return { kind: "viewportRepaint" };
1450
1477
  }
1451
1478
  // The append-tail path can only scroll a clean pure-tail append over an
1452
1479
  // offscreen edit into history: the rows it pushes must equal the net
@@ -1621,6 +1648,10 @@ export class TUI extends Container {
1621
1648
  );
1622
1649
  }
1623
1650
 
1651
+ #nativeViewportIsKnownScrolled(nativeViewportAtBottom: boolean | undefined): boolean {
1652
+ return nativeViewportAtBottom === false;
1653
+ }
1654
+
1624
1655
  #nativeViewportIsAtBottom(nativeViewportAtBottom: boolean | undefined): boolean {
1625
1656
  return nativeViewportAtBottom === true;
1626
1657
  }
@@ -1638,8 +1669,8 @@ export class TUI extends Container {
1638
1669
  /**
1639
1670
  * Live-frame counterpart to {@link #canReplayNativeScrollbackAtCheckpoint}.
1640
1671
  * Decides whether a destructive native scrollback rebuild
1641
- * (`historyRebuild`/`overlayRebuild`, which clear scrollback and snap the
1642
- * viewport to the tail) is safe to emit *during ordinary rendering*. POSIX
1672
+ * (`historyRebuild`/`overlayRebuild`, which clears saved lines and may move
1673
+ * the native viewport) is safe to emit *during ordinary rendering*. POSIX
1643
1674
  * terminals cannot report whether the user has scrolled up
1644
1675
  * (`isNativeViewportAtBottom()` is `undefined`), so an unknown position is
1645
1676
  * treated as unsafe: defer to a non-destructive viewport repaint, mark
@@ -1647,10 +1678,11 @@ export class TUI extends Container {
1647
1678
  * ({@link refreshNativeScrollbackIfDirty} on prompt submit) where the
1648
1679
  * editor keystroke has already pinned the terminal to the bottom. Without
1649
1680
  * this, every offscreen transcript edit while streaming wiped scrollback and
1650
- * yanked a scrolled-up reader back down. `allowUnknownViewportMutation`
1651
- * (autocomplete/IME) opts directly user-driven frames back into the rebuild.
1652
- * Unlike the checkpoint predicate this carries no `process.platform`
1653
- * optimism — resize and checkpoint replays keep using that one.
1681
+ * yanked a scrolled-up reader out of their current context.
1682
+ * `allowUnknownViewportMutation` (autocomplete/IME) opts directly
1683
+ * user-driven frames back into the rebuild. Unlike the checkpoint predicate
1684
+ * this carries no `process.platform` optimism — resize and checkpoint replays
1685
+ * keep using that one.
1654
1686
  */
1655
1687
  #canRebuildNativeScrollbackLive(
1656
1688
  nativeViewportAtBottom: boolean | undefined,