@oh-my-pi/pi-tui 17.1.1 → 17.1.3

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
+ ## [17.1.2] - 2026-07-24
6
+
7
+ ### Fixed
8
+
9
+ - Fixed the terminal flickering when leaving a fullscreen overlay (e.g. `/settings`, the models page) or resizing on terminals that re-report their size whenever the alternate screen buffer toggles: the alt-toggle size echo no longer arms a destructive full-screen (ED3) rebuild, and the in-place repaint path is now auto-detected for any such terminal instead of only Warp ([#6511](https://github.com/can1357/oh-my-pi/issues/6511)).
10
+
5
11
  ## [17.1.1] - 2026-07-24
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": "17.1.1",
4
+ "version": "17.1.3",
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": "17.1.1",
41
- "@oh-my-pi/pi-utils": "17.1.1",
40
+ "@oh-my-pi/pi-natives": "17.1.3",
41
+ "@oh-my-pi/pi-utils": "17.1.3",
42
42
  "lru-cache": "11.5.2",
43
43
  "marked": "^18.0.6"
44
44
  },
package/src/tui.ts CHANGED
@@ -1091,6 +1091,13 @@ export class TUI extends Container {
1091
1091
  // resize frames so width changes truncate the transient viewport instead of
1092
1092
  // pushing wrapped fragments into native scrollback.
1093
1093
  #resizeAltActive = false;
1094
+ // Latched once this terminal is observed re-reporting its size across an
1095
+ // alternate-screen toggle (a pure height change between alt-buffer enter and
1096
+ // exit). That is the Warp-class quirk {@link reportsSizeOnAltScreenToggle}
1097
+ // hardcodes: without it, leaving a fullscreen overlay flashes a destructive
1098
+ // ED3 full paint and the revert SIGWINCH flashes another (#6511). Once set,
1099
+ // {@link #resizeRepaintsInPlace} routes resizes through the in-place path.
1100
+ #altToggleResizesInPlace = false;
1094
1101
  #stopped = false;
1095
1102
  // Always-on event-loop lag probe. The high default threshold keeps it quiet;
1096
1103
  // it only logs `ui.loop-blocked` (with the current loop phase) when a frame
@@ -1590,8 +1597,26 @@ export class TUI extends Container {
1590
1597
  // window) into a single render once the pane is quiet —
1591
1598
  // `#resizeEventPending` is set first so the eventual render still
1592
1599
  // classifies as a resize.
1600
+ // A SIGWINCH while a fullscreen overlay covers the transcript is
1601
+ // either a genuine resize behind the overlay or the alt-toggle size
1602
+ // echo (a terminal re-reporting its size whenever the alternate
1603
+ // screen buffer toggles). The transcript is not visible either way,
1604
+ // so arming the drag/settle would only queue a destructive ED3
1605
+ // rebuild that fires as a flash when the overlay closes (#6511). A
1606
+ // pure height change is the alt-toggle-echo signature — latch the
1607
+ // in-place resize path — and just repaint the overlay at the new
1608
+ // size. #resizeEventPending carries to the overlay-exit render so it
1609
+ // still classifies as a resize.
1610
+ if (this.#altActive) {
1611
+ if (this.#altEnterWidth === this.terminal.columns && this.#altEnterHeight !== this.terminal.rows) {
1612
+ this.#altToggleResizesInPlace = true;
1613
+ }
1614
+ this.#resizeEventPending = true;
1615
+ this.requestRender();
1616
+ return;
1617
+ }
1593
1618
  this.#resizeEventPending = true;
1594
- if (!resizeRepaintsInPlace()) {
1619
+ if (!this.#resizeRepaintsInPlace()) {
1595
1620
  // Enter the viewport fast path and (re)arm the settle timer, then
1596
1621
  // request the cheap viewport-only paint. The authoritative full
1597
1622
  // replay fires from the settle timer once the drag goes quiet.
@@ -2815,9 +2840,15 @@ export class TUI extends Container {
2815
2840
  this.#altPreviousLines = [];
2816
2841
  // A resize while on the alt buffer reflowed the terminal's saved
2817
2842
  // normal screen; it no longer matches our accounting, so force the
2818
- // geometry rebuild path instead of a stale diff.
2843
+ // geometry rebuild path instead of a stale diff. A pure height change
2844
+ // across the alt-buffer boundary (width unchanged) is the signature of
2845
+ // a terminal that re-reports its size whenever the alternate screen
2846
+ // toggles — the Warp-class quirk. Latch the in-place resize path so
2847
+ // this exit and the revert SIGWINCH repaint without an ED3 scrollback
2848
+ // rewrap instead of flashing a destructive full paint (#6511).
2819
2849
  if (width !== this.#altEnterWidth || height !== this.#altEnterHeight) {
2820
2850
  this.#resizeEventPending = true;
2851
+ if (width === this.#altEnterWidth) this.#altToggleResizesInPlace = true;
2821
2852
  }
2822
2853
  } else if (wantMouseTracking !== this.#altMouseTrackingActive) {
2823
2854
  this.terminal.write(wantMouseTracking ? MOUSE_TRACKING_ON : MOUSE_TRACKING_OFF);
@@ -2870,7 +2901,7 @@ export class TUI extends Container {
2870
2901
  // count too: both enter the geometry rebuild path below.
2871
2902
  const replayFullHistory =
2872
2903
  this.#hasEverRendered &&
2873
- !resizeRepaintsInPlace() &&
2904
+ !this.#resizeRepaintsInPlace() &&
2874
2905
  (this.#clearScrollbackOnNextRender ||
2875
2906
  this.#resizeEventPending ||
2876
2907
  (this.#previousWidth > 0 && this.#previousWidth !== width) ||
@@ -3018,7 +3049,7 @@ export class TUI extends Container {
3018
3049
  // feedback loop), so committed history keeps its old wrap.
3019
3050
  const firstPaint = !this.#hasEverRendered;
3020
3051
  const replaceRequested = this.#clearScrollbackOnNextRender;
3021
- const geometryRebuild = geometryChanged && !resizeRepaintsInPlace();
3052
+ const geometryRebuild = geometryChanged && !this.#resizeRepaintsInPlace();
3022
3053
  // Committed history no longer matches the frame: a finalized block
3023
3054
  // replaced its scrolled-off live render, or the frame collapsed into
3024
3055
  // recorded rows. Native scrollback is a render cache, not a court
@@ -3175,7 +3206,8 @@ export class TUI extends Container {
3175
3206
  windowTop,
3176
3207
  prevWindowTop,
3177
3208
  prevHardwareCursorRow,
3178
- forceWindowRewrite: this.#forceViewportRepaintOnNextRender || (geometryChanged && resizeRepaintsInPlace()),
3209
+ forceWindowRewrite:
3210
+ this.#forceViewportRepaintOnNextRender || (geometryChanged && this.#resizeRepaintsInPlace()),
3179
3211
  repaintVirtualScrollInPlace: hasVisibleOverlay,
3180
3212
  cursorTrackingLineCount,
3181
3213
  });
@@ -3812,6 +3844,21 @@ export class TUI extends Container {
3812
3844
  return `${enhancementExit}${ALT_SCREEN_EXIT}`;
3813
3845
  }
3814
3846
 
3847
+ /**
3848
+ * Whether a resize repaints the visible window in place — no alternate-screen
3849
+ * borrow, no ED3 scrollback rewrap. Combines the static host detection
3850
+ * ({@link resizeRepaintsInPlace}) with the runtime {@link #altToggleResizesInPlace}
3851
+ * latch, so a terminal that re-reports its size on alt-screen toggles is
3852
+ * treated like Warp once observed, breaking the overlay-exit ED3 flash loop.
3853
+ * An explicit `PI_TUI_RESIZE_IN_PLACE=0|false` suppresses the runtime latch;
3854
+ * multiplexer handling remains authoritative through the static predicate.
3855
+ */
3856
+ #resizeRepaintsInPlace(): boolean {
3857
+ const override = Bun.env.PI_TUI_RESIZE_IN_PLACE;
3858
+ const allowAutoDetection = override !== "0" && override !== "false";
3859
+ return resizeRepaintsInPlace() || (allowAutoDetection && this.#altToggleResizesInPlace);
3860
+ }
3861
+
3815
3862
  /**
3816
3863
  * Emit a throwaway viewport repaint for the resize fast path as a per-row
3817
3864
  * overwrite. A width change can make the terminal's normal buffer reflow