Adds proposals + proposal_votes + policies + incidents + incident_updates
tables with full RLS, async voting flow, dismissal-appeal pathway,
versioned policies with provenance, and an anonymous-incident token mode.
Adds per-space configurable areas, financial visibility settings, member
self-profile + COI disclosure, and a mission statement that renders in
the app footer.
Reorganises lib/actions.ts into per-domain files (tasks, projects,
members, payments, contacts, knowledge-base, secrets, area-leads,
settings, imports, proposals, incidents, policies, areas) plus a barrel.
Adds lib/permissions.ts and lib/auth-helpers.ts so action bodies stop
duplicating the user-to-member-to-role check. Wires every action that has
a Zod schema; introduces a flexibleDateTime preprocessor so HTML date
pickers no longer hit "Invalid datetime".
Hardens RLS: drops NOT NULL on space_members.user_id for offline members,
adds a privilege-escalation trigger that blocks self-edits to role / tier
/ status / approved / has_card_access / space_id, closes four
cross-tenant holes in the governance kernel, and adds a partial unique
index so two admins cannot concurrently activate two drafts of the same
policy slug.
Ships every deployment artifact: Dockerfile, docker-compose,
.do/app.yaml, .env.example, /api/health, plus a managed-Supabase guide
(docs/DEPLOYMENT.md), an end-to-end self-hosted Droplet guide
(docs/DEPLOY_DO_SELFHOSTED.md), and a Supabase-CLI local-dev quickstart
(docs/LOCAL_DEV.md). Replaces middleware.ts with proxy.ts for Next.js 16.
Adds scripts/schema.sql as the canonical idempotent full deploy plus
fourteen numbered incremental migrations.
Adds scripts/seed.sql so a fresh local DB renders something instead of
empty pages: a demo space, five members across all roles, a v1 code of
conduct, three proposals in different states with seven votes already
cast, an open incident, a project, three tasks, a KB entry, and matching
activity-log rows.
Replaces v0-generated test scaffolding with 266 real tests across
__tests__/{actions,auth-helpers,components,governance,permissions,
security,utils,validations}.test.ts. Adds react-markdown + remark-gfm
for proposal / incident / policy body rendering. Deletes four dead v0
fix-* scripts, the unused components/app-shell.tsx, and the duplicate
app/auth/login/page.tsx.
Routes (31 total): /api/health, /api/paypal/sync, /auth/callback, /,
/login, /signup, /signup/confirm, /dashboard, /tasks, /projects,
/members, /payments, /financials, /contacts, /ops, /ops/[id], /ops/new,
/proposals, /proposals/[id], /proposals/new, /incidents, /incidents/[id],
/incidents/new, /policies, /policies/[slug], /policies/new, /profile,
/recruitment, /settings, /comms, /import.
Full pass-by-pass history is in docs/HANDOFF.md; the roadmap is in
docs/GOVERNANCE_FEATURES.md.
6.6 KiB
Local development quickstart
Fresh-clone to running app in under five minutes. Uses the Supabase CLI to spin up a complete local stack (Postgres + Auth + PostgREST + Realtime + Storage + Studio) in Docker. No Supabase account needed.
Prerequisites
- macOS, Linux, or WSL2 on Windows.
- Docker (running).
docker infoshould print a server section. - Homebrew on macOS, or follow the Supabase CLI install docs for your OS.
- Node 20+ and pnpm 9+. nvm works fine.
1. Install the Supabase CLI
brew install supabase/tap/supabase
supabase --version # 1.x or 2.x
2. Clone and install
git clone <repo-url>
cd hackerspace-management
pnpm install
3. Initialize and start the local Supabase stack
supabase init # writes supabase/config.toml; safe if it already exists
supabase start
First run pulls a few GB of images and takes 1–3 minutes. Subsequent starts are seconds.
When it finishes, the CLI prints something like:
API URL: http://127.0.0.1:54321
GraphQL URL: http://127.0.0.1:54321/graphql/v1
S3 Storage URL: http://127.0.0.1:54321/storage/v1/s3
DB URL: postgresql://postgres:[email protected]:54322/postgres
Studio URL: http://127.0.0.1:54323
Inbucket URL: http://127.0.0.1:54324
JWT secret: super-secret-jwt-token-with-at-least-32-characters-long
anon key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
service_role key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Save the anon key and service_role key. You can re-print them anytime with supabase status.
The DB is at postgresql://postgres:[email protected]:54322/postgres. Studio (Supabase dashboard, locally) is at http://127.0.0.1:54323. The Inbucket email catcher is at http://127.0.0.1:54324 — every email the app would send shows up there.
4. Apply the schema and the seed
# Schema (canonical, covers every migration through 019)
psql "postgresql://postgres:[email protected]:54322/postgres" -f scripts/schema.sql
# Optional but recommended for local: load a demo space with members, tasks,
# proposals, an incident, a policy, and activity-log entries.
psql "postgresql://postgres:[email protected]:54322/postgres" -f scripts/seed.sql
You can verify with Studio: open http://127.0.0.1:54323, switch to the public schema, and see 18 tables including proposals, incidents, policies.
5. Generate TypeScript types from the live local schema
supabase gen types typescript --local > types/database.ts
This clears the masked TS errors that next.config.mjs: typescript.ignoreBuildErrors: true was hiding. After this you can drop that ignoreBuildErrors flag and any future schema drift surfaces in CI.
6. Write .env.local
cp .env.example .env.local
Edit .env.local:
NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321
NEXT_PUBLIC_SUPABASE_ANON_KEY=<paste anon key from step 3>
SUPABASE_SERVICE_ROLE_KEY=<paste service_role key from step 3>
NEXT_PUBLIC_APP_URL=http://localhost:3000
NODE_ENV=development
7. Run the dev server
pnpm dev
Open http://localhost:3000. Health check: curl http://localhost:3000/api/health returns {"status":"ok"}.
8. Sign up as a real user and join the demo space
- Visit
/signup. - Pick Join a Space and enter invite code:
DEMO-2026-TEST. - Fill in your email / password.
- Local Supabase is configured to skip email confirmation by default, so signup takes you straight to
/dashboard. - The dashboard now shows the seeded activity. Visit
/proposals,/incidents,/policies,/financialsto see the full feature surface.
If you want admin permissions for the demo: connect to the DB (or via Studio) and run:
UPDATE public.space_members
SET role = 'admin'
WHERE email = '[email protected]';
Common dev tasks
Wipe and re-seed
supabase db reset # drops & re-runs the database
psql "postgresql://postgres:[email protected]:54322/postgres" -f scripts/schema.sql
psql "postgresql://postgres:[email protected]:54322/postgres" -f scripts/seed.sql
Apply a new incremental migration
psql "postgresql://postgres:[email protected]:54322/postgres" -f scripts/0NN_my_change.sql
PostgREST auto-refreshes on schema changes locally; no restart needed.
Run tests
pnpm vitest run # 259 tests across 8 files; no DB needed
pnpm exec playwright test # E2E (Playwright will spin up the dev server)
Type check
pnpm exec tsc --noEmit
After step 5, this should be clean. If it isn't, regenerate types.
Stop the local stack
supabase stop
Containers are stopped but volumes persist. To nuke them entirely: supabase stop --no-backup then supabase start for a fresh slate.
Tail logs
supabase logs # all services
supabase logs --type api
supabase logs --type db
Run the proposal-expiry job manually
SELECT public.expire_proposals();
-- returns the number of proposals flipped from 'open' to 'decided' or 'expired'
In production you'd schedule this via pg_cron (the schema's migration 019 sets this up automatically when pg_cron is available) or via a Next.js cron endpoint guarded by a CRON_SECRET.
Troubleshooting
supabase start hangs or fails
Make sure Docker Desktop is open and docker info works. Some corporate VPNs interfere with the local images; turn the VPN off, run supabase start, then turn it back on.
"relation does not exist" when the app runs
You skipped step 4. Apply scripts/schema.sql.
"Invalid API key" toasts in the app
The anon key in .env.local doesn't match supabase status. supabase stop --no-backup && supabase start will regenerate the keys cleanly; paste the new ones into .env.local.
Emails aren't arriving
Local Supabase routes outbound mail to Inbucket. Open http://127.0.0.1:54324.
Signup says "Database error saving new user"
The handle_space_signup trigger fired but couldn't insert the space_members row. Almost always because space_id metadata was missing. The app's signup flow handles this correctly via the admin client; if you're testing direct auth.users inserts, that path doesn't write a space_members row by design.
Realtime chat doesn't refresh
Confirm comms_messages and comms_channels are in supabase_realtime:
SELECT tablename FROM pg_publication_tables WHERE pubname = 'supabase_realtime';
If they aren't, re-run scripts/schema.sql (Section 8 handles it idempotently).