diff --git a/.claude/settings.local.json b/.claude/settings.local.json index aac6d9d..2ad86b6 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -5,7 +5,11 @@ "Bash(pnpm --version)", "Bash(git init *)", "Bash(git add *)", - "Bash(git commit *)" + "Bash(git commit *)", + "Bash(export PATH=\"$HOME/.nvm/versions/node/v22.22.2/bin:$HOME/.local/share/pnpm:$PATH\")", + "Bash(pnpm install *)", + "Bash(node scripts/migrate.js)", + "Bash(node scripts/seed.js)" ] } } diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a9189ef --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +# Copy to .env and fill in before running in production. +# In development, a fallback value is used automatically. + +BIFROST_SECRET=change-me-to-a-long-random-string-in-production diff --git a/DECISIONS.md b/DECISIONS.md new file mode 100644 index 0000000..94187b4 --- /dev/null +++ b/DECISIONS.md @@ -0,0 +1,105 @@ +# DECISIONS.md — Project Bifrost + +Decisions made during autonomous build. Each entry: what was chosen, why, and where it applies. + +--- + +## D-01 · Content collections in `src/content/` not root `content/` + +**Chose:** `src/content/updates/` and `src/content/meetings/` for Astro content collections. +**Why:** Astro 4 requires content collections to live inside `src/content/`. The root `content/` folder cannot be used for typed collections with Zod schemas. +**Applies to:** updates, meetings. +**Note:** `content/roadmap.md` stays at root and is read via `fs.readFileSync` since it's a single file, not a collection. + +--- + +## D-02 · `marked` added as a runtime dependency + +**Chose:** Added `marked ^12.0.0` for rendering user-contributed markdown. +**Why:** SPEC requires markdown-lite rendering (bold, italic, links, lists, code blocks) in contributions and replies. A homegrown parser risks XSS and correctness bugs. `marked` is tiny, well-maintained, and ships its own TypeScript types. +**Note:** HTML output is not sanitized (no DOMPurify). Acceptable for a private hub with 14 trusted users. Flag for v1.1 if scope expands. + +--- + +## D-03 · Sessions: 7-day, random 32-byte hex ID + +**Chose:** Sessions stored in SQLite. Cookie `bifrost_session` holds a 32-byte random hex string. Expiry 7 days. HttpOnly, SameSite=Lax. +**Why:** Simple, auditable. No JWTs — session validity is always checkable server-side. SPEC mandates HttpOnly + SameSite=Lax. + +--- + +## D-04 · Invite tokens: HMAC-signed, hash stored in DB + +**Chose:** Token format `${randomBase64url}.${hmac16chars}`. SHA-256 hash of the full token stored in `invites.token_hash`. HMAC key = `BIFROST_SECRET`. +**Why:** SPEC says "HMAC-signed, not JWTs". Storing the hash means a compromised DB doesn't reveal usable tokens. + +--- + +## D-05 · `BIFROST_SECRET` env var with dev fallback + +**Chose:** `process.env.BIFROST_SECRET ?? 'dev-secret-do-not-use-in-production'` +**Why:** Zero-config for local dev. Production must set the env var. A `.env.example` documents it. + +--- + +## D-06 · Calendar navigation via URL params, no JS + +**Chose:** `/calendar?y=2026&m=4` — month grid built server-side per request. +**Why:** Works without JavaScript. Simpler to reason about. JS keyboard nav is a v1.1 enhancement. + +--- + +## D-07 · Reactions use form POST (full reload) + +**Chose:** +1 reaction is a plain `