fix(frontend): readable API errors and no more silent action failures (#284, #286)

#286: request() now throws C2ApiError — message is FastAPI's `detail` or a
plain sentence for the status (401/403/404/429/5xx), with toString() so the
many String(e) call sites read cleanly. status/body stay on the error; the
Discord parsers rebuild the legacy "C2 API error N: body" text via
legacyErrorText(), so their invite/code parsing is unchanged.

#284: Resolve, Regenerate summary, alert-rule toggle/delete/load,
Acknowledge, trip deletes, system delete and Restart OP25 now surface
failures (ErrorBanner where the page has one, alert() where the page
already uses it). Rules tab no longer says "No alert rules configured"
when the load failed. Confirms added for Resolve (no reopen path exists),
rule delete and trip delete. Reopen deliberately not built — manual
resolve doesn't set resolved_at, so reopening is a lifecycle change.

Frontend unverified: no Node on the authoring box (#207).

Closes #286

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Logan Cusano
2026-10-25 21:38:13 -04:00
co-authored by Claude Opus 5.5
parent 6d667e81c9
commit f16f0a9f93
12 changed files with 134 additions and 21 deletions
+3 -1
View File
@@ -2,6 +2,8 @@
// Shapes returned by c2-core app/routers/discord_web.py. No token strings —
// the server never sends them to the browser.
import { legacyErrorText } from "@/lib/c2error";
export type StreamState =
| "pending"
| "connecting"
@@ -131,7 +133,7 @@ function reasonOf(b: Record<string, unknown>): string | null {
/** c2api's request() throws `C2 API error <status>: <body>`. Pull the
* detail, code and invite links back out of a JSON body. */
export function parseC2Error(err: unknown): ParsedC2Error {
const raw = err instanceof Error ? err.message : String(err);
const raw = legacyErrorText(err);
const m = raw.match(/^C2 API error (\d+): ([\s\S]*)$/);
const status = m ? Number(m[1]) : null;
const idx = raw.indexOf(": ");