npm install # install depsnpm run dev # Vite dev server on http://localhost:5173npm run build # typecheck (tsc -b) + vite buildnpm run lint # eslintnpm run typecheck # tsc -b --noEmitnpm test # vitest (frontend unit tests)npm run test:deno # deno test (edge-function tests)npm run gen:types # regenerate database types from the linked project
Always run npm run build before committing UI or logic changes - it typechecks
the whole app. Also run npm test (and npm run test:deno if you changed
edge functions). Add or update tests when you add or change logic like parsing,
validation, or calculations. Tests for frontend logic live next to the code as
src/**/*.test.ts(x); edge-function tests live in supabase/functions/tests/.
The schema lives in supabase/migrations as sequentially-numbered SQL files
(0001_init.sql, 0002_skills.sql, …). They are the single source of truth: a
fresh project becomes a working backend with one supabase db push, and from
then on you never apply migrations by hand.
A GitHub Action (.github/workflows/deploy-migrations.yml) runs supabase db push
whenever a file under supabase/migrations/ lands on main. The CLI only applies
what's pending (the remote tracks applied versions), so merging a PR that adds a new
migration file applies exactly that file — no manual step, safe to re-run.
Automation cron scheduling: After pushing migrations, the workflow automatically
schedules (or reschedules) the background dispatcher and scheduler cron jobs. These
jobs tick once per minute and power listeners, scheduled agents, and webhooks. The
workflow fetches the service role key from the Supabase Management API and calls the
setup_automation_cron() RPC with your project's URL. This is idempotent and safe to
re-run on every deploy. If you set up SupaNet by hand without using the workflow, you
can schedule cron manually from the app: on the Listeners page, if you see an
"Automations aren't running" banner, click Schedule it now — it calls the same RPC.
To set this up, add two repository secrets (Settings → Secrets and variables →
Actions):
Secret
What
SUPABASE_ACCESS_TOKEN
A Supabase personal access token (Dashboard → Account → Access Tokens) — the same one the functions workflow uses.
SUPABASE_DB_PASSWORD
Your project's database password (Dashboard → Project Settings → Database). db push connects straight to Postgres, so the token alone isn't enough.
The project ref defaults in the workflow and is overridable with a repository
variableSUPABASE_PROJECT_REF.
# 1. Create the next sequential file (keep numbers unique and contiguous).# Write it idempotently where practical (create … if not exists, drop … if exists).$EDITOR supabase/migrations/0040_my_change.sql# 2. (Optional) try it locally / against your linked project before merging.supabase db push# 3. Refresh the typed client and open a PR.npm run gen:types
Merging the PR to main triggers the Action, which applies it to the live database.
One rule: every migration filename must have a unique numeric prefix. Two
files sharing a number (e.g. two 0032_*.sql) collide — db push derives the
version from the prefix and will refuse the push. Always use the next free number.
A test (src/lib/migrations.test.ts) runs in CI to catch duplicate or gapped
prefixes before they reach main, preventing silent migration failures. However,
collisions can still slip through a rebase: if your 0086_my_change.sql lands while
someone else's 0086_other_change.sql is already on main, both will be in CI's
single run and the test won't catch it. Re-check the next free number right
before pushing, not just when you create the file.
Changing a function signature: Postgres rejects create or replace function
when the returns or OUT signature changes (error SQLSTATE 42P13), which aborts
the whole db push. When a migration alters an existing function's signature
(e.g. adding a column to a returns table (...)), prepend a DROP:
drop function if exists public.my_function(arg1_type, arg2_type);create or replace function public.my_function(arg1_type, arg2_type)returns table (...) as $$...
A migration that hits this error never records as applied, leaving its objects
missing in production (e.g. a later promote_to_admin in the same file is never
created). Because it failed to apply, editing the file in place to add the DROP is
the correct, safe fix — reapply and it will succeed.
On a brand-new project the storage schema can lag a few seconds. If applying
the full migration fails on storage.buckets, apply the core tables first, then
the storage section.
If you change the schema locally: refresh the typed client with
npm run gen:types and re-check the Supabase security advisors.
Confirmation and magic-link emails use the Supabase project's Site URL plus
the Redirect URLs allowlist. Set these to your deployed origin, or links will
point at localhost.
Tests: a GitHub Action (.github/workflows/test.yml) runs lint, build,
frontend tests, and edge-function tests on every PR and every push to main.
The feature bot's PRs get the same checks via .github/workflows/claude-feature.yml
(GitHub doesn't fire normal PR workflows for bot PRs, so the feature workflow runs
them inline).
Frontend: Railway rebuilds the app from the latest commit.
Edge functions: a GitHub Action redeploys any function that changed.
Database migrations: a separate Action applies any new migration files (see
Database above).
After the one-time setup of secrets and environment variables, you never run
supabase commands by hand.