(props: { error: Error; reset: () => void; mode?: "dark" | "light" })
| 8 | import { useExit } from "../context/exit" |
| 9 | |
| 10 | export function ErrorComponent(props: { error: Error; reset: () => void; mode?: "dark" | "light" }) { |
| 11 | const term = useTerminalDimensions() |
| 12 | const exit = useExit() |
| 13 | const clipboard = useClipboard() |
| 14 | const [copied, setCopied] = createSignal(false) |
| 15 | |
| 16 | // Safe fallback palette per mode (mirrors theme/assets/opencode.json) since the |
| 17 | // theme context may be the thing that crashed. |
| 18 | const isLight = props.mode === "light" |
| 19 | const colors = isLight |
| 20 | ? { |
| 21 | bg: "#ffffff", |
| 22 | element: "#f5f5f5", |
| 23 | borderSubtle: "#d4d4d4", |
| 24 | text: "#1a1a1a", |
| 25 | muted: "#8a8a8a", |
| 26 | primary: "#3b7dd8", |
| 27 | onPrimary: "#ffffff", |
| 28 | error: "#d1383d", |
| 29 | success: "#3d9a57", |
| 30 | } |
| 31 | : { |
| 32 | bg: "#0a0a0a", |
| 33 | element: "#1e1e1e", |
| 34 | borderSubtle: "#3c3c3c", |
| 35 | text: "#eeeeee", |
| 36 | muted: "#808080", |
| 37 | primary: "#fab283", |
| 38 | onPrimary: "#0a0a0a", |
| 39 | error: "#e06c75", |
| 40 | success: "#7fd88f", |
| 41 | } |
| 42 | |
| 43 | const message = props.error.message || "An unknown error occurred." |
| 44 | const stack = props.error.stack || "No stack trace available." |
| 45 | const issueURL = buildIssueURL(message, stack) |
| 46 | |
| 47 | const copyReport = () => { |
| 48 | void clipboard.write?.(issueURL.toString()).then(() => setCopied(true)) |
| 49 | } |
| 50 | |
| 51 | const actions = [ |
| 52 | { key: "c", label: () => (copied() ? "✓ Copied" : "Copy report"), copy: true, onUse: copyReport }, |
| 53 | { key: "r", label: () => "Restart", onUse: props.reset }, |
| 54 | { key: "q", label: () => "Quit", onUse: () => exit() }, |
| 55 | ] |
| 56 | const [selected, setSelected] = createSignal(0) |
| 57 | const move = (delta: number) => setSelected((prev) => (prev + delta + actions.length) % actions.length) |
| 58 | let scroll: ScrollBoxRenderable | undefined |
| 59 | |
| 60 | useKeyboard((evt) => { |
| 61 | if (evt.ctrl && evt.name === "c") return exit() |
| 62 | if (evt.name === "return") { |
| 63 | evt.preventDefault() |
| 64 | evt.stopPropagation() |
| 65 | return actions[selected()].onUse() |
| 66 | } |
| 67 | if (evt.name === "left") { |
nothing calls this directly
no test coverage detected