Skip to content

feat(opencode): TUI sidebar plugin showing active Plannotator session URL - #1386

Open
expnn wants to merge 1 commit into
backnotprop:mainfrom
expnn:tui-sidebar-plugin
Open

feat(opencode): TUI sidebar plugin showing active Plannotator session URL#1386
expnn wants to merge 1 commit into
backnotprop:mainfrom
expnn:tui-sidebar-plugin

Conversation

@expnn

@expnn expnn commented Aug 25, 2026

Copy link
Copy Markdown

Summary

Adds a TUI plugin face to the OpenCode plugin that displays the active Plannotator session URL in the opencode sidebar.

Problem

When running Plannotator on a remote server (via SSH), the session URL is only shown as a stderr overlay that breaks the TUI. Users need a persistent, copyable URL in the sidebar.

image

Result

image

Solution

  • New TUI plugin (tui.tsx, exported via "./tui"): registers a sidebar_content slot that displays the URL. Uses only @opentui/solid's direct API (createElement, insertNode, setProp, insert).
  • Data channel: Server plugin writes the URL to ~/.plannotator/opencode-session-url (JSONL format: {"url":"...","sid":"<session_id>"}) on ready; TUI plugin polls every 2s and filters by session_id for multi-instance correctness.
  • Host resolution (TUI): getServerHostname(api.client, api.state) extracts the opencode server's hostname from api.client.client.getConfig().baseUrl, with fallbacks to OPENCODE_HOST env var and state.config.server.hostname.
  • Host resolution (toast): Uses ctx.serverUrl (the opencode server's actual listen URL) threaded through index.tsembedded.tscommands.tsresolveToastHost(serverUrl). Separate from the TUI's hostname resolution.
  • Stderr suppression: withStderrSuppressed(() => handleServerReady(...)) wraps handleReviewServerReady/handleAnnotateServerReady calls — preserves browser opening + ready-file, suppresses only the stderr overlay that corrupts layout of the TUI.
  • Toast retained via client.tui.showToast with "(URL also in sidebar)" hint.
  • Multi-instance: JSONL entries keyed by session_id; clearSessionUrl(url) removes only the matching URL's line (keeps other sessions intact).
  • Timer guard: Module-level activeTimer limits to one setInterval per process; cleared on sidebar re-mount.

Testing

  • bun test apps/opencode-plugin/ — 118 pass, 0 fail
  • dist/tui.js built with tsup, verified zero solid-js imports
  • Manually tested with opencode 1.18.21: sidebar shows bold "Plannotator" title + URL with correct hostname, toast appears with correct hostname, no stderr overlay, no freeze

Files

File Change
tui.tsx (new) TUI plugin: sidebar_content slot, getServerHostname (TUI-only), insert-based URL display, no solid-js
session-url.ts (new) JSONL registry: writeSessionUrl(url, sid), clearSessionUrl(url), readSessionUrl(sid) — multi-instance safe
tsup.tui.config.ts (new) Build config: esbuild-plugin-solid, external @opentui/solid + solid-js
tsconfig.json (new) Local type-checking (not yet in CI — pre-existing workspace type errors)
commands.ts withStderrSuppressed wrapper, resolveToastHost + showPlannotatorToast, sessionId hoisted, writeSessionUrl/clearSessionUrl hooks in 3 onReady callbacks
embedded.ts serverUrl?: URL in deps type
index.ts serverUrl: ctx.serverUrl threaded to deps
commands.test.ts PLANNOTATOR_DATA_DIR sandbox
package.json "./tui" export, deps (@opentui/solid, solid-js), devDeps (tsup, esbuild-plugin-solid), build script appends tsup
package-boundary.test.ts ./tui export assertion

@expnn
expnn force-pushed the tui-sidebar-plugin branch 9 times, most recently from 9865955 to 75fcf4f Compare August 25, 2026 07:49
… URL

- New TUI plugin face (apps/opencode-plugin/tui.tsx, exported via "./tui")
  registers a sidebar_content slot that displays the current Plannotator
  session URL in the opencode sidebar, matching the built-in section styling
  (bold title via <b> inside <text>, fg={theme.text}).
- Server plugin (commands.ts) writes the full URL to a session-url file
  (~/.plannotator/opencode-session-url) on ready and clears it on session
  end. The TUI plugin polls this file every 2s.
- TUI rendering uses ONLY @opentui/solid's direct API (createElement,
  insertNode, setProp, insert) — no solid-js imports, avoiding dual
  solid-js instance conflicts with the host runtime.
- Host resolution (shared via session-url.ts getServerHostname) mirrors
  @expnn/opencode-server-info: extracts the opencode server's hostname
  from client.client.getConfig().baseUrl; port comes from plannotator's
  URL. Both the TUI sidebar and the toast notification use http://{host}:{port}.
- Toast notification retained via client.tui.showToast; stderr "session
  ready" overlay suppressed by skipping handleServerReady in onReady.
- Build: tsup + esbuild-plugin-solid (universal generate, moduleName
  @opentui/solid), external only @opentui/solid + solid-js.
- Module-level timer guard (activeTimer) limits to one setInterval per
  process; cleared on sidebar re-mount to prevent accumulation.
@expnn
expnn force-pushed the tui-sidebar-plugin branch from 75fcf4f to 5a094a9 Compare August 25, 2026 09:44
@expnn
expnn marked this pull request as ready for review August 25, 2026 13:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant