diff --git a/src/tui.tsx b/src/tui.tsx index bd06c26..25fa55b 100644 --- a/src/tui.tsx +++ b/src/tui.tsx @@ -1,6 +1,6 @@ /** @jsxImportSource @opentui/solid */ -import { createSignal, onMount, onCleanup } from "solid-js"; +import { createSignal, createEffect, onMount, onCleanup } from "solid-js"; import type { TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/tui"; import type { RGBA } from "@opentui/core"; import { BusTui } from "@four-bytes/opencode-plugin-lib/tui"; @@ -66,8 +66,10 @@ function BrainStatusBar(props: { centered?: boolean; api: TuiPluginApi; sessionI }; onMount(() => { - let bus: BusTui | null = null; + // bus is a signal so createEffect below can react when BusTui resolves. + const [bus, setBus] = createSignal(null); let unsub: (() => void) | null = null; + let sessionUnsub: (() => void) | null = null; let timer: ReturnType | null = null; let unmounted = false; let busConnected = false; @@ -75,7 +77,8 @@ function BrainStatusBar(props: { centered?: boolean; api: TuiPluginApi; sessionI onCleanup(() => { unmounted = true; unsub?.(); - bus?.close(); + sessionUnsub?.(); + bus()?.close(); if (timer) clearInterval(timer); }); @@ -83,11 +86,14 @@ function BrainStatusBar(props: { centered?: boolean; api: TuiPluginApi; sessionI BusTui.connect() .then((b) => { if (unmounted) { b.close(); return; } - bus = b; + setBus(b); busConnected = true; if (timer) { clearInterval(timer); timer = null; } - const channel = props.sessionId ? `brain/${props.sessionId}` : "brain/status"; - unsub = b.subscribe(channel, (envelope) => { + // Always subscribe to brain/status — server publishes here during ingest, + // before any chat message creates a session. Once sessionId is known, + // also subscribe to the per-session channel (server switches to it + // after first chat.message). Prevents missing pre-session status updates. + unsub = b.subscribe("brain/status", (envelope) => { handleStatus(envelope.payload as BrainStatusEvent); }); }) @@ -97,6 +103,19 @@ function BrainStatusBar(props: { centered?: boolean; api: TuiPluginApi; sessionI if (!timer) timer = setInterval(poll, 200); }); + // React to props.sessionId changes — sessionId is set later by the host + // (after first chat.message), so it is often undefined at mount time. + // Tracks bus() so the effect re-runs once BusTui.connect() resolves. + createEffect(() => { + const sid = props.sessionId; + const b = bus(); + sessionUnsub?.(); + if (!sid || !b) return; + sessionUnsub = b.subscribe(`brain/${sid}`, (envelope) => { + handleStatus(envelope.payload as BrainStatusEvent); + }); + }); + // HTTP fallback for when bus is unavailable (cross-process) let statusUrl = ""; const hash = createHash("md5").update(props.api.state.path.directory).digest("hex").slice(0, 12);