Nests: team rooms with core crows and borrowing

- Server: nests API, persistence, core-first picking, borrowed flag
  (built by the backend agent against EVENTS.md; tests pass with -race)
- UI: nests in the sidebar, one feed per nest, header shows the core
  team, New nest dialog, borrowed crows labeled, per-nest waiting count
- start.command: one-click local server in controlled mode (mock crows,
  localhost only, state in .crows/)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UQnSw4RRWp8uqpj3nsnnXF
This commit is contained in:
SmokeyandClaude Opus 5.5 committed 2026-10-27 13:45:04 -06:00
1 parent e9f20349a1
commit 2d85cab716
14 files changed
+782 -62

No files matched your search

+34 -2
View File
@@ -5,11 +5,11 @@ export type PostListResult =
| { ok: false; pranked: true; message: string; cooldownS: number }
| { ok: false; pranked: false; message: string };
export async function postList(text: string, requester: string): Promise<PostListResult> {
export async function postList(text: string, requester: string, nestId: string): Promise<PostListResult> {
const res = await fetch("/api/lists", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text, requester }),
body: JSON.stringify({ text, requester, nest_id: nestId }),
});
const body = await res.json().catch(() => ({}));
if (res.ok) return { ok: true, listId: String(body.list_id) };
@@ -43,3 +43,35 @@ export async function getTrust(): Promise<TrustLevels> {
export function listEvents(listId: string): EventSource {
return new EventSource(`/api/events?list_id=${encodeURIComponent(listId)}`);
}
// ---------- nests ----------
export type Nest = { nestId: string; name: string; crows: string[] };
export type Nests = { roster: string[]; nests: Nest[] };
const DEFAULT_ROSTER = ["Hugin", "Munin", "Corvina", "Soot", "Pepper", "Inky", "Edgar", "Jet"];
export async function getNests(): Promise<Nests> {
try {
const res = await fetch("/api/nests");
if (!res.ok) throw new Error(String(res.status));
const body = await res.json();
return {
roster: (body.roster ?? DEFAULT_ROSTER).map(String),
nests: (body.nests ?? []).map((n: any) => ({ nestId: String(n.nest_id), name: String(n.name), crows: (n.crows ?? []).map(String) })),
};
} catch {
// A server without nests still has the default room.
return { roster: DEFAULT_ROSTER, nests: [{ nestId: "todo", name: "todo", crows: [] }] };
}
}
export async function createNest(name: string, crows: string[]): Promise<Nest> {
const res = await fetch("/api/nests", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, crows }),
});
const body = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(String(body.message ?? `Couldn't build that nest (${res.status}).`));
return { nestId: String(body.nest_id), name: String(body.name), crows: (body.crows ?? []).map(String) };
}