Merge PR #5866: fix(tui): prevent settings exit flicker (@roboomp)
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
- Fixed wrapped OSC 8 links in Markdown tables making cell padding, separators, and adjacent cells clickable ([#5885](https://github.com/can1357/oh-my-pi/issues/5885)).
|
||||
- Fixed interactive sessions surviving terminal closure and entering a runaway render loop by stopping the TUI and raising SIGHUP when terminal input closes or output fails ([#5835](https://github.com/can1357/oh-my-pi/issues/5835)).
|
||||
- Fixed native cmux SSH pane resizes inserting blank rows into terminal scrollback by routing remote-transport sessions through the in-place repaint path ([#5857](https://github.com/can1357/oh-my-pi/issues/5857)).
|
||||
- Fixed the terminal flickering when leaving a fullscreen overlay (e.g. `/settings`) on terminals that re-report their size when the alternate screen buffer toggles: the alt-toggle SIGWINCH echo is height-only, so the resize fast path no longer borrows the alternate screen for it ([#5854](https://github.com/can1357/oh-my-pi/issues/5854)).
|
||||
|
||||
## [17.0.2] - 2026-07-17
|
||||
|
||||
|
||||
+15
-7
@@ -3727,15 +3727,23 @@ export class TUI extends Container {
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a throwaway viewport repaint for the resize fast path as an alternate-
|
||||
* screen per-row overwrite. The normal buffer may reflow full-width rows on a
|
||||
* width change before the app can repaint; keeping the drag on the alternate
|
||||
* screen makes those transient resizes truncate instead of pushing wrapped
|
||||
* fragments into native scrollback. Normal-screen history is rebuilt once at
|
||||
* settle via `#emitFullPaint`.
|
||||
* Emit a throwaway viewport repaint for the resize fast path as a per-row
|
||||
* overwrite. A width change can make the terminal's normal buffer reflow
|
||||
* full-width rows before the app repaints, so a width drag borrows the
|
||||
* alternate screen: transient resizes truncate the viewport instead of
|
||||
* pushing wrapped fragments into native scrollback. A height-only resize
|
||||
* reflows nothing, so it repaints the normal screen in place — borrowing the
|
||||
* alt buffer there is pure flicker, and on terminals that re-report their
|
||||
* size when the alt buffer toggles it is self-sustaining: leaving a
|
||||
* fullscreen overlay's alt screen fires a height-only SIGWINCH echo, which
|
||||
* would otherwise re-borrow the alt buffer for one frame (the settings-exit
|
||||
* flash, #5854). Normal-screen history is rebuilt once at settle via
|
||||
* `#emitFullPaint`.
|
||||
*/
|
||||
#emitResizeViewport(window: readonly string[], height: number, contentRows: number, width: number): void {
|
||||
let buffer = `${this.#paintBeginSequence + this.#enterResizeAltSequence()}\x1b[H`;
|
||||
const widthChanged = this.#previousWidth > 0 && this.#previousWidth !== width;
|
||||
const altEnter = widthChanged ? this.#enterResizeAltSequence() : "";
|
||||
let buffer = `${this.#paintBeginSequence + altEnter}\x1b[H`;
|
||||
for (let r = 0; r < height; r++) {
|
||||
if (r > 0) buffer += "\r\n";
|
||||
buffer += this.#lineRewriteSequence(window[r] ?? "", width);
|
||||
|
||||
@@ -495,6 +495,38 @@ describe("non-multiplexer resize viewport fast path", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("does not borrow the alternate screen for a height-only resize (settings-exit flash, #5854)", async () => {
|
||||
await withEnvPatch(NO_MULTIPLEXER_ENV, async () => {
|
||||
const term = new VirtualTerminal(40, 10, 1000);
|
||||
const { tui, scheduler } = makeTui(term);
|
||||
try {
|
||||
tui.start();
|
||||
await scheduler.flushImmediates(term);
|
||||
|
||||
const writes = captureWrites(term);
|
||||
|
||||
// A height-only SIGWINCH — width unchanged — reflows nothing in the
|
||||
// terminal's normal buffer, so the fast path repaints it in place.
|
||||
// Borrowing the alt buffer here is pure flicker: on terminals that
|
||||
// re-report their size when the alt buffer toggles, leaving a
|
||||
// fullscreen overlay fires exactly this height-only echo, and an
|
||||
// alt borrow would re-enter the alt screen for one frame (the flash).
|
||||
term.resize(40, 8);
|
||||
await scheduler.flushImmediates(term);
|
||||
|
||||
expect(tui.resizeViewportActive).toBe(true);
|
||||
expect(tui.resizeViewportPaints).toBeGreaterThan(0);
|
||||
const drag = writes.join("");
|
||||
expect(drag).not.toContain(ALT_SCREEN_ENTER);
|
||||
expect(drag).not.toContain("\x1b[2J");
|
||||
expect(drag).not.toContain("\x1b[3J");
|
||||
expect(visible(term).at(-1)).toBe("b14-y");
|
||||
} finally {
|
||||
tui.stop();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("resize repaints in place on sensitive terminal hosts", () => {
|
||||
|
||||
Reference in New Issue
Block a user