diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 833c8dc8c3..b85f7fdcaa 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -117,11 +117,8 @@ export function tui(input: { headers?: RequestInit["headers"] events?: EventSource }) { - // promise to prevent immediate exit - // oxlint-disable-next-line no-async-promise-executor -- intentional: async executor used for sequential setup before resolve - return new Promise(async (resolve) => { + return new Promise((resolve, reject) => { const unguard = win32InstallCtrlCGuard() - win32DisableProcessedInput() const onExit = async () => { unguard?.() @@ -132,73 +129,77 @@ export function tui(input: { await TuiPluginRuntime.dispose() } - const renderer = await createCliRenderer(rendererConfig(input.config)) - const mode = (await renderer.waitForThemeMode(1000)) ?? "dark" + void (async () => { + win32DisableProcessedInput() - await render(() => { - return ( - ( - - )} - > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ) - }, renderer) + const renderer = await createCliRenderer(rendererConfig(input.config)) + const mode = (await renderer.waitForThemeMode(1000)) ?? "dark" + + await render(() => { + return ( + ( + + )} + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) + }, renderer) + })().catch(reject) }) } diff --git a/packages/opencode/test/cli/tui/app.test.ts b/packages/opencode/test/cli/tui/app.test.ts new file mode 100644 index 0000000000..b2f308021f --- /dev/null +++ b/packages/opencode/test/cli/tui/app.test.ts @@ -0,0 +1,29 @@ +import { afterEach, expect, mock, spyOn, test } from "bun:test" +import * as Core from "@opentui/core" + +afterEach(() => { + mock.restore() +}) + +test("tui rejects when renderer startup fails", async () => { + const err = new Error("setRawMode failed with errno: 9") + spyOn(Core, "createCliRenderer").mockRejectedValue(err) + + const { tui } = await import("../../../src/cli/cmd/tui/app") + const result = await Promise.race([ + tui({ + url: "http://opencode.internal", + config: {}, + args: { + continue: false, + fork: false, + }, + }).then( + () => "resolved", + (error) => error, + ), + Bun.sleep(100).then(() => "timeout"), + ]) + + expect(result).toBe(err) +})