From 1c1b13a3a9631a64353bdb5f5e0f5132da781e2e Mon Sep 17 00:00:00 2001 From: Kail Tian Date: Fri, 28 Aug 2026 17:31:16 +0800 Subject: [PATCH] fix(tui): preserve cursor position on Windows exit --- tui/src/app.ts | 3 ++- tui/src/host.test.ts | 26 +++++++++++++++++++++++++- tui/src/host.ts | 13 +++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/tui/src/app.ts b/tui/src/app.ts index 71d18fe30..df224a99e 100644 --- a/tui/src/app.ts +++ b/tui/src/app.ts @@ -94,7 +94,7 @@ import { type FooterMode, type FooterHintTheme, } from "./footer-hints" -import { createTuiHost, type TuiHost } from "./host" +import { configureOpenTuiEnvironment, createTuiHost, type TuiHost } from "./host" interface AppOptions { wsUrl?: string @@ -820,6 +820,7 @@ export class NanobotTui { } static async create(options: AppOptions): Promise { + configureOpenTuiEnvironment() const host = createTuiHost() const renderer = await createCliRenderer({ targetFps: 30, diff --git a/tui/src/host.test.ts b/tui/src/host.test.ts index 0ae9183f1..bff1cefd7 100644 --- a/tui/src/host.test.ts +++ b/tui/src/host.test.ts @@ -1,6 +1,9 @@ import { describe, expect, test } from "bun:test" -import { createTuiHost } from "./host" +import { + configureOpenTuiEnvironment, + createTuiHost, +} from "./host" async function settle(): Promise { await Bun.sleep(0) @@ -8,6 +11,27 @@ async function settle(): Promise { } describe("TUI host integration", () => { + test("disables the explicit-width probe on Windows", () => { + const environment: Record = {} + + configureOpenTuiEnvironment(environment, "win32") + + expect(environment.OPENTUI_FORCE_EXPLICIT_WIDTH).toBe("false") + }) + + test("preserves explicit probe choices and leaves other platforms unchanged", () => { + const overridden = { + OPENTUI_FORCE_EXPLICIT_WIDTH: "true", + } + const nonWindows: Record = {} + + configureOpenTuiEnvironment(overridden, "win32") + configureOpenTuiEnvironment(nonWindows, "linux") + + expect(overridden.OPENTUI_FORCE_EXPLICIT_WIDTH).toBe("true") + expect(nonWindows.OPENTUI_FORCE_EXPLICIT_WIDTH).toBeUndefined() + }) + test("standalone terminals remain a no-op", async () => { const commands: string[][] = [] const host = createTuiHost({}, async (command) => { commands.push([...command]) }) diff --git a/tui/src/host.ts b/tui/src/host.ts index 95c03d770..470156555 100644 --- a/tui/src/host.ts +++ b/tui/src/host.ts @@ -8,6 +8,19 @@ type CommandRunner = (command: readonly string[]) => Promise const METADATA_SOURCE = "nanobot:tui:metadata" +export function configureOpenTuiEnvironment( + environment: Environment = process.env, + platform = process.platform, +): void { + if (platform !== "win32") return + + // OpenTUI probes OSC 66 support on the main screen before its renderer is + // active. Some Windows terminal hosts do not restore the cursor around that + // probe, so shutdown resumes in terminal history instead of below the TUI. + // Keep an explicit user choice, but use the safe default on Windows. + environment.OPENTUI_FORCE_EXPLICIT_WIDTH ??= "false" +} + class StandaloneHost implements TuiHost { reportTitle(): void {} release(): void {}