From 9b389d6006ff46a41be5437b98ccb3e0c156bbbc Mon Sep 17 00:00:00 2001 From: roboomp Date: Thu, 13 Aug 2026 14:45:03 +0000 Subject: [PATCH] fix(tui): preserved direct herdr clears Scoped direct Herdr multiplexer behavior to resize repaint selection so explicit transcript replacements continue to clear and replay scrollback. Added coverage for resize stability and resetDisplay ED3 behavior. --- packages/tui/src/tui.ts | 28 +++++++++++++++++----- packages/tui/test/issue-2088-repro.test.ts | 20 ++++++++++++++++ 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/packages/tui/src/tui.ts b/packages/tui/src/tui.ts index 3b2353912..55f91d547 100644 --- a/packages/tui/src/tui.ts +++ b/packages/tui/src/tui.ts @@ -413,9 +413,25 @@ function parseSizeValue(value: SizeValue | undefined, referenceSize: number): nu return undefined; } -/** Detect terminal multiplexers where scrollback clearing and height-change redraws are hostile. */ +/** + * Detect sessions where ED3 cannot safely rebuild scrollback. Direct HerdR + * panes support explicit clears; nested multiplexers remain unsafe because the + * inner tmux/screen/Zellij layer owns their history. + */ function isMultiplexerSession(): boolean { - return isInsideTerminalMultiplexer(); + if (!isInsideTerminalMultiplexer()) return false; + if (Bun.env.HERDR_ENV !== "1") return true; + const term = Bun.env.TERM?.toLowerCase() ?? ""; + return Boolean( + Bun.env.TMUX || + Bun.env.STY || + Bun.env.ZELLIJ || + Bun.env.CMUX_WORKSPACE_ID || + Bun.env.CMUX_SURFACE_ID || + Bun.env.CMUX_REMOTE_TRANSPORT || + term.startsWith("tmux") || + term.startsWith("screen"), + ); } /** @@ -439,12 +455,12 @@ function reportsSizeOnAltScreenToggle(): boolean { /** * Resize should repaint the visible window in place — no alternate-screen - * borrow, no ED3 scrollback rewrap — for multiplexer panes and for terminals - * that loop on alt-screen toggles. The tradeoff is identical to a multiplexer: - * scrollback above the window keeps its old wrap instead of being re-flowed. + * borrow, no ED3 scrollback rewrap — for multiplexer and direct HerdR panes, + * plus terminals that loop on alt-screen toggles. Direct HerdR remains a + * direct terminal for explicit transcript replacement and display reset. */ function resizeRepaintsInPlace(): boolean { - return isMultiplexerSession() || reportsSizeOnAltScreenToggle(); + return isMultiplexerSession() || Bun.env.HERDR_ENV === "1" || reportsSizeOnAltScreenToggle(); } /** diff --git a/packages/tui/test/issue-2088-repro.test.ts b/packages/tui/test/issue-2088-repro.test.ts index 1126c7ef1..3604ccfd3 100644 --- a/packages/tui/test/issue-2088-repro.test.ts +++ b/packages/tui/test/issue-2088-repro.test.ts @@ -2564,6 +2564,26 @@ describe("multiplexer detection gates ED3 on resize", () => { }); }); + it("preserves explicit scrollback clears in direct HerdR", async () => { + await withEnvPatch({ ...NO_MULTIPLEXER_ENV, TERM: "dumb", HERDR_ENV: "1" }, async () => { + const term = new VirtualTerminal(40, 10, 1000); + const tui = new TUI(term); + tui.addChild(new MutableLinesComponent(Array.from({ length: 20 }, (_value, index) => `line-${index}`))); + + try { + tui.start(); + await settle(term); + const writes = captureWrites(term); + tui.resetDisplay(); + await settle(term); + + expect(writes.join("")).toContain(ED3); + } finally { + tui.stop(); + } + }); + }); + it("keeps nested tmux inside HerdR on the ED3-unsafe path", async () => { await withEnvPatch({ ...TMUX_ENV, TERM: "tmux-256color", HERDR_ENV: "1" }, async () => { const term = new VirtualTerminal(40, 10, 1000);