Compare commits

...
2 Commits
Author SHA1 Message Date
Simon Klee 1cf61593b5 ci: fix node build (#44349) 2026-08-23 11:26:26 +02:00
opencode-agent[bot] 708e4d8867 fix(app): refine mobile error page (#44353) 2026-08-23 09:23:10 +00:00
5 changed files with 56 additions and 11 deletions
+2 -1
View File
@@ -234,6 +234,7 @@ jobs:
host: macos-26
- target: windows-arm64
host: blacksmith-4vcpu-windows-2025
bun_install_flags: --cpu=*
- target: windows-x64
host: blacksmith-4vcpu-windows-2025
runs-on: ${{ matrix.settings.host }}
@@ -245,7 +246,7 @@ jobs:
- uses: ./.github/actions/setup-bun
with:
install-flags: --os=* --cpu=*
install-flags: ${{ matrix.settings.bun_install_flags }}
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
+3
View File
@@ -570,10 +570,13 @@ export const dict = {
"toast.update.action.notYet": "Not yet",
"error.page.title": "Something went wrong",
"error.page.title.status": "Server request failed",
"error.page.description": "An error occurred while loading the application.",
"error.page.description.status": "The server returned an HTTP {{status}} response.",
"error.page.description.localServerStartup": "An error occurred while starting the local server.",
"error.page.details.label": "Error Details",
"error.page.action.restart": "Restart",
"error.page.action.reload": "Reload",
"error.page.action.report": "Report Error",
"error.page.action.reported": "Error Reported",
"error.page.action.exportLogs": "Export Logs",
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test"
import { errorDescriptionKey } from "./description"
import { errorDescriptionKey, errorStatus } from "./description"
describe("error description", () => {
test("describes local server startup errors", () => {
@@ -15,3 +15,19 @@ describe("error description", () => {
)
})
})
describe("error status", () => {
test("finds status codes in an error cause", () => {
expect(errorStatus(new Error("UnexpectedStatus", { cause: { status: 502 } }))).toBe(502)
})
test("finds status codes in structured error data", () => {
expect(errorStatus({ name: "APIError", data: { statusCode: 401 } })).toBe(401)
})
test("ignores invalid and circular status values", () => {
const error: { status: number; cause?: unknown } = { status: 99 }
error.cause = error
expect(errorStatus(error)).toBeUndefined()
})
})
@@ -9,3 +9,21 @@ export function errorDescriptionKey(error: unknown) {
}
return "error.page.description" as const
}
export function errorStatus(error: unknown) {
const seen = new Set<object>()
const visit = (value: unknown): number | undefined => {
if (typeof value !== "object" || value === null || seen.has(value)) return
seen.add(value)
const item = value as Record<string, unknown>
for (const key of ["status", "statusCode"] as const) {
const status = item[key]
if (typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599) return status
}
return visit(item.cause) ?? visit(item.data)
}
return visit(error)
}
+16 -9
View File
@@ -7,7 +7,7 @@ import { createStore } from "solid-js/store"
import { usePlatform } from "@/runtime/platform/platform"
import { useLanguage } from "@/runtime/i18n/language"
import { Icon } from "@opencode-ai/ui/icon"
import { errorDescriptionKey } from "./description"
import { errorDescriptionKey, errorStatus } from "./description"
export type InitError = {
name: string
@@ -223,6 +223,7 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => {
const platform = usePlatform()
const language = useLanguage()
const formattedError = () => formatError(props.error, language.t)
const status = () => errorStatus(props.error)
let recordedFatalError: Promise<void> | undefined
const [store, setStore] = createStore({
actionError: undefined as string | undefined,
@@ -277,14 +278,20 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => {
return (
<div
class="relative flex-1 h-screen w-screen min-h-0 flex flex-col items-center justify-center font-sans"
class="relative flex-1 h-screen w-screen min-h-0 overflow-y-auto flex flex-col items-center justify-start sm:justify-center p-4 sm:p-8 font-sans"
data-tauri-drag-region
>
<div class="w-2/3 max-w-3xl flex flex-col items-center justify-center gap-8">
<Logo class="w-58.5 opacity-12 shrink-0" />
<div class="w-full max-w-3xl flex flex-col items-center justify-center gap-6 sm:gap-8 my-auto">
<Logo class="w-48 sm:w-58.5 opacity-12 shrink-0" />
<div class="flex flex-col items-center gap-2 text-center">
<h1 class="text-lg font-medium text-text-strong">{language.t("error.page.title")}</h1>
<p class="text-sm text-text-weak">{language.t(errorDescriptionKey(props.error))}</p>
<h1 class="text-lg font-medium text-text-strong">
{language.t(status() ? "error.page.title.status" : "error.page.title")}
</h1>
<p class="text-sm text-text-weak">
{status()
? language.t("error.page.description.status", { status: status()! })
: language.t(errorDescriptionKey(props.error))}
</p>
</div>
<TextField
value={formattedError()}
@@ -297,7 +304,7 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => {
/>
<div class="flex flex-row items-center justify-center gap-3 flex-wrap max-w-64">
<Button size="large" onClick={platform.restart}>
{language.t("error.page.action.restart")}
{language.t(platform.platform === "web" ? "error.page.action.reload" : "error.page.action.restart")}
</Button>
<Show when={platform.platform === "desktop" && platform.exportDebugLogs}>
<Button size="large" variant="ghost" onClick={exportDebugLogs}>
@@ -348,8 +355,8 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => {
<Show when={store.actionError}>
{(message) => <p class="text-xs text-text-danger-base text-center max-w-2xl">{message()}</p>}
</Show>
<div class="flex flex-col items-center gap-2">
<div class="flex items-center justify-center gap-1">
<div class="flex flex-col items-center gap-2 text-xs text-center">
<div class="flex flex-wrap items-center justify-center gap-1">
{language.t("error.page.report.prefix")}
<button
type="button"