Add SHD themed error handling

This commit is contained in:
JB
2026-07-25 18:52:39 -04:00
parent 92cda8a576
commit e0bf3b05ab
6 changed files with 97 additions and 2 deletions
+49
View File
@@ -0,0 +1,49 @@
<script lang="ts">
import { page } from '$app/state';
type ErrorPresentation = { code: string; title: string; detail: string; action: 'home' | 'signin' | 'retry' };
const presentations: Record<number, ErrorPresentation> = {
400: { code: 'REQUEST REJECTED', title: 'INVALID TRANSMISSION', detail: 'The submitted request could not be processed. Review the supplied information and try again.', action: 'home' },
401: { code: 'AUTHENTICATION REQUIRED', title: 'IDENTITY NOT VERIFIED', detail: 'This operation requires an authenticated SHD.build agent.', action: 'signin' },
403: { code: 'ACCESS DENIED', title: 'CLEARANCE INSUFFICIENT', detail: 'Your current role does not authorize access to this location or operation.', action: 'home' },
404: { code: 'SIGNAL LOST', title: 'LOCATION NOT FOUND', detail: 'ISAC could not resolve the requested location. It may have moved, been archived, or never existed.', action: 'home' },
405: { code: 'METHOD REJECTED', title: 'PROTOCOL NOT SUPPORTED', detail: 'This location does not support the attempted operation.', action: 'home' },
409: { code: 'DATA CONFLICT', title: 'STATE HAS CHANGED', detail: 'The requested change conflicts with newer or existing data. Reload the source and review it before trying again.', action: 'retry' },
413: { code: 'PAYLOAD REJECTED', title: 'TRANSMISSION TOO LARGE', detail: 'The submitted payload exceeds the accepted size. Reduce the file or content size and try again.', action: 'home' },
429: { code: 'RATE LIMITED', title: 'CHANNEL SATURATED', detail: 'Too many requests reached this node in a short period. Wait briefly before trying again.', action: 'retry' },
500: { code: 'SYSTEM FAULT', title: 'ISAC PROCESS INTERRUPTED', detail: 'An unexpected fault interrupted this operation. No further action was applied.', action: 'retry' },
502: { code: 'UPSTREAM FAULT', title: 'SERVICE LINK FAILED', detail: 'A required service returned an invalid response. Try the operation again shortly.', action: 'retry' },
503: { code: 'SERVICE OFFLINE', title: 'NODE TEMPORARILY UNAVAILABLE', detail: 'This service is temporarily unavailable or not yet configured.', action: 'retry' },
504: { code: 'LINK TIMEOUT', title: 'SERVICE RESPONSE EXPIRED', detail: 'A required service did not respond in time. Try the operation again shortly.', action: 'retry' }
};
const status = $derived(page.status || 500);
const presentation = $derived(presentations[status] ?? { code: 'REQUEST FAILED', title: 'OPERATION INTERRUPTED', detail: 'ISAC could not complete the requested operation.', action: status >= 500 ? 'retry' : 'home' });
const suppliedMessage = $derived(page.error?.message?.trim() ?? '');
const safeMessage = $derived(suppliedMessage && suppliedMessage !== 'Internal Error' ? suppliedMessage : presentation.detail);
const errorId = $derived(page.error && 'errorId' in page.error ? String(page.error.errorId ?? '') : '');
</script>
<svelte:head><title>{status} · {presentation.title} — SHD.build</title></svelte:head>
<main class="error-page">
<section class="error-console" class:server-fault={status >= 500} aria-labelledby="error-title">
<header><div><span>ISAC EXCEPTION REPORT</span><strong>{presentation.code}</strong></div><b>{status}</b></header>
<div class="error-signal" aria-hidden="true"><i></i><span>{status}</span><i></i></div>
<div class="error-copy"><p class="eyebrow"><span></span> SHD NETWORK RESPONSE</p><h1 id="error-title">{presentation.title}</h1><p>{safeMessage}</p></div>
<dl>
<div><dt>STATUS</dt><dd>HTTP {status}</dd></div>
<div><dt>REQUEST PATH</dt><dd>{page.url.pathname}</dd></div>
<div><dt>NODE</dt><dd>SHD.BUILD / ISAC</dd></div>
{#if errorId}<div><dt>REFERENCE</dt><dd>{errorId}</dd></div>{/if}
</dl>
<footer>
<div><i></i><span>{status >= 500 ? 'FAULT LOGGED · CORE SERVICES PROTECTED' : 'REQUEST TERMINATED · NO FURTHER ACTION'}</span></div>
<nav aria-label="Error recovery">
<button class="secondary" type="button" onclick={() => history.length > 1 ? history.back() : location.assign('/')}>GO BACK</button>
{#if presentation.action === 'signin'}<a class="primary" href="/sign-in">SIGN IN →</a>
{:else if presentation.action === 'retry'}<button class="primary" type="button" onclick={() => location.reload()}>RETRY REQUEST →</button>
{:else}<a class="primary" href="/">RETURN HOME →</a>{/if}
</nav>
</footer>
</section>
</main>