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:
@@ -2,6 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { createParticle, createStreamParticle } from "@/lib/firestore-particles";
|
||||
import { CONTAINER_TYPES, type NetworkUsage, type ParticleType, type ParticlePropertiesMap } from "@/api/types";
|
||||
import { parseParticlePath, particlePath, ParticlePath, toFirestoreChildrenPath } from "@/lib/particle-path";
|
||||
import { QuotaExceededError } from "@/lib/errors";
|
||||
import {
|
||||
isUsageExhausted,
|
||||
networkUsageQueryKey,
|
||||
@@ -9,18 +10,8 @@ import {
|
||||
useInvalidateNetworkUsage,
|
||||
} from "./use-network-usage";
|
||||
|
||||
/**
|
||||
* Thrown when a free-plan network attempts to create a non-container particle
|
||||
* after hitting its daily message limit. Callers should surface an upgrade
|
||||
* prompt; compose UI should also disable triggers proactively via
|
||||
* `useNetworkUsage` rather than relying on this throw.
|
||||
*/
|
||||
export class QuotaExceededError extends Error {
|
||||
constructor(public readonly networkId: string) {
|
||||
super("Daily message limit reached");
|
||||
this.name = "QuotaExceededError";
|
||||
}
|
||||
}
|
||||
// Re-export for back-compat with existing `import { QuotaExceededError } from "@/hooks/use-create-particle"`.
|
||||
export { QuotaExceededError };
|
||||
|
||||
interface CreateParticleParams<T extends ParticleType = ParticleType> {
|
||||
// Path to which the new particle will be added as a child
|
||||
@@ -36,6 +27,9 @@ export function useCreateParticle() {
|
||||
const invalidateUsage = useInvalidateNetworkUsage();
|
||||
|
||||
return useMutation({
|
||||
// Compose UI renders a custom quota-exceeded toast + cancels the overlay.
|
||||
// Opt out of the global mutation error toast to avoid a double-toast.
|
||||
meta: { suppressToast: true },
|
||||
mutationFn: async (params: CreateParticleParams) => {
|
||||
const { networkId } = parseParticlePath(params.path);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user