Skip to content

fix(tui): truncate fractional mtimes in fresh plugin specifiers - #41891

Merged
kitlangton merged 1 commit into
v2from
fresh-specifier-mtime
Aug 12, 2026
Merged

fix(tui): truncate fractional mtimes in fresh plugin specifiers#41891
kitlangton merged 1 commit into
v2from
fresh-specifier-mtime

Conversation

@kitlangton

@kitlangton kitlangton commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

What

External TUI plugins that use JSX or import solid-js / @opentui/solid fail to load in the compiled opencode2 binary unless a compatible node_modules happens to sit above the plugin file. The cause is one character: freshSpecifier appends raw stat.mtimeMs to the import specifier, and on APFS that value is almost always fractional — plugin.tsx?mtime=1786494961337.0317. The extra dot in the query makes Bun's compiled binaries skip runtime plugin hooks (the solid transform and the opentui:runtime-module:* resolvers) for that import.

Before / After

Before: the TUI discovers ~/.config/opencode/plugins/tui/pr-indicator.tsx and imports it as pr-indicator.tsx?mtime=1786494961337.0317. OpenTUI's runtime plugin support never sees the import, so the JSX pragma falls through to Bun's native transpiler, which emits import "@opentui/solid/jsx-dev-runtime". That specifier resolves via ordinary node_modules walking from the plugin's own directory:

  • global config dir (no node_modules): hard failure — toast Plugin: Cannot find module '@opentui/solid/jsx-dev-runtime', plugin never loads
  • project dir inside a repo with @opentui/solid installed: loads a second solid instance, so the plugin renders once but host-store reactivity silently breaks

After: the specifier is pr-indicator.tsx?mtime=1786494961337. Runtime plugin hooks fire, JSX and solid imports rewrite to the host's embedded runtime modules, and the same plugin loads and stays reactive from any discovery directory.

Verified against a minimal compiled repro (ensureRuntimePluginSupport() + dynamic import) built with the production build options: the fractional query fails deterministically, the truncated query loads, on both plain and minify + splitting + solid-plugin builds.

How

  • packages/tui/src/plugin/discovery.ts: freshSpecifier truncates the mtime to whole milliseconds before appending it, in both the Bun and Node branches, with a comment pinning why. Cache-bust granularity coarsens from sub-millisecond to 1ms, matching the core plugin supervisor (Date#getTime()); the watcher's 100ms reconcile debounce makes a same-millisecond staleness miss unreachable in practice.
  • packages/tui/test/plugin-discovery.test.ts: regression test asserting a fractional mtimeMs produces a dot-free query.

Scope

Testing

  • bun test test/plugin-discovery.test.ts — new regression test passes.
  • bun test in packages/tui — 661 pass / 0 fail.
  • bun typecheck in packages/tui — clean after rebasing onto v2 with fix(core): restore bundler resolution for source-imported deps #41885 (an earlier v2 HEAD breakage made the first CI run fail; unrelated to this change).
  • End-to-end: compiled repro binary loads a real-world JSX plugin (~/.config/opencode/plugins/tui/pr-indicator.tsx) with the truncated specifier and fails with the fractional one.
  • Independently reviewed pre-merge: version-equality semantics in the plugin registry, 1ms granularity risk, Node-branch URL safety, Windows test behavior, and blast radius (the only other ?mtime= builder, core's plugin supervisor, already interpolates integers).
sequenceDiagram
    participant R as TUI reconcile
    participant B as Bun module loader
    participant P as runtime plugin hooks
    R->>B: import plugin.tsx?mtime=...337.0317
    Note over B,P: before: hooks skipped (dot in query)
    B-->>R: Cannot find module '@opentui/solid/jsx-dev-runtime'
    R->>B: import plugin.tsx?mtime=...337
    B->>P: onLoad / onResolve fire
    P-->>B: rewrite to opentui:runtime-module:*
    B-->>R: plugin loaded against host solid
Loading

@kitlangton
kitlangton force-pushed the fresh-specifier-mtime branch from ae17370 to 74f6e95 Compare August 12, 2026 01:05
@kitlangton
kitlangton enabled auto-merge (squash) August 12, 2026 01:09
@kitlangton
kitlangton merged commit 452335a into v2 Aug 12, 2026
16 of 18 checks passed
@kitlangton
kitlangton deleted the fresh-specifier-mtime branch August 12, 2026 01:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant