- POST /api/fenjaops/invites on server.js (requireAuth+requireAdmin).
Ignores any is_admin field in the body — always stores 0. Records
the acting admin's email in invited_by so the audit trail shows
who added whom (CLI adds still record "cli").
- admin/index.html: new "Invite a new user" form panel at the top
(email + optional first name).
- admin/admin.js: wires the form submit to the POST, shows inline
success/error, refreshes the tables on success.
- admin/admin.css: form styling matching the existing paper/ink
palette; mobile stacks.
- Docs: CLAUDE.md, PROJECT.md, OPERATIONS.md, CHECKLIST.md, README.md
all updated. New non-negotiable property in PROJECT.md: no web
endpoint can set is_admin=1 or delete an invite — promotion +
removal stay on bin/invite.js. New CHECKLIST.md section H2 covers
the page's gating, the invite form, and an escalation-path audit.
Admin promotion and invite deletion remain CLI-only so a compromised
admin session cannot escalate or evict.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- new is_admin column on invites (migration 4) with DEFAULT 0
- requireAdmin middleware returns 404 for non-admins so the route's
existence isn't leaked; path obscured as /fenjaops (not /admin)
- admin/ dir lives outside public/ and protected/; only reachable via
the explicit gated mount + /api/fenjaops/{invites,joins} endpoints
- bin/invite.js gains `admin add|remove|list` subcommands
- OPERATIONS.md + CLAUDE.md + PROJECT.md document the hidden URL
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Six scroll-bound scenes (hero, architecture stack, words fly-in,
aurora arc, treasure-map, join CTA) now live inside page-overview,
above the existing 23-headline timeline. The Europe map stays as a
static background that fades with scroll.
- protected/index.html: rewrote #page-overview only; timeline and
archive sections unchanged. Site-2 palette re-mapped to site-1
Nordic Editorial tokens, Fraunces to Newsreader, tokens scoped
to #page-overview.
- protected/timeline.js: dot-nav boots window.__bifrost.init()
on first Overview activation. Added .js class on documentElement.
- protected/bifrost.js (new): Lenis + ScrollTrigger wired to the
overview's internal scroller via scrollerProxy; drives Europe
map opacity on scroll.
- protected/vendor/{lenis,gsap,scrolltrigger}.min.js (new):
extracted from site-2's inlined vendor blobs; CSP-compliant.
- protected/fenja/illustrations/{community,council,pilot}.svg
(new): treasure-map stop images.
No changes to src/, server.js, deploy/, or public/. CSP stays
strict (script-src 'self'); zero inline scripts added. Auth gate
and session model untouched.
Points to existing PROJECT/OPERATIONS/INSTALL/CHECKLIST docs rather than
duplicating them, and surfaces the non-obvious bits: middleware ordering,
auth flow, and the invariants that must not be broken silently.