Repository navigation
fix(tui): contain plugin dialogs that throw while rendering - #62110
Conversation
There was a problem hiding this comment.
Looks good. I reproduced #61101 from source with the issue's script: on the base, opening the plugin dialog brings up the "OpenCode crashed" screen. With this PR the TUI stays up, the repro-dialog crashed in dialog: … toast appears, the dialog closes and the prompt still takes input. The error in that script comes from an effect, not the render itself, so that case is covered too. The new test and the nearby plugin tests pass, and tsc -b is clean in packages/tui.
Public API is unchanged. PluginBoundary is now exported and takes an optional onCrash, but both are internal to the TUI. Plugins see no change, except that a crashing ctx.ui.dialog.show dialog now closes instead of crashing the app.
No concerns.
Related: #62051
Brings upstream's performance work (part rows anomalyco#58916, stale tool calls anomalyco#61421, skip decoding a completed assistant anomalyco#61159, OpenTUI 0.8.1 anomalyco#60913, stable keymap commands anomalyco#59294, history flash anomalyco#59236, plugin dialog containment anomalyco#62110) with the rest of dev, as one merge rather than cherry-picks so later syncs stay merges against a shared history. Conflicts and how they were resolved: - core/src/config/normalize.ts: upstream decodes every experimental field generically; took upstream (the fork's shell_background_after is a field of ConfigExperimental.Info, so the loop decodes it). - core/src/job.ts: kept the fork's undelivered/awaitDelivered beside upstream's backgroundJobs. - core/src/session/background.ts: kept the fork's awaitQuiet beside upstream's list. - core/src/tool.ts: kept the fork's isRuntime beside upstream's commit. - core/src/shell.ts: upstream's "File" entry type with the fork's rotated .out.1 cleanup. - client/src/promise/generated/types.ts: both, regenerated. - core/test/tool-shell.test.ts: the fork's handle-shaped tests, with upstream's withTempDir import for its new tests.


Issue for this PR
Closes #61101
Type of change
What does this PR do?
Problem: If a plugin dialog throws while it renders, the whole TUI goes down with the "OpenCode crashed" screen. That's what happens with
/supabasefrom opencode-supabase, which still uses v1 theme names (undefined is not an object (evaluating 'e.length')). Plugin routes and slots are already wrapped inPluginBoundary, but content passed toctx.ui.dialog.showwas rendered without it.Change:
ctx.ui.dialog.showcontent is now wrapped in the samePluginBoundary, usingwhere="dialog". A crash shows the usual error toast (<plugin> crashed in dialog: ...) and closes the dialog. Without the close, an empty dialog would stay open and keep focus away from the prompt. The plugin's theme names still need to be fixed in its own repo.How did you verify your code works?
devit hits the crash screen. On this branch it shows the error toast, the dialog closes, and the prompt still works (screenshots below).packages/tui/test/plugin-slot-output.test.tsxwith a fixture plugin whose dialog throws. It fails without the fix ("OpenCode crashed"), and also fails if the dialog isn't closed (focus stays stuck). With the fix it passes:bun test test/plugin-slot-output.test.tsx --rerun-each 10→ 20 pass.bun test test/plugin-select.test.tsx test/plugin-toast.test.tsx test/plugin-keymap.test.tsx→ 23 passbun typecheckinpackages/tui→ no errorsbun run checkat the repo root → 35/35 tasks successfulScreenshots / recordings
Before:
After:
Visual
Checklist
Added by the opencode bot (AI-generated).
Related: #62051