From 11c76c6d9ae2284baf569fecacda15201fe56939 Mon Sep 17 00:00:00 2001 From: Sebastian Herrlinger Date: Wed, 4 Mar 2026 21:15:45 +0100 Subject: [PATCH] untangle --- packages/opencode/src/cli/cmd/tui/app.tsx | 46 +++---------------- packages/opencode/src/cli/cmd/tui/plugin.ts | 46 ++++++++++++++++++- .../opencode/src/cli/cmd/tui/routes/home.tsx | 10 ++-- .../src/cli/cmd/tui/routes/session/index.tsx | 8 ++-- 4 files changed, 58 insertions(+), 52 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 6d951482c2..149db14b60 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -1,12 +1,4 @@ -import { - createSlot, - createSolidSlotRegistry, - render, - useKeyboard, - useRenderer, - useTerminalDimensions, - type SolidPlugin, -} from "@opentui/solid" +import { render, useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid" import { Clipboard } from "@tui/util/clipboard" import { Selection } from "@tui/util/selection" import { createCliRenderer, MouseButton, TextAttributes, type CliRendererConfig } from "@opentui/core" @@ -49,9 +41,7 @@ import { writeHeapSnapshot } from "v8" import { PromptRefProvider, usePromptRef } from "./context/prompt" import { TuiConfigProvider } from "./context/tui-config" import { TuiConfig } from "@/config/tui" -import type { TuiSlotContext, TuiSlotMap, TuiSlots } from "@opencode-ai/plugin/tui" - -type TuiSlot = (props: { name: K } & TuiSlotMap[K]) => unknown +import { TuiPlugin } from "./plugin" async function getTerminalBackgroundColor(): Promise<"dark" | "light"> { // can't set raw mode if not a TTY @@ -160,29 +150,7 @@ export function tui(input: { } const renderer = await createCliRenderer(rendererConfig(input.config)) - const registry = createSolidSlotRegistry( - renderer, - {}, - { - onPluginError(event) { - console.error("[tui.slot] plugin error", { - plugin: event.pluginId, - slot: event.slot, - phase: event.phase, - source: event.source, - message: event.error.message, - }) - }, - }, - ) - const Slot = createSlot(registry) - const slot: TuiSlot = (props) => Slot(props) - const slots: TuiSlots = { - register(plugin) { - console.error("[tui.slot] register", plugin.id) - return registry.register(plugin as SolidPlugin) - }, - } + const slots = TuiPlugin.slots(renderer) await render(() => { return ( @@ -214,7 +182,7 @@ export function tui(input: { - + @@ -238,7 +206,7 @@ export function tui(input: { }) } -function App(props: { slot: TuiSlot }) { +function App() { const route = useRoute() const dimensions = useTerminalDimensions() const renderer = useRenderer() @@ -805,10 +773,10 @@ function App(props: { slot: TuiSlot }) { > - + - + diff --git a/packages/opencode/src/cli/cmd/tui/plugin.ts b/packages/opencode/src/cli/cmd/tui/plugin.ts index aa9af59126..b3000a686f 100644 --- a/packages/opencode/src/cli/cmd/tui/plugin.ts +++ b/packages/opencode/src/cli/cmd/tui/plugin.ts @@ -2,8 +2,13 @@ import { type TuiPlugin as TuiPluginFn, type TuiPluginInput, type TuiPluginModule, + type TuiSlotContext, + type TuiSlotMap, type TuiSlotPlugin, + type TuiSlots, } from "@opencode-ai/plugin/tui" +import { createSlot, createSolidSlotRegistry, type SolidPlugin } from "@opentui/solid" +import type { CliRenderer } from "@opentui/core" import type { JSX } from "solid-js" import "@opentui/solid/preload" @@ -14,9 +19,46 @@ import { BunProc } from "@/bun" import { Instance } from "@/project/instance" import { registerThemes } from "./context/theme" +type Slot = (props: { name: K } & TuiSlotMap[K]) => unknown + +function empty(_props: { name: K } & TuiSlotMap[K]) { + return null +} + export namespace TuiPlugin { const log = Log.create({ service: "tui.plugin" }) let loaded: Promise | undefined + let view: Slot = empty + + export const Slot: Slot = (props) => view(props) + + export function slots(renderer: CliRenderer): TuiSlots { + const reg = createSolidSlotRegistry( + renderer, + {}, + { + onPluginError(event) { + console.error("[tui.slot] plugin error", { + plugin: event.pluginId, + slot: event.slot, + phase: event.phase, + source: event.source, + message: event.error.message, + }) + }, + }, + ) + + const slot = createSlot(reg) + view = (props) => slot(props) + + return { + register(plugin) { + console.error("[tui.slot] register", plugin.id) + return reg.register(plugin as SolidPlugin) + }, + } + } export async function init(input: TuiPluginInput) { if (loaded) return loaded @@ -32,7 +74,7 @@ export namespace TuiPlugin { return BunProc.install(pkg, version) } - function slot(entry: unknown) { + function pick(entry: unknown) { if (!entry || typeof entry !== "object") return if ("id" in entry && typeof entry.id === "string" && "slots" in entry && typeof entry.slots === "object") { return entry as TuiSlotPlugin @@ -88,7 +130,7 @@ export namespace TuiPlugin { registerThemes(pluginEntry.themes as Record) } - const plugin = slot(pluginEntry) + const plugin = pick(pluginEntry) if (plugin) { input.slots.register(plugin) } diff --git a/packages/opencode/src/cli/cmd/tui/routes/home.tsx b/packages/opencode/src/cli/cmd/tui/routes/home.tsx index 7daccda371..eab6c8cf1d 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/home.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/home.tsx @@ -15,14 +15,12 @@ import { Installation } from "@/installation" import { useKV } from "../context/kv" import { useCommandDialog } from "../component/dialog-command" import { useLocal } from "../context/local" -import type { TuiSlotMap } from "@opencode-ai/plugin/tui" - -type Slot = (props: { name: K } & TuiSlotMap[K]) => unknown +import { TuiPlugin } from "../plugin" // TODO: what is the best way to do this? let once = false -export function Home(props: { slot: Slot }) { +export function Home() { const sync = useSync() const kv = useKV() const { theme } = useTheme() @@ -75,7 +73,7 @@ export function Home(props: { slot: Slot }) { - {props.slot({ name: "home_hint" }) as never} + {TuiPlugin.Slot({ name: "home_hint" }) as never} ) @@ -154,7 +152,7 @@ export function Home(props: { slot: Slot }) { - {props.slot({ name: "home_footer" }) as never} + {TuiPlugin.Slot({ name: "home_footer" }) as never} {Installation.VERSION} diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index a461e53894..df1280a2e5 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -80,12 +80,10 @@ import { DialogExportOptions } from "../../ui/dialog-export-options" import { formatTranscript } from "../../util/transcript" import { UI } from "@/cli/ui.ts" import { useTuiConfig } from "../../context/tui-config" -import type { TuiSlotMap } from "@opencode-ai/plugin/tui" +import { TuiPlugin } from "../../plugin" addDefaultParsers(parsers.parsers) -type Slot = (props: { name: "session_footer"; session_id: TuiSlotMap["session_footer"]["session_id"] }) => unknown - class CustomSpeedScroll implements ScrollAcceleration { constructor(private speed: number) {} @@ -115,7 +113,7 @@ function use() { return ctx } -export function Session(props: { slot: Slot }) { +export function Session() { const route = useRouteData("session") const { navigate } = useRoute() const sync = useSync() @@ -1180,7 +1178,7 @@ export function Session(props: { slot: Slot }) { }} sessionID={route.sessionID} /> - {props.slot({ name: "session_footer", session_id: route.sessionID }) as never} + {TuiPlugin.Slot({ name: "session_footer", session_id: route.sessionID }) as never}