@oh-my-pi/pi-tui 17.2.2 → 17.2.4
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 +7 -0
- package/package.json +3 -3
- package/src/components/loader.ts +30 -15
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [17.2.4] - 2026-08-01
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed animated Loader paints saturating a CPU core on slow WSL/ConPTY terminals by applying cost-aware cadence backpressure while preserving 30fps on cheap frames ([#7290](https://github.com/can1357/oh-my-pi/issues/7290)).
|
|
10
|
+
- Fixed interactive terminals suppressing all output and input when the host project sets `NODE_ENV=test` or `BUN_ENV=test` ([#7261](https://github.com/can1357/oh-my-pi/issues/7261)).
|
|
11
|
+
|
|
5
12
|
## [17.2.2] - 2026-07-31
|
|
6
13
|
|
|
7
14
|
### Added
|
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.2.
|
|
4
|
+
"version": "17.2.4",
|
|
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.2.
|
|
41
|
-
"@oh-my-pi/pi-utils": "17.2.
|
|
40
|
+
"@oh-my-pi/pi-natives": "17.2.4",
|
|
41
|
+
"@oh-my-pi/pi-utils": "17.2.4",
|
|
42
42
|
"lru-cache": "11.5.2",
|
|
43
43
|
"marked": "^18.0.6"
|
|
44
44
|
},
|
package/src/components/loader.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { Text } from "./text";
|
|
|
4
4
|
|
|
5
5
|
const RENDER_INTERVAL_MS = 1000 / 30;
|
|
6
6
|
const SPINNER_ADVANCE_MS = 80;
|
|
7
|
+
const MAX_RENDER_BACKPRESSURE_MS = 200;
|
|
8
|
+
const RENDER_BACKPRESSURE_MULTIPLIER = 9;
|
|
7
9
|
|
|
8
10
|
type ColorFn = (str: string) => string;
|
|
9
11
|
|
|
@@ -98,25 +100,12 @@ export class Loader extends Text {
|
|
|
98
100
|
this.#syncText();
|
|
99
101
|
this.#requestPaint();
|
|
100
102
|
const intervalMs = this.messageColorFn.animated === true ? RENDER_INTERVAL_MS : SPINNER_ADVANCE_MS;
|
|
101
|
-
this.#
|
|
102
|
-
const now = performance.now();
|
|
103
|
-
const elapsed = now - this.#lastSpinnerTick;
|
|
104
|
-
const shouldAdvanceSpinner = elapsed >= SPINNER_ADVANCE_MS;
|
|
105
|
-
if (shouldAdvanceSpinner) {
|
|
106
|
-
const steps = Math.floor(elapsed / SPINNER_ADVANCE_MS);
|
|
107
|
-
this.#currentFrame = (this.#currentFrame + steps) % this.#frames.length;
|
|
108
|
-
this.#lastSpinnerTick += steps * SPINNER_ADVANCE_MS;
|
|
109
|
-
this.#syncText();
|
|
110
|
-
}
|
|
111
|
-
if (shouldAdvanceSpinner || this.#ui?.synchronizedOutput === true) {
|
|
112
|
-
this.#requestPaint();
|
|
113
|
-
}
|
|
114
|
-
}, intervalMs);
|
|
103
|
+
this.#scheduleTick(intervalMs, intervalMs);
|
|
115
104
|
}
|
|
116
105
|
|
|
117
106
|
stop() {
|
|
118
107
|
if (this.#intervalId) {
|
|
119
|
-
|
|
108
|
+
clearTimeout(this.#intervalId);
|
|
120
109
|
this.#intervalId = undefined;
|
|
121
110
|
}
|
|
122
111
|
}
|
|
@@ -135,6 +124,32 @@ export class Loader extends Text {
|
|
|
135
124
|
this.#requestPaint();
|
|
136
125
|
}
|
|
137
126
|
|
|
127
|
+
#scheduleTick(intervalMs: number, delayMs: number): void {
|
|
128
|
+
const timer = setTimeout(() => {
|
|
129
|
+
if (this.#intervalId !== timer) return;
|
|
130
|
+
const startedAt = performance.now();
|
|
131
|
+
const elapsed = startedAt - this.#lastSpinnerTick;
|
|
132
|
+
const shouldAdvanceSpinner = elapsed >= SPINNER_ADVANCE_MS;
|
|
133
|
+
if (shouldAdvanceSpinner) {
|
|
134
|
+
const steps = Math.floor(elapsed / SPINNER_ADVANCE_MS);
|
|
135
|
+
this.#currentFrame = (this.#currentFrame + steps) % this.#frames.length;
|
|
136
|
+
this.#lastSpinnerTick += steps * SPINNER_ADVANCE_MS;
|
|
137
|
+
this.#syncText();
|
|
138
|
+
}
|
|
139
|
+
if (shouldAdvanceSpinner || this.#ui?.synchronizedOutput === true) {
|
|
140
|
+
this.#requestPaint();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const frameCostMs = performance.now() - startedAt;
|
|
144
|
+
if (this.#intervalId !== timer) return;
|
|
145
|
+
const cadenceDelayMs = Math.max(0, intervalMs - frameCostMs);
|
|
146
|
+
// Idle for nine times the paint cost to keep animation at or below
|
|
147
|
+
// 10% CPU, while cheap frames retain their original cadence.
|
|
148
|
+
const backpressureDelayMs = Math.min(MAX_RENDER_BACKPRESSURE_MS, frameCostMs * RENDER_BACKPRESSURE_MULTIPLIER);
|
|
149
|
+
this.#scheduleTick(intervalMs, Math.max(cadenceDelayMs, backpressureDelayMs));
|
|
150
|
+
}, delayMs);
|
|
151
|
+
this.#intervalId = timer;
|
|
152
|
+
}
|
|
138
153
|
/** Re-wrap the underlying Text only when its message or frame width changes. */
|
|
139
154
|
#syncText(): boolean {
|
|
140
155
|
const layoutFrame = this.#layoutFrames[this.#currentFrame];
|