@oh-my-pi/pi-tui 16.3.13 → 16.3.15

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
+ ## [16.3.14] - 2026-07-09
6
+
7
+ ### Fixed
8
+
9
+ - Fixed race condition where scrollback rows could be incorrectly retracted between render frames
10
+
5
11
  ## [16.3.13] - 2026-07-09
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": "16.3.13",
4
+ "version": "16.3.15",
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": "16.3.13",
41
- "@oh-my-pi/pi-utils": "16.3.13",
40
+ "@oh-my-pi/pi-natives": "16.3.15",
41
+ "@oh-my-pi/pi-utils": "16.3.15",
42
42
  "lru-cache": "11.5.1",
43
43
  "marked": "^18.0.5"
44
44
  },
package/src/tui.ts CHANGED
@@ -1137,8 +1137,14 @@ export class TUI extends Container {
1137
1137
  // Feed the engine's committed-row claim (from the previous frame's
1138
1138
  // emit) before rendering so the child can skip re-deriving blocks
1139
1139
  // that already live in immutable native scrollback. Reused segments
1140
- // skip this: they never call render(), so the signal is moot.
1141
- setNativeScrollbackCommittedRows(child, Math.max(0, this.#committedRows - offset));
1140
+ // skip this: they never call render(), so the signal is moot. The
1141
+ // claim is in the previous frame's coordinates and never exceeds
1142
+ // the rows the child actually contributed there — history that
1143
+ // advanced into LATER root children must not read as this child's
1144
+ // own future rows being pre-committed.
1145
+ const prevRows = previous !== undefined && previous.component === child ? previous.rowCount : 0;
1146
+ const prevStart = previous !== undefined && previous.component === child ? previous.start : offset;
1147
+ setNativeScrollbackCommittedRows(child, Math.min(prevRows, Math.max(0, this.#committedRows - prevStart)));
1142
1148
  childLines = child.render(width);
1143
1149
  const liveRegionStart = getNativeScrollbackLiveRegionStart(child);
1144
1150
  if (liveRegionStart !== undefined) {
@@ -2802,6 +2808,7 @@ export class TUI extends Container {
2802
2808
  this.#committedPrefixAuditRows = Math.min(chunkTo, finalBoundary);
2803
2809
  this.#clearScrollbackOnNextRender = false;
2804
2810
  this.#hasEverRendered = true;
2811
+ this.#publishCommittedRows();
2805
2812
  if (!firstPaint && frameLength > height) this.#armPostFullPaintSettle();
2806
2813
  return;
2807
2814
  }
@@ -2829,6 +2836,7 @@ export class TUI extends Container {
2829
2836
  } else {
2830
2837
  this.#committedPrefixAuditRows = Math.min(preAuditRows, this.#committedRows);
2831
2838
  }
2839
+ this.#publishCommittedRows();
2832
2840
  }
2833
2841
 
2834
2842
  /**
@@ -2853,6 +2861,25 @@ export class TUI extends Container {
2853
2861
  }
2854
2862
  }
2855
2863
 
2864
+ /**
2865
+ * Push the post-emit committed-row count to root children that implement
2866
+ * {@link NativeScrollbackCommittedRows}. Compose feeds the same signal
2867
+ * before each child render (see {@link render}), but guards that run
2868
+ * BETWEEN frames — e.g. a controller consulting the transcript's
2869
+ * committed boundary to decide whether a displaceable block may still be
2870
+ * retracted — would otherwise observe a count one frame stale and retract
2871
+ * rows that just entered immutable native scrollback, stranding an
2872
+ * orphaned copy above the repainted block.
2873
+ */
2874
+ #publishCommittedRows(): void {
2875
+ for (const segment of this.#frameSegments) {
2876
+ setNativeScrollbackCommittedRows(
2877
+ segment.component,
2878
+ Math.min(segment.rowCount, Math.max(0, this.#committedRows - segment.start)),
2879
+ );
2880
+ }
2881
+ }
2882
+
2856
2883
  /**
2857
2884
  * Prepare the composed frame for emission, in place. Rows below
2858
2885
  * `#preparedValidRows` are already prepared against the current frame (the