feat(tui): add developer debug bar (#38359)

This commit is contained in:
James Long 2026-07-22 14:35:28 -04:00 committed by GitHub
commit 8a36abd328
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 554 additions and 193 deletions

View file

@ -1,4 +1,4 @@
import { render, TimeToFirstDraw, useRenderer, useTerminalDimensions } from "@opentui/solid"
import { render, useRenderer, useTerminalDimensions } from "@opentui/solid"
import { registerOpencodeSpinner } from "./component/register-spinner"
import { Deferred, Effect } from "effect"
import { Service, type Endpoint } from "@opencode-ai/client/effect/service"
@ -36,6 +36,7 @@ import {
TuiPathsProvider,
TuiStartupProvider,
TuiTerminalEnvironmentProvider,
useTuiApp,
useTuiStartup,
type TuiApp,
} from "./context/runtime"
@ -47,8 +48,7 @@ import { EditorContextProvider } from "./context/editor"
import { useEvent } from "./context/event"
import { ClientProvider, useClient } from "./context/client"
import { StartupLoading } from "./component/startup-loading"
import { DevToolsSidebar } from "./component/devtools-sidebar"
import { DevTools } from "./devtools"
import { DevToolsBar } from "./component/devtools-bar"
import { Reconnecting } from "./component/reconnecting"
import { DataProvider, useData } from "./context/data"
import { LocationProvider, useLocation } from "./context/location"
@ -88,8 +88,6 @@ import { win32DisableProcessedInput, win32FlushInputBuffer } from "./terminal-wi
import { destroyRenderer } from "./util/renderer"
import { cliErrorMessage, errorFormat } from "./util/error"
const themePerformance = DevTools.register({ id: "theme-performance", title: "Theme performance" })
registerOpencodeSpinner()
const appGlobalBindingCommands = [
@ -257,12 +255,9 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
const pluginRuntime = createPluginRuntime()
yield* Effect.tryPromise(async () => {
const appStarted = performance.now()
// Prewarm palette before ThemeProvider mounts so `system` theme avoids a first-paint fallback flash.
void renderer.getPalette({ size: 16 }).catch(() => undefined)
const modeStarted = performance.now()
const mode = handoff?.mode ?? (await renderer.waitForThemeMode(1000)) ?? "dark"
themePerformance.set("Detect light/dark mode", `${(performance.now() - modeStarted).toFixed(2)} ms`)
if (renderer.isDestroyed) return
await render(() => {
@ -351,7 +346,6 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
<EditorContextProvider>
<PluginProvider packages={input.packages}>
<App
started={appStarted}
pair={
input.server.endpoint.auth
? input.server.endpoint.auth
@ -409,11 +403,12 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
})
})
function App(props: { pair?: DialogPairCredentials; started: number }) {
function App(props: { pair?: DialogPairCredentials }) {
const log = useLog({ component: "app" })
const app = useTuiApp()
const startup = useTuiStartup()
const config = useConfig()
const devtools = createMemo(() => config.data.debug?.devtools ?? false)
const devtools = createMemo(() => config.data.debug?.devtools ?? app.channel === "local")
const route = useRoute()
const dimensions = useTerminalDimensions()
const renderer = useRenderer()
@ -433,11 +428,6 @@ function App(props: { pair?: DialogPairCredentials; started: number }) {
const plugins = usePlugin()
const clipboard = useClipboard()
createEffect(() => {
if (!themeState.ready) return
themePerformance.set("Total", `${(performance.now() - props.started).toFixed(2)} ms`)
})
// Toast once when an MCP server enters a failed or needs-auth state so the user knows to act,
// without having to open the status panel. Tracking the last alerted status avoids re-toasting
// the same problem on every refresh while still re-alerting if the state changes.
@ -1110,9 +1100,6 @@ function App(props: { pair?: DialogPairCredentials; started: number }) {
: undefined
}
>
<Show when={config.data.debug?.timing}>
<TimeToFirstDraw />
</Show>
<box flexGrow={1} minHeight={0} flexDirection="row">
<box flexGrow={1} minWidth={0} flexDirection="column">
<Show when={plugins.ready()}>
@ -1141,10 +1128,10 @@ function App(props: { pair?: DialogPairCredentials; started: number }) {
<PluginSlot name="app" />
</Show>
</box>
<Show when={devtools()}>
<DevToolsSidebar />
</Show>
</box>
<Show when={devtools()}>
<DevToolsBar />
</Show>
<Show when={!startup.skipInitialLoading}>
<StartupLoading ready={plugins.ready} />
</Show>