@gajae-code/tui 0.16.7 → 0.17.1

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,16 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.17.1] - 2026-09-17
6
+
7
+ ## [0.17.0] - 2026-09-17
8
+
9
+ ### Performance
10
+
11
+ - Defer resize reflow-decision scans to their consuming branch, skipping them on unchanged-width frames and viewport-only repaints; coalesced resize appends avoid that decision scan while retained-prefix validation may still measure rows, preserving rendering and settled scrollback repair.
12
+
13
+ - Skip the kitty placement scan on terminals that do not run the kitty graphics protocol, and keep the outgoing logical and raw frames by reference instead of copying them. Both removed a walk over every transcript line from every render frame; emitted bytes are unchanged.
14
+
5
15
  ## [0.16.7] - 2026-09-13
6
16
 
7
17
  ## [0.16.6] - 2026-09-07
@@ -362,6 +362,8 @@ type TuiRenderCounterSnapshot = {
362
362
  debugRedrawAppendWrites: number;
363
363
  differentialGuardVisibleWidthCalls: number;
364
364
  widthReflowScanRows: number;
365
+ widthReflowVisibleWidthCalls: number;
366
+ kittyPlacementScanRows: number;
365
367
  };
366
368
  /**
367
369
  * TUI - Main class for managing terminal UI with differential rendering
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/tui",
4
- "version": "0.16.7",
4
+ "version": "0.17.1",
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.16.7",
40
- "@gajae-code/utils": "0.16.7",
39
+ "@gajae-code/natives": "0.17.1",
40
+ "@gajae-code/utils": "0.17.1",
41
41
  "lru-cache": "11.3.6",
42
42
  "marked": "18.0.6"
43
43
  },
package/src/tui.ts CHANGED
@@ -819,6 +819,8 @@ type TuiRenderCounterSnapshot = {
819
819
  debugRedrawAppendWrites: number;
820
820
  differentialGuardVisibleWidthCalls: number;
821
821
  widthReflowScanRows: number;
822
+ widthReflowVisibleWidthCalls: number;
823
+ kittyPlacementScanRows: number;
822
824
  };
823
825
  type RenderCommitWaiter = {
824
826
  resolve: (committed: boolean) => void;
@@ -1183,6 +1185,8 @@ export class TUI extends Container {
1183
1185
  debugRedrawAppendWrites: 0,
1184
1186
  differentialGuardVisibleWidthCalls: 0,
1185
1187
  widthReflowScanRows: 0,
1188
+ widthReflowVisibleWidthCalls: 0,
1189
+ kittyPlacementScanRows: 0,
1186
1190
  };
1187
1191
 
1188
1192
  static resetRenderCountersForTest(): void {
@@ -1191,6 +1195,8 @@ export class TUI extends Container {
1191
1195
  debugRedrawAppendWrites: 0,
1192
1196
  differentialGuardVisibleWidthCalls: 0,
1193
1197
  widthReflowScanRows: 0,
1198
+ widthReflowVisibleWidthCalls: 0,
1199
+ kittyPlacementScanRows: 0,
1194
1200
  };
1195
1201
  }
1196
1202
 
@@ -4128,7 +4134,12 @@ export class TUI extends Container {
4128
4134
  lines: string[],
4129
4135
  owners: ReadonlyMap<string, KittyPlacementOwner>,
4130
4136
  ): KittyPlacementSpan[] {
4137
+ // encodeKittyPlacement is only reachable from renderImage's kitty branch, so a
4138
+ // line rendered under any other protocol cannot carry a placement and the scan
4139
+ // can only return empty. See the commit message for the consumers this covers.
4140
+ if (TERMINAL.imageProtocol !== ImageProtocol.Kitty) return [];
4131
4141
  const placements: KittyPlacementSpan[] = [];
4142
+ TUI.#renderCounters.kittyPlacementScanRows += lines.length;
4132
4143
  for (let row = 0; row < lines.length; row++) {
4133
4144
  for (const placement of extractKittyPlacementReferences(lines[row])) {
4134
4145
  placements.push({
@@ -4736,8 +4747,9 @@ export class TUI extends Container {
4736
4747
  if (usedWindowNormalize) renderMetrics.recordLineCount("offscreenScan", diffStart);
4737
4748
  }
4738
4749
  const nextKittyPlacementSpans = this.#kittyPlacementSpansForLines(newLines, placementOwners);
4739
- const previousLogicalFrame = this.#latestRenderedLines.slice();
4740
- const previousRawFrame = this.#latestRaw.slice();
4750
+ // Reassigned, never mutated in place -- a reference is the snapshot.
4751
+ const previousLogicalFrame = this.#latestRenderedLines;
4752
+ const previousRawFrame = this.#latestRaw;
4741
4753
  const previousRenderedLength = previousLogicalFrame.length;
4742
4754
  this.#latestRenderedLines = newLines;
4743
4755
  this.#latestRenderedTranscriptLineCount = nextTranscriptLineCount;
@@ -5086,13 +5098,6 @@ export class TUI extends Container {
5086
5098
  return;
5087
5099
  }
5088
5100
  const useViewportRepaintPath = this.#viewportRepaintHost();
5089
- const widthReflowRequired =
5090
- widthChanged &&
5091
- this.#previousWidth > 0 &&
5092
- rawLines.some(line => {
5093
- TUI.#renderCounters.widthReflowScanRows += 1;
5094
- return !TERMINAL.isImageLine(line) && visibleWidth(line) > Math.min(this.#previousWidth, width);
5095
- });
5096
5101
  if (
5097
5102
  widthChanged &&
5098
5103
  !this.#legacyMultiplexerFullRender &&
@@ -5176,6 +5181,16 @@ export class TUI extends Container {
5176
5181
  // Width changes always need a full re-render because wrapping changes, unless
5177
5182
  // a proven coalesced append is continuing through the durable append path.
5178
5183
  if (widthChanged && !coalescedWidthAppend) {
5184
+ // Measure only where the reflow decision is consumed. Viewport-only
5185
+ // resize repaints return above; coalesced appends also skip this scan.
5186
+ const widthReflowRequired =
5187
+ this.#previousWidth > 0 &&
5188
+ rawLines.some(line => {
5189
+ TUI.#renderCounters.widthReflowScanRows += 1;
5190
+ if (TERMINAL.isImageLine(line)) return false;
5191
+ TUI.#renderCounters.widthReflowVisibleWidthCalls += 1;
5192
+ return visibleWidth(line) > Math.min(this.#previousWidth, width);
5193
+ });
5179
5194
  if (!widthReflowRequired) {
5180
5195
  this.#widthSettleRepairPending = false;
5181
5196
  logRedraw(`terminal width changed without reflow (${this.#previousWidth} -> ${width})`);