import "@/index.css" import * as Sentry from "@sentry/solid" import { I18nProvider } from "@opencode-ai/ui/context" import { DialogProvider } from "@opencode-ai/ui/context/dialog" import { FileComponentProvider } from "@opencode-ai/ui/context/file" import { MarkedProvider } from "@opencode-ai/ui/context/marked" import { File } from "@opencode-ai/ui/file" import { Font } from "@opencode-ai/ui/font" import { Splash } from "@opencode-ai/ui/logo" import { ThemeProvider } from "@opencode-ai/ui/theme/context" import { MetaProvider } from "@solidjs/meta" import { type BaseRouterProps, Navigate, Route, Router, useParams, useSearchParams } from "@solidjs/router" import { QueryClient, QueryClientProvider } from "@tanstack/solid-query" import { Effect } from "effect" import { type Component, createEffect, createMemo, createResource, createSignal, ErrorBoundary, For, type JSX, lazy, onCleanup, type ParentProps, Show, } from "solid-js" import { Dynamic } from "solid-js/web" import { CommandProvider } from "@/context/command" import { CommentsProvider } from "@/context/comments" import { FileProvider } from "@/context/file" import { ServerSDKProvider } from "@/context/server-sdk" import { ServerSyncProvider } from "@/context/server-sync" import { GlobalProvider } from "@/context/global" import { HighlightsProvider } from "@/context/highlights" import { LanguageProvider, type Locale, useLanguage } from "@/context/language" import { LayoutProvider } from "@/context/layout" import { ModelsProvider } from "@/context/models" import { NotificationProvider } from "@/context/notification" import { PermissionProvider } from "@/context/permission" import { PromptProvider } from "@/context/prompt" import { ServerConnection, ServerProvider, serverName, useServer } from "@/context/server" import { SettingsProvider, useSettings } from "@/context/settings" import { TerminalProvider } from "@/context/terminal" import { TabsProvider, useTabs, type DraftTab } from "@/context/tabs" import { SDKProvider, useSDK } from "@/context/sdk" import { WslServersProvider } from "@/wsl/context" import DirectoryLayout, { DirectoryDataProvider } from "@/pages/directory-layout" import Layout from "@/pages/layout" import { ErrorPage } from "./pages/error" import { useCheckServerHealth } from "./utils/server-health" const HomeRoute = lazy(() => import("@/pages/home")) const Session = lazy(() => import("@/pages/session")) const NewSession = lazy(() => import("@/pages/new-session")) const SessionRoute = Object.assign( () => { const settings = useSettings() const params = useParams() const [search] = useSearchParams<{ draftId?: string; prompt?: string }>() const sdk = useSDK() const server = useServer() const tabs = useTabs() // When the new layout is enabled, the legacy new-session route (/:dir/session with no id) // is replaced by a draft at /new-session?draftId=… createEffect(() => { if (!settings.general.newLayoutDesigns()) return if (params.id || search.draftId) return if (!tabs.ready() || !sdk.directory) return tabs.newDraft({ server: server.key, directory: sdk.directory }, search.prompt) }) return ( ) }, { preload: Session.preload }, ) function DraftRoute() { const [search] = useSearchParams<{ draftId?: string }>() const tabs = useTabs() return ( }> {(draftID) => } ) } function ResolvedDraftRoute(props: { draftID: string }) { const server = useServer() const tabs = useTabs() const draft = createMemo(() => tabs.store.find((tab): tab is DraftTab => tab.type === "draft" && tab.draftID === props.draftID), ) createEffect(() => { const current = draft() if (current && current.server !== server.key) server.setActive(current.server) }) // Key on the directory so retargeting the draft's project re-instantiates the // SDK/data providers for the new directory while keeping the same draft id. const directory = () => draft()?.directory return ( {(dir) => ( )} ) } function UiI18nBridge(props: ParentProps) { const language = useLanguage() return {props.children} } declare global { interface Window { __OPENCODE__?: { deepLinks?: string[] } api?: { setTitlebar?: (theme: { mode: "light" | "dark" }) => Promise exportDebugLogs?: () => Promise } } } function QueryProvider(props: ParentProps) { const client = new QueryClient({ defaultOptions: { queries: { refetchOnReconnect: false, refetchOnMount: false, refetchOnWindowFocus: false, }, }, }) return {props.children} } function BodyDesignClass() { const settings = useSettings() createEffect(() => { if (typeof document === "undefined") return const enabled = settings.general.newLayoutDesigns() document.body.classList.toggle("text-12-regular", !enabled) document.body.classList.toggle("font-(family-name:--font-family-text)", enabled) document.body.classList.toggle("text-[13px]", enabled) document.body.classList.toggle("font-[440]", enabled) }) return null } function AppShellProviders(props: ParentProps) { return ( {props.children} ) } function SessionProviders(props: ParentProps) { return ( {props.children} ) } // The draft page only renders the prompt composer, so it drops TerminalProvider. // FileProvider and CommentsProvider stay because PromptInput uses file search and comment context. function DraftProviders(props: ParentProps) { return ( {props.children} ) } function RouterRoot(props: ParentProps<{ appChildren?: JSX.Element }>) { return ( {/*}>*/} {props.appChildren} {props.children} {/**/} ) } export function AppBaseProviders(props: ParentProps<{ locale?: Locale }>) { return ( { void window.api?.setTitlebar?.({ mode }) }} > { Sentry.captureException(error) return }} > {props.children} ) } function ConnectionGate(props: ParentProps<{ disableHealthCheck?: boolean }>) { const server = useServer() const checkServerHealth = useCheckServerHealth() const [checkMode, setCheckMode] = createSignal<"blocking" | "background">("blocking") // performs repeated health check with a grace period for // non-http connections, otherwise fails instantly const [startupHealthCheck, healthCheckActions] = createResource(() => props.disableHealthCheck ? true : Effect.gen(function* () { if (!server.current) return true const { http, type } = server.current while (true) { const res = yield* Effect.promise(() => checkServerHealth(http)) if (res.healthy) return true if (checkMode() === "background" || type === "http") return false } }).pipe( Effect.timeoutOrElse({ duration: "10 seconds", orElse: () => Effect.succeed(false) }), Effect.ensuring(Effect.sync(() => setCheckMode("background"))), Effect.runPromise, ), ) const checking = createMemo( () => checkMode() === "blocking" && ["unresolved", "pending"].includes(startupHealthCheck.state), ) return ( } > { if (checkMode() === "background") void healthCheckActions.refetch() }} onServerSelected={(key) => { setCheckMode("blocking") server.setActive(key) void healthCheckActions.refetch() }} /> } > {props.children} ) } function ConnectionError(props: { onRetry?: () => void; onServerSelected?: (key: ServerConnection.Key) => void }) { const language = useLanguage() const server = useServer() const others = () => server.list.filter((s) => ServerConnection.key(s) !== server.key) const name = createMemo(() => server.name || server.key) const serverToken = "\u0000server\u0000" const unreachable = createMemo(() => language.t("app.server.unreachable", { server: serverToken }).split(serverToken)) const timer = setInterval(() => props.onRetry?.(), 1000) onCleanup(() => clearInterval(timer)) return ( {unreachable()[0]} {name()} {unreachable()[1]} {language.t("app.server.retrying")} 0}> {language.t("app.server.otherServers")} {(conn) => { const key = ServerConnection.key(conn) return ( props.onServerSelected?.(key)} > {serverName(conn)} ) }} ) } function ServerKey(props: ParentProps) { const server = useServer() return ( {props.children} ) } export function AppInterface(props: { children?: JSX.Element defaultServer: ServerConnection.Key canonicalLocalServer?: ServerConnection.Key servers?: Array router?: Component disableHealthCheck?: boolean }) { return ( ( {routerProps.children} )} > } /> ) }
{unreachable()[0]} {name()} {unreachable()[1]}
{language.t("app.server.retrying")}