fix(tui): import Solid control-flow builtins from solid-js, not @opentui/solid - #282
Conversation
10ac5a2 to
aeb6c19
Compare
There was a problem hiding this comment.
All reported issues were addressed
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 3 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
…tui/solid
The sidebar and every /ctx-* command disappear, with no crash and nothing on
stderr. Reproduces on clean master.
The Solid transform routes ALL emitted imports through the single `moduleName`
build-tui.ts passes it (`@opentui/solid`) — renderer helpers and control-flow
builtins alike. `@opentui/solid` re-exports only its own renderer helpers; `For`,
`Show`, `Index`, `Switch`, `Match`, `ErrorBoundary`, `Suspense` and
`SuspenseList` live in solid-js. The `<For>` at sidebar-content.tsx:997
therefore compiled to
import { For as _$For } from "opentui:runtime-module:%40opentui%2Fsolid";
and the host threw while loading the plugin:
Export named 'For' not found in module 'opentui:runtime-module:@opentui/solid'
OpenCode swallows errors thrown while loading a TUI plugin, so the plugin was
dropped with no diagnostic. This is latent on master today — the committed
tui-compiled bundle carries the bad import; it turns fatal once the host
resolves that virtual module strictly.
Fix: after the transform, redirect any specifier the OpenTUI runtime does not
export to the solid-js runtime module. The split is read from the real export
sets at build time rather than hardcoded, so it keeps working when OpenTUI
changes what it re-exports — a hardcoded list would relocate the bug to the next
version bump. A name exported by neither module now fails the build instead of
shipping a bundle that breaks the TUI silently.
Verified against the live host: @opentui/solid 0.4.5 exposes 48 names and none
of the eight builtins; solid-js exports all eight. Portal and Dynamic really do
belong to @opentui/solid and are left alone. All 24 runtime specifiers in the
rebuilt bundle resolve, build:tui is reproducible, and the sidebar returns.
The specifier list moved to src/shared/tui-runtime-specifiers.ts so the build
and the guard test read one source and cannot drift apart. It sits in
src/shared/ rather than src/tui/ because build-tui.ts copies every file under
src/tui/ into the shipped bundle, and this is build/test tooling.
Regression test (src/tui/tui-compiled-runtime-imports.test.ts) resolves every
compiled specifier against the actual export sets of all 8 rewritten modules,
fails on any runtime module id outside that set, and separately asserts no
solid-js-only builtin is imported from @opentui/solid. Red-checked: 2/3 fail
against a bundle built with the pre-fix script; an injected import from an
unknown runtime module fails with the module named; duplicating an entry in the
specifier list and stubbing a module to an empty export set each fail their own
assertion.
Gates: plugin 3534/0, typecheck 0 across 3 packages, lint clean,
check:tui-compiled PASS.
ffbc472 to
15371df
Compare
|
Merged in 3a38ed9 and shipped in v0.34.1 — thank you for an exceptional report and fix. We hit the same broken import in CI within the hour (a new sidebar slot used The merge carries your commit as authored; the compiled bundle was regenerated over the combined result (your routing + the now-explicit |
Symptom
The sidebar and every
/ctx-*command disappear. No crash, no stderr, nothing in~/.local/share/opencode/log. The server plugin is healthy the whole time — transforms run, RPC answers, the DB is fine. Only the TUI surface is gone.I hit this on a daily driver after an OpenCode upgrade. It reproduces on clean
master.Cause
scripts/build-tui.tspasses the Solid transformmoduleName: runtimeModuleId("@opentui/solid"). The transform routes every import it emits through that single module — renderer helpers and control-flow builtins alike.@opentui/solidonly re-exports its own renderer helpers. The builtins live insolid-js. Measured against@opentui/solid0.4.5:@opentui/solidsolid-jsForShowIndexSwitchMatchErrorBoundarySuspenseSuspenseListPortalDynamic@opentui/solidexposes 48 names and none of the eight control-flow builtins.PortalandDynamicgenuinely do belong to it.The
<For>atsrc/tui/slots/sidebar-content.tsx:997therefore compiles to:which throws at plugin load:
OpenCode swallows errors thrown while loading a TUI plugin, so the plugin is dropped with no diagnostic. That silence is why the symptom looks like "the sidebar broke" rather than "the plugin failed to load".
This is latent on
master— the committedsrc/tui-compiled/slots/sidebar-content.tsxcarries the bad import today. It only becomes fatal once the host resolves that virtual module strictly.Fix
Redirect, after the transform, any specifier the OpenTUI runtime does not export to the
solid-jsruntime module.The split is read from the real export sets at build time (
Object.keys(await import(...))) rather than hardcoded, so it keeps working when OpenTUI changes what it re-exports — a hardcoded list would just relocate the bug to the next version bump.A name exported by neither module now fails the build, instead of shipping a bundle that breaks the TUI silently.
The emitted imports are one specifier per statement, so the rewrite is a narrow line-level match rather than a general import parser.
Verification
master: committed bundle has the badForimport.opentui:runtime-module:*specifiers in the rebuilt bundle resolve against their target module's real exports.build:tuiis reproducible — rebuilding leaves no drift./ctx-*commands return.Gates on this branch (clean
masterbase): plugin 3532 pass / 0 fail,typecheck0 across all three packages,check:tui-compiledPASS.Regression test
src/tui/tui-compiled-runtime-imports.test.tsresolves every compiled specifier against the actual module export sets, and separately asserts no solid-js-only builtin is imported from@opentui/solid.Red-checked both ways: 2/2 fail with the pre-fix build script, 2/2 pass after.
Note on debugging this class
Worth knowing for anyone who hits a silent TUI drop: instrumenting the
tuifactory is not enough, because an empty log cannot distinguish "module never imported" from "factory never called".src/tui/entry.mjsruns before any TypeScript module loads and is the only place an import-time failure is observable. It also needs a synchronous writer —src/shared/logger.tsbuffers and flushes on process exit, which is exactly the path a hard load failure skips.Happy to split the test into its own commit or adjust the redirect's placement if you'd prefer it inside
compileTsx.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by cubic
Fixes the TUI by routing Solid control‑flow builtins to
solid-jsinstead of@opentui/solid. Restores the sidebar and/ctx-*commands, and prevents silent plugin drops.@opentui/solidandsolid-jsat build time and rewrites single‑specifieropentui:runtime-module:*imports tosolid-jswhen needed; fails the build if a name exists in neither module.<For>imports fromsolid-js.TUI_RUNTIME_SPECIFIERSandruntimeModuleId; guard tests validate all compiled imports, reject unknown runtime module IDs, ensure modules are non‑empty and the list has no duplicates, and assert control‑flow builtins never import from@opentui/solid.Written for commit 15371df. Summary will update on new commits.
Greptile Summary
The PR corrects generated TUI imports by routing Solid-only control-flow builtins to the host-provided
solid-jsruntime while retaining OpenTUI renderer helpers in@opentui/solid.Forimported fromsolid-js.Confidence Score: 5/5
The PR appears safe to merge.
No blocking failure remains.
Important Files Changed
Forbuiltin imports fromsolid-js.Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart LR A[TSX source] --> B[Solid transform] B --> C{Generated name exported by OpenTUI Solid?} C -->|Yes| D[Keep @opentui/solid runtime import] C -->|No| E{Exported by solid-js?} E -->|Yes| F[Redirect to solid-js runtime import] E -->|No| G[Fail TUI build] D --> H[Compiled TUI] F --> H H --> I[Regression test validates runtime exports]Reviews (5): Last reviewed commit: "fix(tui): import Solid control-flow buil..." | Re-trigger Greptile