diff options
| author | Adam <[email protected]> | 2025-12-19 07:38:33 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2025-12-19 07:38:38 -0600 |
| commit | e1ad2a355c84422ef8c6a20f998bc71f2881713a (patch) | |
| tree | b37c2dc4ff16bf6d9fcfd83f35a2ff5aea37d5aa /packages/desktop/src | |
| parent | 4f318f913e034ccd2fe4456461bf56b22a37eed9 (diff) | |
| download | opencode-e1ad2a355c84422ef8c6a20f998bc71f2881713a.tar.gz opencode-e1ad2a355c84422ef8c6a20f998bc71f2881713a.zip | |
fix(desktop): error handling
Diffstat (limited to 'packages/desktop/src')
| -rw-r--r-- | packages/desktop/src/app.tsx | 2 | ||||
| -rw-r--r-- | packages/desktop/src/context/global-sdk.tsx | 12 | ||||
| -rw-r--r-- | packages/desktop/src/context/global-sync.tsx | 5 | ||||
| -rw-r--r-- | packages/desktop/src/context/platform.tsx | 12 | ||||
| -rw-r--r-- | packages/desktop/src/context/sdk.tsx | 11 | ||||
| -rw-r--r-- | packages/desktop/src/entry.tsx | 3 | ||||
| -rw-r--r-- | packages/desktop/src/pages/error.tsx | 26 | ||||
| -rw-r--r-- | packages/desktop/src/pages/layout.tsx | 7 |
8 files changed, 54 insertions, 24 deletions
diff --git a/packages/desktop/src/app.tsx b/packages/desktop/src/app.tsx index 4edbfd8f9..2ed529bbc 100644 --- a/packages/desktop/src/app.tsx +++ b/packages/desktop/src/app.tsx @@ -41,7 +41,7 @@ export function App() { return ( <MetaProvider> <Font /> - <ErrorBoundary fallback={ErrorPage}> + <ErrorBoundary fallback={(error) => <ErrorPage error={error} />}> <DialogProvider> <MarkedProvider> <DiffComponentProvider component={Diff}> diff --git a/packages/desktop/src/context/global-sdk.tsx b/packages/desktop/src/context/global-sdk.tsx index 0d301d2f3..ac6697093 100644 --- a/packages/desktop/src/context/global-sdk.tsx +++ b/packages/desktop/src/context/global-sdk.tsx @@ -1,15 +1,17 @@ import { createOpencodeClient, type Event } from "@opencode-ai/sdk/v2/client" import { createSimpleContext } from "@opencode-ai/ui/context" import { createGlobalEmitter } from "@solid-primitives/event-bus" -import { onCleanup } from "solid-js" +import { usePlatform } from "./platform" export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleContext({ name: "GlobalSDK", init: (props: { url: string }) => { - const abort = new AbortController() + const platform = usePlatform() + const sdk = createOpencodeClient({ baseUrl: props.url, - signal: abort.signal, + signal: AbortSignal.timeout(1000 * 60 * 10), + fetch: platform.fetch, throwOnError: true, }) @@ -24,10 +26,6 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo } }) - onCleanup(() => { - abort.abort() - }) - return { url: props.url, client: sdk, event: emitter } }, }) diff --git a/packages/desktop/src/context/global-sync.tsx b/packages/desktop/src/context/global-sync.tsx index 6c6a02432..fffef5b5f 100644 --- a/packages/desktop/src/context/global-sync.tsx +++ b/packages/desktop/src/context/global-sync.tsx @@ -21,6 +21,8 @@ import { Binary } from "@opencode-ai/util/binary" import { useGlobalSDK } from "./global-sdk" import { ErrorPage, type InitError } from "../pages/error" import { createContext, useContext, onMount, type ParentProps, Switch, Match } from "solid-js" +import { showToast } from "@opencode-ai/ui/toast" +import { getFilename } from "@opencode-ai/util/path" type State = { ready: boolean @@ -118,7 +120,8 @@ function createGlobalSync() { }) .catch((err) => { console.error("Failed to load sessions", err) - setGlobalStore("error", err) + const project = getFilename(directory) + showToast({ title: `Failed to load sessions for ${project}`, description: err.message }) }) } diff --git a/packages/desktop/src/context/platform.tsx b/packages/desktop/src/context/platform.tsx index 2ac9f64d4..73d4c7f3e 100644 --- a/packages/desktop/src/context/platform.tsx +++ b/packages/desktop/src/context/platform.tsx @@ -5,6 +5,12 @@ export type Platform = { /** Platform discriminator */ platform: "web" | "tauri" + /** Open a URL in the default browser */ + openLink(url: string): void + + /** Restart the app */ + restart(): Promise<void> + /** Open native directory picker dialog (Tauri only) */ openDirectoryPickerDialog?(opts?: { title?: string; multiple?: boolean }): Promise<string | string[] | null> @@ -14,9 +20,6 @@ export type Platform = { /** Save file picker dialog (Tauri only) */ saveFilePickerDialog?(opts?: { title?: string; defaultPath?: string }): Promise<string | null> - /** Open a URL in the default browser */ - openLink(url: string): void - /** Storage mechanism, defaults to localStorage */ storage?: (name?: string) => SyncStorage | AsyncStorage @@ -25,6 +28,9 @@ export type Platform = { /** Install updates (Tauri only) */ update?(): Promise<void> + + /** Fetch override */ + fetch?: typeof fetch } export const { use: usePlatform, provider: PlatformProvider } = createSimpleContext({ diff --git a/packages/desktop/src/context/sdk.tsx b/packages/desktop/src/context/sdk.tsx index 0e556167b..4d1c797c9 100644 --- a/packages/desktop/src/context/sdk.tsx +++ b/packages/desktop/src/context/sdk.tsx @@ -1,17 +1,18 @@ import { createOpencodeClient, type Event } from "@opencode-ai/sdk/v2/client" import { createSimpleContext } from "@opencode-ai/ui/context" import { createGlobalEmitter } from "@solid-primitives/event-bus" -import { onCleanup } from "solid-js" import { useGlobalSDK } from "./global-sdk" +import { usePlatform } from "./platform" export const { use: useSDK, provider: SDKProvider } = createSimpleContext({ name: "SDK", init: (props: { directory: string }) => { + const platform = usePlatform() const globalSDK = useGlobalSDK() - const abort = new AbortController() const sdk = createOpencodeClient({ baseUrl: globalSDK.url, - signal: abort.signal, + signal: AbortSignal.timeout(1000 * 60 * 10), + fetch: platform.fetch, directory: props.directory, throwOnError: true, }) @@ -24,10 +25,6 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({ emitter.emit(event.type, event) }) - onCleanup(() => { - abort.abort() - }) - return { directory: props.directory, client: sdk, event: emitter, url: globalSDK.url } }, }) diff --git a/packages/desktop/src/entry.tsx b/packages/desktop/src/entry.tsx index eec6396e9..ecbce9815 100644 --- a/packages/desktop/src/entry.tsx +++ b/packages/desktop/src/entry.tsx @@ -15,6 +15,9 @@ const platform: Platform = { openLink(url: string) { window.open(url, "_blank") }, + restart: async () => { + window.location.reload() + }, } render( diff --git a/packages/desktop/src/pages/error.tsx b/packages/desktop/src/pages/error.tsx index 66fc81d98..352b9f3e8 100644 --- a/packages/desktop/src/pages/error.tsx +++ b/packages/desktop/src/pages/error.tsx @@ -1,5 +1,6 @@ import { TextField } from "@opencode-ai/ui/text-field" import { Logo } from "@opencode-ai/ui/logo" +import { Button } from "@opencode-ai/ui/button" import { Component } from "solid-js" import { usePlatform } from "@/context/platform" import { Icon } from "@opencode-ai/ui/icon" @@ -9,9 +10,17 @@ export type InitError = { data: Record<string, unknown> } -function formatError(error: InitError | undefined): string { - if (!error) return "Unknown error" +function isInitError(error: unknown): error is InitError { + return ( + typeof error === "object" && + error !== null && + "name" in error && + "data" in error && + typeof (error as InitError).data === "object" + ) +} +function formatInitError(error: InitError): string { const data = error.data switch (error.name) { case "MCPFailed": @@ -53,8 +62,16 @@ function formatError(error: InitError | undefined): string { } } +function formatError(error: unknown): string { + if (!error) return "Unknown error" + if (isInitError(error)) return formatInitError(error) + if (error instanceof Error) return `${error.name}: ${error.message}\n\n${error.stack}` + if (typeof error === "string") return error + return JSON.stringify(error, null, 2) +} + interface ErrorPageProps { - error: InitError | undefined + error: unknown } export const ErrorPage: Component<ErrorPageProps> = (props) => { @@ -76,6 +93,9 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => { label="Error Details" hideLabel /> + <Button size="large" onClick={platform.restart}> + Restart + </Button> <div class="flex items-center justify-center gap-1"> Please report this error to the OpenCode team <button diff --git a/packages/desktop/src/pages/layout.tsx b/packages/desktop/src/pages/layout.tsx index 5f4a5d797..626bceb22 100644 --- a/packages/desktop/src/pages/layout.tsx +++ b/packages/desktop/src/pages/layout.tsx @@ -69,7 +69,7 @@ export default function Layout(props: ParentProps) { const command = useCommand() onMount(async () => { - if (platform.checkUpdate && platform.update) { + if (platform.checkUpdate && platform.update && platform.restart) { const { updateAvailable, version } = await platform.checkUpdate() if (updateAvailable) { showToast({ @@ -80,7 +80,10 @@ export default function Layout(props: ParentProps) { actions: [ { label: "Install and restart", - onClick: () => platform!.update!(), + onClick: async () => { + await platform.update!() + await platform.restart!() + }, }, { label: "Not yet", |
