Add web UI with Smokey's crow as the stand-in mascot
- ui/: Vite + plain TypeScript, builds into ./web for `crows serve` - CrowView mirrors CrowModel in RIVE_CONTRACT.md (pose, trust, reactions) so the .riv can replace the SVG later - Event handling follows the contract's mapping table; server text is only ever inserted as text - Prank: toast plus a send-button cooldown countdown - art/crow.svg: source art for Rive import Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UQnSw4RRWp8uqpj3nsnnXF
This commit is contained in:
12 files changed
+1796
No files matched your search
@@ -0,0 +1,45 @@
|
||||
// Thin client for the HTTP API in EVENTS.md.
|
||||
|
||||
export type PostListResult =
|
||||
| { ok: true; listId: string }
|
||||
| { ok: false; pranked: true; message: string; cooldownS: number }
|
||||
| { ok: false; pranked: false; message: string };
|
||||
|
||||
export async function postList(text: string, requester: string): Promise<PostListResult> {
|
||||
const res = await fetch("/api/lists", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ text, requester }),
|
||||
});
|
||||
const body = await res.json().catch(() => ({}));
|
||||
if (res.ok) return { ok: true, listId: String(body.list_id) };
|
||||
if (res.status === 429) return { ok: false, pranked: true, message: String(body.message ?? "Caw."), cooldownS: Number(body.cooldown_s ?? 30) };
|
||||
return { ok: false, pranked: false, message: String(body.message ?? `The flock said no (${res.status}).`) };
|
||||
}
|
||||
|
||||
export async function postVerdict(itemId: string, target: "triage" | "output", approve: boolean): Promise<void> {
|
||||
const res = await fetch("/api/verdicts", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ item_id: itemId, target, approve }),
|
||||
});
|
||||
if (!res.ok) throw new Error(res.status === 404 ? "That question already has an answer." : `Verdict failed (${res.status}).`);
|
||||
}
|
||||
|
||||
export type TrustLevels = Map<string, number>; // "Name|task_type" -> level
|
||||
|
||||
export async function getTrust(): Promise<TrustLevels> {
|
||||
const out: TrustLevels = new Map();
|
||||
try {
|
||||
const res = await fetch("/api/trust");
|
||||
const body = await res.json();
|
||||
for (const [key, rec] of Object.entries<{ level?: number }>(body.crows ?? {})) out.set(key, rec.level ?? 0);
|
||||
} catch {
|
||||
// Badges start at zero if the ledger can't be read.
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export function listEvents(listId: string): EventSource {
|
||||
return new EventSource(`/api/events?list_id=${encodeURIComponent(listId)}`);
|
||||
}
|
||||
Reference in new issue
Block a user