summaryrefslogtreecommitdiffhomepage
path: root/packages/desktop/src
diff options
context:
space:
mode:
authorAdam <[email protected]>2025-12-19 07:38:33 -0600
committerAdam <[email protected]>2025-12-19 07:38:38 -0600
commite1ad2a355c84422ef8c6a20f998bc71f2881713a (patch)
treeb37c2dc4ff16bf6d9fcfd83f35a2ff5aea37d5aa /packages/desktop/src
parent4f318f913e034ccd2fe4456461bf56b22a37eed9 (diff)
downloadopencode-e1ad2a355c84422ef8c6a20f998bc71f2881713a.tar.gz
opencode-e1ad2a355c84422ef8c6a20f998bc71f2881713a.zip
fix(desktop): error handling
Diffstat (limited to 'packages/desktop/src')
-rw-r--r--packages/desktop/src/app.tsx2
-rw-r--r--packages/desktop/src/context/global-sdk.tsx12
-rw-r--r--packages/desktop/src/context/global-sync.tsx5
-rw-r--r--packages/desktop/src/context/platform.tsx12
-rw-r--r--packages/desktop/src/context/sdk.tsx11
-rw-r--r--packages/desktop/src/entry.tsx3
-rw-r--r--packages/desktop/src/pages/error.tsx26
-rw-r--r--packages/desktop/src/pages/layout.tsx7
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",