@oh-my-pi/pi-tui 15.7.6 → 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,14 +2,19 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
- ## [15.7.6] - 2026-06-01
5
+ ## [15.8.0] - 2026-06-02
6
6
 
7
7
  ### Fixed
8
8
 
9
- - 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.
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
10
14
 
11
15
  ### Fixed
12
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.
13
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))
14
19
 
15
20
  ## [15.7.5] - 2026-06-01
@@ -50,6 +55,7 @@
50
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))
51
56
 
52
57
  ## [15.6.0] - 2026-05-30
58
+
53
59
  ### Added
54
60
 
55
61
  - Added autocomplete triggering for internal URL scheme tokens such as `local://` and `skill://` while typing in the editor
@@ -96,6 +102,7 @@
96
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))
97
103
 
98
104
  ## [15.2.3] - 2026-05-22
105
+
99
106
  ### Added
100
107
 
101
108
  - Added `SettingsList#setItems` to replace the entire settings list with a new items array while automatically clamping selection to a valid index
@@ -123,6 +130,7 @@
123
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.
124
131
 
125
132
  ## [15.0.1] - 2026-05-14
133
+
126
134
  ### Breaking Changes
127
135
 
128
136
  - Increased the minimum required Bun version for the TUI package from >=1.3.7 to >=1.3.14
@@ -156,6 +164,10 @@
156
164
  - `SlashCommand.getArgumentCompletions()` may return a `Promise`; results are now awaited and non-array returns are ignored (ports pi-mono `a1e10789`)
157
165
  - Fuzzy `@` autocomplete now follows symlinked directories via `ScanOptions.follow_links` plumbed through the native walker (ports pi-mono `780d5367`)
158
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
159
171
 
160
172
  ### Fixed
161
173
 
@@ -171,20 +183,11 @@
171
183
  - Allowed `SlashCommand.getArgumentCompletions` to return asynchronous results by accepting Promise-based completions
172
184
  - Added `argumentHint` support to slash command definitions and displayed it in command suggestion descriptions
173
185
  - Added support for xterm `modifyOtherKeys` printable key sequences by decoding `CSI 27;mod;key~` into text input
174
-
175
- ### Changed
176
-
177
- - Changed slash-command autocomplete list rendering to combine command hint and description in a single displayed suggestion text
178
- - Changed render scheduling to throttle `requestRender` calls to roughly 60fps by batching updates
179
- - Changed terminal input handling to process complete cell-size responses without buffering partial input
180
- - Changed `KeyId` to accept super-modifier combinations and improve typed key-id validation
181
-
182
- ### Fixed
183
-
184
186
  - Normalized line output during rendering to correct Thai/Lao AM glyph composition for displayed text
185
187
  - Fixed duplicated Kitty key input emissions by dropping the matching unmodified follow-up sequence after a Kitty CSI-u printable-key event
186
188
 
187
189
  ## [14.9.5] - 2026-05-12
190
+
188
191
  ### Fixed
189
192
 
190
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))
@@ -238,6 +241,7 @@
238
241
  - Autocomplete fuzzy discovery now accepts optional SearchDb instance for faster searches
239
242
 
240
243
  ## [13.16.0] - 2026-03-27
244
+
241
245
  ### Changed
242
246
 
243
247
  - Updated tab replacement in editor text sanitization to respect configured tab width setting
@@ -253,6 +257,7 @@
253
257
  - Fixed editor consuming user-rebound copy keys, preventing custom keybindings from working in the editor
254
258
 
255
259
  ## [13.14.1] - 2026-03-21
260
+
256
261
  ### Added
257
262
 
258
263
  - Added Ctrl+_ as an additional default shortcut for undo
@@ -273,17 +278,20 @@
273
278
  - Fixed paste marker expansion to handle special regex replacement tokens ($1, $2, $&, $$, $`, $') literally in pasted content
274
279
 
275
280
  ## [13.11.0] - 2026-03-12
281
+
276
282
  ### Fixed
277
283
 
278
284
  - Fixed OSC 11 background color detection to correctly handle partial escape sequences that arrive mid-buffer, preventing user input from being swallowed
279
285
  - Fixed race condition where overlapping OSC 11 queries would be incorrectly cancelled by DA1 sentinels from previous queries
280
286
 
281
287
  ## [13.7.5] - 2026-03-04
288
+
282
289
  ### Changed
283
290
 
284
291
  - Extracted word navigation logic into reusable `moveWordLeft` and `moveWordRight` utility functions for consistent cursor movement across components
285
292
 
286
293
  ## [13.6.2] - 2026-03-03
294
+
287
295
  ### Fixed
288
296
 
289
297
  - Fixed cursor positioning when content shrinks to empty without clearOnShrink enabled
@@ -293,6 +301,7 @@
293
301
  ### Fixed
294
302
 
295
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
+
296
305
  ## [13.5.3] - 2026-03-01
297
306
 
298
307
  ### Fixed
@@ -302,6 +311,7 @@
302
311
  - Fixed cursor positioning instability when appending content under external cursor relocation by using absolute screen addressing instead of relative cursor movement
303
312
 
304
313
  ## [13.5.2] - 2026-03-01
314
+
305
315
  ### Breaking Changes
306
316
 
307
317
  - Removed `getMermaidImage` callback from MarkdownTheme; replaced with `getMermaidAscii` that accepts ASCII string instead of image data
@@ -312,6 +322,7 @@
312
322
  - Mermaid diagrams now render as ASCII text instead of terminal graphics protocol images
313
323
 
314
324
  ## [13.5.1] - 2026-03-01
325
+
315
326
  ### Fixed
316
327
 
317
328
  - Fixed viewport shift handling to prevent stale content when mixed updates remap screen rows
@@ -339,6 +350,7 @@
339
350
 
340
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).
341
352
  - Fixed scroll overshoot on `stop()` when content fills the viewport by clamping target row movement to valid screen rows.
353
+
342
354
  ## [13.4.0] - 2026-03-01
343
355
 
344
356
  ### Added
@@ -360,6 +372,7 @@
360
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.
361
373
 
362
374
  ## [13.3.8] - 2026-02-28
375
+
363
376
  ### Breaking Changes
364
377
 
365
378
  - Changed mermaid hash type from string to bigint in `getMermaidImage` callback and `extractMermaidBlocks` return type
@@ -381,6 +394,7 @@
381
394
  - Fixed stale viewport rows appearing when terminal height increases by triggering full re-render on height changes
382
395
 
383
396
  ## [12.18.0] - 2026-02-21
397
+
384
398
  ### Fixed
385
399
 
386
400
  - Fixed viewport synchronization issue by clearing scrollback when terminal state becomes desynced during full re-renders
@@ -409,18 +423,21 @@
409
423
  - Fixed incremental stale-row clearing to use erase-below semantics in synchronized output, reducing leftover-line artifacts after shrink operations.
410
424
 
411
425
  ## [12.9.0] - 2026-02-17
426
+
412
427
  ### Added
413
428
 
414
429
  - Exported `getTerminalId()` function to get a stable identifier for the current terminal, with support for TTY device paths and terminal multiplexers
415
430
  - Exported `getTtyPath()` function to resolve the TTY device path for stdin via POSIX `ttyname(3)`
416
431
 
417
432
  ## [12.5.0] - 2026-02-15
433
+
418
434
  ### Added
419
435
 
420
436
  - Added `cursorOverride` and `cursorOverrideWidth` properties to customize the end-of-text cursor glyph with ANSI-styled strings
421
437
  - Added `getUseTerminalCursor()` method to query the terminal cursor mode setting
422
438
 
423
439
  ## [11.10.0] - 2026-02-10
440
+
424
441
  ### Added
425
442
 
426
443
  - Added `hint` property to autocomplete items to display dim ghost text after cursor when item is selected
@@ -433,6 +450,7 @@
433
450
  - Updated editor to render inline hint text as dim ghost text after cursor when autocomplete suggestions are active or provider supplies hints
434
451
 
435
452
  ## [11.8.0] - 2026-02-10
453
+
436
454
  ### Added
437
455
 
438
456
  - Added Alt+Y keybinding to cycle through kill ring entries (yank-pop)
@@ -449,6 +467,7 @@
449
467
  - Changed undo coalescing in Input component to group consecutive word typing into single undo units
450
468
 
451
469
  ## [11.4.1] - 2026-02-06
470
+
452
471
  ### Fixed
453
472
 
454
473
  - Fixed terminal scrolling when displaying overlays after rendering large content, preventing hundreds of blank lines from being output
@@ -541,6 +560,7 @@
541
560
  - Fixed handling of private use Unicode codepoints (U+E000 to U+F8FF) in Kitty key decoding to prevent invalid character interpretation
542
561
 
543
562
  ## [9.7.0] - 2026-02-01
563
+
544
564
  ### Breaking Changes
545
565
 
546
566
  - Removed `Key` helper object from public API; use string literals like `"ctrl+c"` instead of `Key.ctrl("c")`
@@ -552,6 +572,7 @@
552
572
  - Simplified `isKeyRelease()` and `isKeyRepeat()` to use regex pattern matching instead of string inclusion checks
553
573
 
554
574
  ## [9.6.2] - 2026-02-01
575
+
555
576
  ### Changed
556
577
 
557
578
  - Renamed `EllipsisKind` enum to `Ellipsis` for clearer API naming
@@ -565,6 +586,7 @@
565
586
  - Removed `extractAnsiCode` function from public API
566
587
 
567
588
  ## [9.6.1] - 2026-02-01
589
+
568
590
  ### Changed
569
591
 
570
592
  - Improved performance of key ID parsing with optimized cache lookup strategy
@@ -575,12 +597,14 @@
575
597
  - Removed `visibleWidth` benchmark file in favor of Kitty sequence benchmarking
576
598
 
577
599
  ## [9.5.0] - 2026-02-01
600
+
578
601
  ### Changed
579
602
 
580
603
  - Improved fuzzy file search performance by using native implementation instead of spawning external process
581
604
  - Replaced external `fd` binary with native fuzzy path search for `@`-prefixed autocomplete
582
605
 
583
606
  ## [9.4.0] - 2026-01-31
607
+
584
608
  ### Added
585
609
 
586
610
  - Exported `padding` utility function for creating space-padded strings efficiently
@@ -592,59 +616,74 @@
592
616
  ## [9.2.2] - 2026-01-31
593
617
 
594
618
  ### Added
619
+
595
620
  - Added setAutocompleteMaxVisible() configuration (3-20 items)
596
621
  - Added image detection to terminal capabilities (containsImage method)
597
622
  - Added stdin monitoring to detect stalled input events and log warnings
598
623
 
599
624
  ### Changed
625
+
600
626
  - Improved blockquote rendering with text wrapping in Markdown component
601
627
  - Restructured terminal capabilities from interface-based to class-based model
602
628
  - Improved table column width calculation with word-aware wrapping
603
629
  - Refactored text utilities to use native WASM implementations for strings >256 chars with JS fast path
604
630
 
605
631
  ### Fixed
632
+
606
633
  - Simplified terminal write error handling to mark terminal as dead on any write failure
607
634
  - Fixed multi-line strings in renderOutputBlock causing width overflow
608
635
  - Fixed slash command autocomplete applying stale completion when typing quickly
609
636
 
610
637
  ### Removed
638
+
611
639
  - Removed TUI layout engine exports from public API (BoxNode, ColumnNode, LayoutNode, etc.)
612
640
 
613
641
  ## [8.12.7] - 2026-01-29
614
642
 
615
643
  ### Fixed
644
+
616
645
  - Fixed slash command autocomplete applying stale completion when typing quickly
617
646
 
618
647
  ## [8.4.1] - 2026-01-25
619
648
 
620
649
  ### Added
650
+
621
651
  - Added fuzzy match function for autocomplete suggestions
652
+
622
653
  ## [8.4.0] - 2026-01-25
623
654
 
624
655
  ### Changed
656
+
625
657
  - Added Ctrl+Backspace as a delete-word-backward keybinding and improved modified backspace matching
626
658
 
627
659
  ### Fixed
660
+
628
661
  - Terminal gracefully handles write failures by marking dead instead of exiting the process
629
662
  - Reserved cursor space for zero padding and corrected end-of-line cursor rendering to prevent wrap glitches
630
663
  - Corrected editor end-of-line cursor rendering assertion to use includes() instead of endsWith()
664
+
631
665
  ## [8.2.0] - 2026-01-24
632
666
 
633
667
  ### Added
668
+
634
669
  - Added mermaid diagram rendering engine (renderMermaidToPng) with mmdc CLI integration
635
670
  - Added terminal graphics encoding (iTerm2/Kitty) for mermaid diagrams with automatic width scaling
636
671
  - Added mermaid block extraction and deduplication utilities (extractMermaidBlocks)
637
672
 
638
673
  ### Changed
674
+
639
675
  - Updated TypeScript configuration for better publish-time configuration handling with tsconfig.publish.json
640
676
  - Migrated file system operations from synchronous to asynchronous APIs in autocomplete provider for non-blocking I/O
641
677
  - Migrated node module imports from named to namespace imports across all packages for consistency with project guidelines
642
678
 
643
679
  ### Fixed
680
+
644
681
  - Fixed crash when terminal becomes unavailable (EIO errors) by exiting gracefully instead of throwing
645
682
  - Fixed potential errors during emergency terminal restore when terminal is already dead
646
683
  - Fixed autocomplete race condition by tracking request ID to prevent stale suggestion results
684
+
647
685
  ## [6.8.3] - 2026-01-21
686
+
648
687
  ### Added
649
688
 
650
689
  - Added undo support in the editor via `Ctrl+-`
@@ -700,6 +739,7 @@
700
739
  - Fixed Alt+letter key combinations for better recognition
701
740
 
702
741
  ## [5.3.1] - 2026-01-15
742
+
703
743
  ### Fixed
704
744
 
705
745
  - Fixed rendering issues on Windows by preventing re-entrant renders
@@ -729,27 +769,32 @@
729
769
  ## [4.7.0] - 2026-01-12
730
770
 
731
771
  ### Fixed
772
+
732
773
  - Remove trailing space padding from Text, Markdown, and TruncatedText components when no background color is set (fixes copied text including unwanted whitespace)
733
774
 
734
775
  ## [4.6.0] - 2026-01-12
735
776
 
736
777
  ### Added
778
+
737
779
  - Add fuzzy matching module (`fuzzyMatch`, `fuzzyFilter`) for command autocomplete
738
780
  - Add `getExpandedText()` to editor for expanding paste markers
739
781
  - Add backslash+enter newline fallback for terminals without Kitty protocol
740
782
 
741
783
  ### Fixed
784
+
742
785
  - Remove Kitty protocol query timeout that caused shift+enter delays
743
786
  - Add bracketed paste check to prevent false key release/repeat detection
744
787
  - Rendering optimizations: only re-render changed lines
745
788
  - Refactor input component to use keybindings manager
746
789
 
747
790
  ## [4.4.4] - 2026-01-11
791
+
748
792
  ### Fixed
749
793
 
750
794
  - Fixed Ctrl+Enter sequences to insert new lines in the editor
751
795
 
752
796
  ## [4.2.1] - 2026-01-11
797
+
753
798
  ### Changed
754
799
 
755
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
@@ -760,11 +805,13 @@
760
805
  - Fixed `fd` tool detection to automatically find `fd` or `fdfind` in PATH when not explicitly configured
761
806
 
762
807
  ## [4.1.0] - 2026-01-10
808
+
763
809
  ### Added
764
810
 
765
811
  - Added persistent prompt history storage support via `setHistoryStorage()` method, allowing history to be saved and restored across sessions
766
812
 
767
813
  ## [4.0.0] - 2026-01-10
814
+
768
815
  ### Added
769
816
 
770
817
  - `EditorComponent` interface for custom editor implementations
@@ -795,6 +842,7 @@
795
842
  - Fixed text wrapping allowing long whitespace tokens to exceed line width
796
843
 
797
844
  ## [3.20.0] - 2026-01-06
845
+
798
846
  ### Added
799
847
 
800
848
  - Added `isCapsLock` helper function for detecting Caps Lock key press via Kitty protocol
@@ -826,6 +874,7 @@
826
874
  - Added support for custom spinner frames in the Loader component
827
875
 
828
876
  ## [3.9.1337] - 2026-01-04
877
+
829
878
  ### Added
830
879
 
831
880
  - Added `setTopBorder()` method to Editor component for displaying custom status content in the top border
@@ -838,6 +887,7 @@
838
887
  - Changed cursor style from block to thin blinking bar (▏) at end of line
839
888
 
840
889
  ## [1.500.0] - 2026-01-03
890
+
841
891
  ### Added
842
892
 
843
893
  - Added `getText()` method to Text component for retrieving current text content
@@ -902,4 +952,4 @@ Initial release under @oh-my-pi scope. See previous releases at [badlogic/pi-mon
902
952
 
903
953
  ### Fixed
904
954
 
905
- - **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.6",
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.6",
41
- "@oh-my-pi/pi-utils": "15.7.6",
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
  },
@@ -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
@@ -1660,8 +1669,8 @@ export class TUI extends Container {
1660
1669
  /**
1661
1670
  * Live-frame counterpart to {@link #canReplayNativeScrollbackAtCheckpoint}.
1662
1671
  * Decides whether a destructive native scrollback rebuild
1663
- * (`historyRebuild`/`overlayRebuild`, which clear scrollback and snap the
1664
- * 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
1665
1674
  * terminals cannot report whether the user has scrolled up
1666
1675
  * (`isNativeViewportAtBottom()` is `undefined`), so an unknown position is
1667
1676
  * treated as unsafe: defer to a non-destructive viewport repaint, mark
@@ -1669,10 +1678,11 @@ export class TUI extends Container {
1669
1678
  * ({@link refreshNativeScrollbackIfDirty} on prompt submit) where the
1670
1679
  * editor keystroke has already pinned the terminal to the bottom. Without
1671
1680
  * this, every offscreen transcript edit while streaming wiped scrollback and
1672
- * yanked a scrolled-up reader back down. `allowUnknownViewportMutation`
1673
- * (autocomplete/IME) opts directly user-driven frames back into the rebuild.
1674
- * Unlike the checkpoint predicate this carries no `process.platform`
1675
- * 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.
1676
1686
  */
1677
1687
  #canRebuildNativeScrollbackLive(
1678
1688
  nativeViewportAtBottom: boolean | undefined,