Merge PR #2775: fix(tui): reduce loader repaint flicker (@roboomp)
This commit is contained in:
@@ -67,6 +67,11 @@
|
||||
- Fixed CMUX sessions being treated as direct terminals during resize/reset because they do not set `TMUX`/`STY`/`ZELLIJ` and may run with `TERM=dumb`; the renderer now treats CMUX workspace/surface env markers as multiplexer signals and preserves pane scrollback instead of emitting ED3 (`CSI 3 J`).
|
||||
- Fixed a self-sustaining resize-redraw storm in Warp: the non-multiplexer resize fast path borrows the alternate screen, and Warp re-reports a one-row-different size whenever the alt buffer is toggled, so each drag frame fed back a fresh resize event and the TUI flooded ED3 full repaints with stable geometry. Resize now repaints in place (no alt-screen borrow, no ED3 rewrap) on terminals that re-report size on alt-screen toggles, matching the multiplexer path. Overridable with `PI_TUI_RESIZE_IN_PLACE=1|0`.
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed animated working-message loader frames repainting at 30fps on terminals without synchronized-output support, which could cause visible flicker during normal prompt rendering ([#2771](https://github.com/can1357/oh-my-pi/issues/2771)).
|
||||
|
||||
## [16.0.1] - 2026-06-15
|
||||
|
||||
### Added
|
||||
|
||||
@@ -57,12 +57,15 @@ export class Loader extends Text {
|
||||
this.#intervalId = setInterval(() => {
|
||||
const now = performance.now();
|
||||
const elapsed = now - this.#lastSpinnerTick;
|
||||
if (elapsed >= SPINNER_ADVANCE_MS) {
|
||||
const shouldAdvanceSpinner = elapsed >= SPINNER_ADVANCE_MS;
|
||||
if (shouldAdvanceSpinner) {
|
||||
const steps = Math.floor(elapsed / SPINNER_ADVANCE_MS);
|
||||
this.#currentFrame = (this.#currentFrame + steps) % this.#frames.length;
|
||||
this.#lastSpinnerTick += steps * SPINNER_ADVANCE_MS;
|
||||
}
|
||||
this.#updateDisplay();
|
||||
if (shouldAdvanceSpinner || this.#ui?.synchronizedOutput === true) {
|
||||
this.#updateDisplay();
|
||||
}
|
||||
}, intervalMs);
|
||||
}
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ describe("Loader component", () => {
|
||||
it("requests render when animated message bytes change between spinner frames", () => {
|
||||
vi.useFakeTimers();
|
||||
setSystemTime(new Date(1_000));
|
||||
const ui = { requestComponentRender: vi.fn() } as unknown as TUI;
|
||||
const ui = { synchronizedOutput: true, requestComponentRender: vi.fn() } as unknown as TUI;
|
||||
const colorMessage = ((text: string) => `${text}-${Date.now()}`) as LoaderMessageColorFn & { animated: true };
|
||||
colorMessage.animated = true;
|
||||
const loader = new Loader(ui, text => text, colorMessage, "Checking", ["0"]);
|
||||
@@ -106,6 +106,26 @@ describe("Loader component", () => {
|
||||
loader.stop();
|
||||
});
|
||||
|
||||
it("holds animated message-only frames when synchronized output is unavailable", () => {
|
||||
vi.useFakeTimers();
|
||||
setSystemTime(new Date(1_000));
|
||||
const ui = { synchronizedOutput: false, requestComponentRender: vi.fn() } as unknown as TUI;
|
||||
const colorMessage = ((text: string) => `${text}-${Date.now()}`) as LoaderMessageColorFn & { animated: true };
|
||||
colorMessage.animated = true;
|
||||
const loader = new Loader(ui, text => text, colorMessage, "Checking", ["0", "1"]);
|
||||
|
||||
expect(ui.requestComponentRender).toHaveBeenCalledTimes(1);
|
||||
|
||||
vi.advanceTimersByTime(34);
|
||||
expect(ui.requestComponentRender).toHaveBeenCalledTimes(1);
|
||||
|
||||
vi.advanceTimersByTime(67);
|
||||
expect(ui.requestComponentRender).toHaveBeenCalledTimes(2);
|
||||
expect(loader.render(40).join("\n")).toContain("1 Checking-");
|
||||
|
||||
loader.stop();
|
||||
});
|
||||
|
||||
it("dispose() stops the animation so no further renders are scheduled", async () => {
|
||||
const term = new VirtualTerminal(20, 4);
|
||||
const tui = new TUI(term);
|
||||
|
||||
Reference in New Issue
Block a user