bcff17a718
AtomHttpApi.Service derives the typed client straight from the shared RomManagerApi contract — no hand-mirrored types, no react-query. Queries carry reactivity keys; mutations pass them per-call (constants exported beside each mutation). liveStatusAtom merges the kit sseAtom feed over GET /api/status; statusEventsAtom folds frames into the activity feed. useConfigDraft keeps a raw-config working copy (defaults never baked in) with dirty tracking and toast-reporting save. Mock mode is a full in-memory RomManagerApi as an HttpClient layer plus a scenario ticker (healthy/firstRun/syncing/errors), registered through a fixture-free seam so production chunks stay clean — mocks/http.ts is only imported behind import.meta.env.DEV and by Storybook. Scenario and mode atoms are keepAlive: the transport reads them outside the reactive graph and registry-seeded values must survive pages that never subscribe them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
19 lines
623 B
TypeScript
19 lines
623 B
TypeScript
// One place to turn a typed API error (or any transport failure) into a sentence.
|
|
export const errorText = (error: unknown): string => {
|
|
if (typeof error === "object" && error !== null) {
|
|
const e = error as {
|
|
_tag?: string;
|
|
issues?: string;
|
|
message?: string;
|
|
reason?: unknown;
|
|
};
|
|
if (e._tag === "ConfigInvalid" && typeof e.issues === "string") {
|
|
return e.issues;
|
|
}
|
|
if (e._tag === "SyncInProgress") return "A sync pass is already running";
|
|
if (typeof e.message === "string" && e.message.length > 0) return e.message;
|
|
if (typeof e._tag === "string") return e._tag;
|
|
}
|
|
return String(error);
|
|
};
|