/** * Error reporting surface. * * Maps a classified {@link ErrorKind} to a translated, actionable message * and either raises it as a toast (with a Retry action for retryable * kinds) or hands the message key back for a view to render inline. */ import { toast } from "../toast.svelte"; import type { TranslationKey } from "../i18n/index.svelte"; import { classifyError, isRetryable, type ErrorKind } from "./classify"; const MESSAGE_KEY: Record = { offline: "error.offline", network: "error.network", auth: "error.auth", forbidden: "error.forbidden", conflict: "error.conflict", notFound: "error.not_found", rateLimit: "error.rate_limit", server: "error.server", unknown: "error.unknown", }; /** The translated message key for an error, for inline rendering. */ export function errorMessageKey(err: unknown): TranslationKey { return MESSAGE_KEY[classifyError(err)]; } export interface ReportErrorOptions { /** * Retry callback. When supplied and the error kind is retryable, the * toast gains a Retry action and stays until dismissed; otherwise the * toast auto-dismisses. */ onRetry?: () => void; } /** * Classify `err`, raise a translated toast, and return the kind. A * retryable error with an `onRetry` handler shows a sticky toast with a * Retry action; everything else auto-dismisses. */ export function reportError( err: unknown, options: ReportErrorOptions = {}, ): ErrorKind { const kind = classifyError(err); const withRetry = options.onRetry !== undefined && isRetryable(kind); toast.show({ messageKey: MESSAGE_KEY[kind], actionLabelKey: withRetry ? "common.retry" : undefined, onAction: withRetry ? options.onRetry : undefined, durationMs: withRetry ? null : 8000, }); return kind; }