Compare commits

...
Sign in to create a new pull request.

3 commits

Author SHA1 Message Date
Simon Klee
9973c7e739
simplify 2026-04-28 10:16:37 +02:00
Simon Klee
7141d76e1c
add test 2026-04-28 10:16:37 +02:00
Simon Klee
81e8cb81a5
fix(tui): startup rejection handling
Propagate renderer startup failures from the TUI promise instead of
leaving opencode hanging, and destroy any partially initialized
renderer before rejecting to restore terminal state.
2026-04-28 10:16:37 +02:00
2 changed files with 101 additions and 71 deletions

View file

@ -117,11 +117,8 @@ export function tui(input: {
headers?: RequestInit["headers"] headers?: RequestInit["headers"]
events?: EventSource events?: EventSource
}) { }) {
// promise to prevent immediate exit return new Promise<void>((resolve, reject) => {
// oxlint-disable-next-line no-async-promise-executor -- intentional: async executor used for sequential setup before resolve
return new Promise<void>(async (resolve) => {
const unguard = win32InstallCtrlCGuard() const unguard = win32InstallCtrlCGuard()
win32DisableProcessedInput()
const onExit = async () => { const onExit = async () => {
unguard?.() unguard?.()
@ -132,6 +129,9 @@ export function tui(input: {
await TuiPluginRuntime.dispose() await TuiPluginRuntime.dispose()
} }
void (async () => {
win32DisableProcessedInput()
const renderer = await createCliRenderer(rendererConfig(input.config)) const renderer = await createCliRenderer(rendererConfig(input.config))
const mode = (await renderer.waitForThemeMode(1000)) ?? "dark" const mode = (await renderer.waitForThemeMode(1000)) ?? "dark"
@ -199,6 +199,7 @@ export function tui(input: {
</ErrorBoundary> </ErrorBoundary>
) )
}, renderer) }, renderer)
})().catch(reject)
}) })
} }

View file

@ -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)
})