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
+3
View File
File diff suppressed because one or more lines are too long
+4 -1
View File
@@ -2,7 +2,10 @@
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
interface Error {
message: string;
errorId?: string;
}
// interface Locals {}
// interface PageData {}
// interface PageState {}
+12
View File
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>%sveltekit.status% · SHD.build system fault</title>
<style>
:root{color-scheme:dark;font-family:Arial,Helvetica,sans-serif;background:#0a0c0d;color:#ecebe5}*{box-sizing:border-box}body{min-height:100vh;margin:0;display:grid;place-items:center;padding:24px;background:linear-gradient(135deg,#101314 0,#0a0c0d 55%)}body:before{content:"";position:fixed;inset:0;pointer-events:none;opacity:.08;background-image:linear-gradient(#ffffff14 1px,transparent 1px),linear-gradient(90deg,#ffffff14 1px,transparent 1px);background-size:48px 48px}main{position:relative;width:min(760px,100%);border:1px solid #3b4142;border-top:2px solid #df8069;background:#111415}header{height:64px;display:flex;align-items:center;justify-content:space-between;padding:0 20px;border-bottom:1px solid #303536}header div{display:grid;gap:4px}header span{color:#858b8d;font-size:8px;font-weight:900;letter-spacing:1.5px}header strong{color:#df8069;font-size:11px;letter-spacing:1px}header b{font-size:32px;color:#df8069}section{padding:clamp(28px,6vw,58px)}p{color:#858b8d;font:15px/1.6 Georgia,serif}small{display:block;color:#f6a821;font-size:8px;font-weight:900;letter-spacing:1.8px}h1{max-width:620px;margin:14px 0;font-size:clamp(34px,7vw,62px);line-height:.92;letter-spacing:-2px}a{display:inline-block;margin-top:20px;padding:14px 18px;background:#f6a821;color:#111;font-size:9px;font-weight:900;letter-spacing:1.2px;text-decoration:none}footer{display:flex;align-items:center;gap:8px;padding:12px 20px;border-top:1px solid #303536;color:#697174;font-size:7px;letter-spacing:1px}footer i{width:6px;height:6px;background:#df8069;box-shadow:0 0 7px #df8069}
</style>
</head>
<body><main><header><div><span>ISAC FALLBACK CHANNEL</span><strong>CORE SHELL INTERRUPTED</strong></div><b>%sveltekit.status%</b></header><section><small>SHD NETWORK RESPONSE</small><h1>SYSTEM INTERFACE UNAVAILABLE</h1><p>%sveltekit.error.message%</p><a href="/">RETURN TO SHD.BUILD →</a></section><footer><i></i> FAULT ISOLATED · CORE SERVICES PROTECTED</footer></main></body>
</html>
+11 -1
View File
@@ -1,4 +1,5 @@
import { json, type Handle } from '@sveltejs/kit';
import { json, type Handle, type HandleServerError } from '@sveltejs/kit';
import { randomUUID } from 'node:crypto';
import { getSessionUser } from '$lib/server/auth/session';
import { DATA_MODULES, queueDataProposal, shouldModerateDataMutation, type DataModule } from '$lib/server/data-moderation';
@@ -30,3 +31,12 @@ export const handle: Handle = async ({ event, resolve }) => {
}
return resolve(event);
};
export const handleError: HandleServerError = ({ error, event, status, message }) => {
const errorId = randomUUID().slice(0, 8).toUpperCase();
console.error(`[${errorId}] ${event.request.method} ${event.url.pathname} -> ${status}`, error);
return {
message: status >= 500 ? 'An unexpected fault interrupted this operation. No further action was applied.' : message,
errorId
};
};
+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>