feat(electron): error handling foundation
Adds the infrastructure for a coherent client-side error story: - `lib/errors.ts`: canonical ApiError + QuotaExceededError, `toUserMessage` (friendly strings for ApiError/ZodError/network errors, strips Electron IPC message prefixes), `logError` (expected), `reportError` (unexpected). - `lib/query-client.ts`: QueryClient factory with sane retry defaults (no retry on 4xx except 408/429, 2 retries otherwise; 0 mutation retries), `QueryCache` onError logs + opts in via `meta.toastOnError`, and `MutationCache` onError toasts `toUserMessage(err)` by default with `meta.suppressToast` as the opt-out. - `components/app-error-boundary.tsx` + `error-fallback.tsx`: two boundaries (top-level outside the router, route-level inside) with a Card-based fallback offering 'Go home' + 'Try again'. Route boundary resets on pathname change and clears React Query error cache on retry. - `main/ipc-utils.ts` + main.ts migration: `safeHandle` wraps ipcMain.handle so main-process failures log with full stack and surface a sanitized message to the renderer. `link:fetch-metadata` keeps its null contract but now logs. - `useCreateParticle` opts out of the global toast (compose-overlay renders its own quota UX) so nothing double-toasts. Render crashes now have a recovery UI, every mutation gets a free error toast, and silent-catch cleanup + Sentry land in follow-up PRs.
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
import { ipcMain, type IpcMainInvokeEvent } from "electron";
|
||||
|
||||
type InvokeHandler = (
|
||||
event: IpcMainInvokeEvent,
|
||||
...args: unknown[]
|
||||
) => Promise<unknown> | unknown;
|
||||
|
||||
/**
|
||||
* Wraps `ipcMain.handle` so handler failures log the full stack in the main
|
||||
* process and re-throw a sanitized message to the renderer. Without this,
|
||||
* main-process exceptions silently reject the renderer promise with no trace.
|
||||
*/
|
||||
export function safeHandle(channel: string, handler: InvokeHandler): void {
|
||||
ipcMain.handle(channel, async (event, ...args) => {
|
||||
try {
|
||||
return await handler(event, ...(args as unknown[]));
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`[ipc:${channel}]`, err);
|
||||
const message = err instanceof Error ? err.message : "IPC error";
|
||||
throw new Error(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user