Skip to content

TUI plugin JSX slot output silently dropped when plugin is loaded from node_modules path #32996

Closed as not planned

Description

@mormubis

Bug

External TUI plugins installed via github: or npm specs render nothing from their JSX slot functions, even though the slot function is called correctly. The same plugin loaded via file:// pointing to a path outside node_modules works correctly.

Minimal Reproduction

Repo: https://github.com/mormubis/opencode-tui-repro

The plugin registers a simple app slot:

/** @jsxImportSource @opentui/solid */
api.slots.register({
  order: 100,
  slots: {
    app(ctx) {
      const theme = ctx.theme.current
      return <text fg={theme.text}>REPRO VISIBLE</text>
    },
  },
})

Broken — add to tui.json:

{ "plugin": ["github:mormubis/opencode-tui-repro"] }
  • /tmp/repro.log shows: module imported, tui() called, app slot called ✓
  • "REPRO VISIBLE" does not appear in the TUI ✗

Working — add to tui.json:

{ "plugin": ["file:///path/to/cloned/opencode-tui-repro"] }
  • "REPRO VISIBLE" appears in the TUI ✓

Root Cause

When OpenCode installs a TUI plugin via npm/GitHub, arborist places it at:

~/.cache/opencode/packages/github:user/repo/
  node_modules/
    opencode-tui-repro/    ← path contains node_modules
      tui/index.tsx

In @opentui/core/runtime-plugin.js, the Bun plugin that rewrites runtime module imports uses isNodeModulesPath() to branch:

  • Non-node_modules path (file:// local): installRewriteLoader() is called directly — JSX uses the host's @opentui/solid instance and renders correctly.
  • node_modules path (npm/GitHub install): collectNodeModulesRuntimeRewritePaths() is called instead — the JSX nodes produced are silently ignored by the host renderer.

The slot function fires (confirmed via appendFileSync logs), the JSX is returned, but the rendered content is invisible. No error is thrown.

Environment

  • OpenCode: 1.17.8
  • @opentui/solid: 0.4.0
  • @opentui/core: 0.4.0
  • macOS arm64

Workaround

Use file:// pointing to a local clone of the plugin (outside node_modules).

Activity

  1. nomaed commented on Jul 18, 2026

    @nomaed

    Same thing happens for me.

    With local path in tui.json plugin works fine:

    {
      "plugin": ["file:///Users/<user>/git/opencode-tui-usage/index.tsx"]
    }

    With npm plugin name in tui.json it doesn't load properly and even when it loads, it doesn't work correctly.

    {
      "plugin": ["opencode-tui-usage@v0.4.1"]
    }

    And it's not that it doesn't work at all, it's just kind of broken in all sort of ways:

    • The plugin has a side panel which doesn't appear at all
    • The Plugins dialog shows the plugin as active
    • If I deactivate it and activate it back, then the panel suddenly appears
    • Even after it appears, it doesn't work correctly: It isn't sized correctly to fill the width of the panel and TUI elements that are supposed to react to mouse clicks aren't working at all
  2. JLegends commented on Sep 17, 2026

    @JLegends

    Same root cause, with two extra data points that might help narrow it.

    I hit this writing a sidebar plugin, and saw two failure modes that appear to depend on whether the package ships its own copies of solid-js / @opentui/*.

    Package with no local copies (only optional peers declared, none installed): the plugin does not run at all. A top-level side effect in the module never fires and the plugin never appears in the plugins dialog. The only variable I could isolate was JSX:

    • .tsx with TypeScript syntax only, no JSX, in the installed package referenced through exports["./tui"] → loads, top-level marker written.
    • the same file with <box>/<text> added → never executes, no marker, no error, plugin absent from the dialog.

    Package with its own copies: matches #48883, it loads, may paint once, then never updates because the signals belong to a second Solid runtime.

    Workaround that works today, verified end to end from an npm install: ship precompiled plain JS rather than TSX. jsx/jsxs from @opentui/solid/jsx-runtime, getters on every prop that reads reactive state, and no local copies of solid-js / @opentui/*. That renders and updates live. Two packages doing this now: https://github.com/JLegends/opencode-subagent-usage and @pistonex/opencode-go-usage 0.1.3 from #48883.

    One request: please pair this with #41574. None of these paths emits a diagnostic at any log level, so a broken plugin and an unloaded plugin look identical from the outside. A single "plugin X failed: unresolved jsx runtime" line would turn a multi-hour hunt into a one-minute fix.

  3. thdxr commented on Oct 11, 2026

    @thdxr
    Member

    Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.

    — from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions