@gajae-code/tui 0.18.9 → 0.18.10

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,14 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.18.10] - 2026-10-12
6
+
7
+ ### Fixed
8
+
9
+ - TUI no longer goes blank after stop/restart cycles: `#hardwareCursorRow` is now properly reset during `stop()` to prevent stale cursor position state from corrupting viewport calculations on the next `start()`.
10
+
11
+ - With `clearOnShrink` enabled, a live block that shrinks after the transcript has scrolled into history no longer leaves blank spacer rows above the pinned composer until the next resize. The contraction now clears the screen and history and replays the frame, so the viewport is filled again and no row appears twice in history.
12
+
5
13
  ## [0.18.9] - 2026-10-11
6
14
 
7
15
  ### Added
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/tui",
4
- "version": "0.18.9",
4
+ "version": "0.18.10",
5
5
  "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
6
6
  "homepage": "https://gajae-code.com",
7
7
  "author": "Yeachan-Heo and Gajae Code Contributors",
@@ -36,8 +36,8 @@
36
36
  "fmt": "biome format --write ."
37
37
  },
38
38
  "dependencies": {
39
- "@gajae-code/natives": "0.18.9",
40
- "@gajae-code/utils": "0.18.9",
39
+ "@gajae-code/natives": "0.18.10",
40
+ "@gajae-code/utils": "0.18.10",
41
41
  "lru-cache": "11.3.6",
42
42
  "marked": "18.0.6"
43
43
  },
package/src/tui.ts CHANGED
@@ -1152,6 +1152,7 @@ export class TUI extends Container {
1152
1152
  #fullRedrawCount = 0;
1153
1153
  #stopped = false;
1154
1154
  #terminalUnavailable = false;
1155
+ #restartNeedsClear = false; // Set when stop() resets viewport state
1155
1156
  #bottomPinnedComponent: Component | null = null;
1156
1157
  #pendingTerminalCleanup: Array<{ payload: string; onDelivered?: () => void }> = [];
1157
1158
  #mouseSelectionStart: MouseSelectionPoint | null = null;
@@ -1432,6 +1433,11 @@ export class TUI extends Container {
1432
1433
  this.#clearOnShrink = enabled;
1433
1434
  }
1434
1435
 
1436
+ /** Overlays rely on padded rows, so contraction replays only without them. */
1437
+ #clearOnShrinkReplays(): boolean {
1438
+ return this.#clearOnShrink && this.overlayStack.length === 0;
1439
+ }
1440
+
1435
1441
  setFocus(component: Component | null): void {
1436
1442
  // Clear focused flag on old component
1437
1443
  if (isFocusable(this.#focusedComponent)) {
@@ -2981,6 +2987,12 @@ export class TUI extends Container {
2981
2987
  this.#lineEmitWidthCache.clear();
2982
2988
  this.#previousWidth = 0;
2983
2989
  this.#previousHeight = 0;
2990
+ this.#cursorRow = 0;
2991
+ this.#hardwareCursorRow = 0;
2992
+ this.#viewportTopRow = 0;
2993
+ this.#maxLinesRendered = 0;
2994
+ // Mark that the next initial render needs to clear the screen after terminal reset
2995
+ this.#restartNeedsClear = true;
2984
2996
  this.#forcedRenderPreviousWidth = 0;
2985
2997
  this.#forcedRenderPreviousHeight = 0;
2986
2998
  this.#transcriptRebuildPending = false;
@@ -5268,7 +5280,10 @@ export class TUI extends Container {
5268
5280
  if (hasStickySuffix && height > 0 && this.#manualViewportTop === undefined) {
5269
5281
  // Rebuilds start at their new transcript frontier. Forced redraws use the
5270
5282
  // captured rendered dimensions rather than their -1 invalidation sentinels.
5283
+ // clearOnShrink replays the whole frame on contraction instead; spacer rows
5284
+ // would otherwise stay on screen as a blank gap above the pinned component.
5271
5285
  const preserveLiveFrontier =
5286
+ !this.#clearOnShrinkReplays() &&
5272
5287
  !transcriptIdentityReplaced &&
5273
5288
  !transcriptRebuildPending &&
5274
5289
  frontierPreviousWidth === width &&
@@ -5788,9 +5803,12 @@ export class TUI extends Container {
5788
5803
  return;
5789
5804
  }
5790
5805
  // First render - just output everything without clearing (assumes clean screen)
5806
+ // unless we're restarting after a stop() which leaves stale content
5791
5807
  if (initialRender) {
5792
5808
  logRedraw("first render");
5793
- fullRender(false, "first render");
5809
+ const shouldClear = this.#restartNeedsClear;
5810
+ this.#restartNeedsClear = false;
5811
+ fullRender(shouldClear, "first render");
5794
5812
  return;
5795
5813
  }
5796
5814
 
@@ -5848,13 +5866,11 @@ export class TUI extends Container {
5848
5866
  // Content shrunk below the previous render and no overlays - re-render to clear empty rows
5849
5867
  // (overlays need the padding, so only do this when no overlays are active)
5850
5868
  // Configurable via setClearOnShrink() or GJC_CLEAR_ON_SHRINK=0 env var
5851
- if (this.#clearOnShrink && newLines.length < this.#previousLines.length && this.overlayStack.length === 0) {
5869
+ // Erase native scrollback too: scrolled-off rows cannot be pulled back into the
5870
+ // viewport, and repainting them on screen would duplicate them in history.
5871
+ if (this.#clearOnShrinkReplays() && newLines.length < this.#previousLines.length) {
5852
5872
  logRedraw(`clearOnShrink (prev=${this.#previousLines.length}, new=${newLines.length})`);
5853
- if (useViewportRepaintPath) {
5854
- viewportRepaint(`clearOnShrink (prev=${this.#previousLines.length}, new=${newLines.length})`);
5855
- } else {
5856
- fullRender(true, "clearOnShrink");
5857
- }
5873
+ fullRender(true, "clearOnShrink", true);
5858
5874
  return;
5859
5875
  }
5860
5876