Files
av-planner/deployment-plan.md
T
aarbitandClaude Sonnet 5 0a7f2f7fee Deploy to Cloudflare Workers and rebrand to Diagrav
Adds Workers + static-assets configs for the app (app.diagrav.com) and a
separate marketing/landing site (diagrav.com), plus the deployment runbook
used to stand up Supabase Cloud, Google OAuth, Resend SMTP, and Cloudflare
DNS/custom domains. Renames the app from "AV Planner" to "Diagrav" to match
the new domain.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
2026-09-28 10:05:59 -05:00

189 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Deployment & CI/CD planning notes
Discussion notes only — nothing here has been implemented yet. Written so a
different Claude Code conversation on this repo can pick up where this one
left off without re-deriving it. Nothing below is committed to as final; it's
a record of what was discussed and concluded, plus open decisions.
## Hosting shape (discussed, not yet implemented)
The app is a static frontend (Vite build, no server of its own) + Supabase
backend (Postgres, Auth, RLS, one Edge Function). [organized-ideas.md](organized-ideas.md)
already commits to this being provider-agnostic (Supabase Cloud, self-hosted
Supabase Docker stack, or export the Postgres DB elsewhere — no app code
changes needed either way).
To go live:
1. **Backend**: create a Supabase Cloud project, `supabase link` + `supabase db push`
to apply the 15 existing migrations, `supabase functions deploy admin-user-action`.
Set real Google OAuth credentials for the production redirect URL — right
now `supabase/config.toml` and `vite.config.ts` are hardcoded to
`127.0.0.1` with `skip_nonce_check = true` for local dev; this needs a
production config pass. Needs a real SMTP provider for auth emails
(Resend suggested in organized-ideas.md as the default) since Inbucket
(local fake mail catcher) doesn't exist in the cloud.
2. **Frontend**: `npm run build` → static `dist/`, deploy to any static host
(Vercel/Netlify/Cloudflare Pages, or self-hosted). Set
`VITE_SUPABASE_URL` / `VITE_SUPABASE_ANON_KEY` as build-time env vars
pointing at the real Supabase project (Vite bakes these in at build
time — not runtime-configurable without a refactor).
## Marketing site (`site/`)
A separate, informational "what is this thing" page lives in `site/` at the
repo root — plain hand-written HTML/CSS (`index.html` + `style.css` +
`images/`), no build step, not part of the Vite app or its `src/` tree at
all. Screenshots under `images/` are real captures from the app (a demo
DAWless rig wired up for the purpose), referenced with relative paths, so
the folder is fully self-contained.
Deploys the same way the app's frontend would, just simpler — no env vars to
bake in, no build command, just serve the static files as-is. Same free-tier
host options apply (Vercel/Netlify/Cloudflare Pages, or self-hosted).
**Domain purchased: `diagrav.com`.** The app was renamed from "AV Planner" to
**Diagrav** to match. The site's nav ("Log in" / "Sign up") and its closing
CTA ("Sign up free") now link to `https://app.diagrav.com/` — so the
subdomain-split option below is the working assumption baked into the site
as of now, even though neither `diagrav.com` nor `app.diagrav.com` is
actually deployed anywhere yet. Those links will 404 until the app is
actually live at that subdomain; treat this as a leaning, not a final
decision — swapping to the path-based option only means changing the few
`href`s in `site/index.html`, nothing structural.
- **Subdomain split (current assumption)**: bare `diagrav.com` for the
marketing site, `app.diagrav.com` for the actual tool — cleanest
separation, two hosting targets instead of one.
- **Same domain, different path** (marketing at `/`, app at `/app` or
similar) — one hosting target, but means the static host needs to route
`/` to `site/` and everything else to the Vite `dist/` output, which the
simple "just deploy `dist/`" flow above doesn't currently account for.
Whichever is chosen, this needs its own line in the CI/CD plan below
(currently only the app's frontend/backend deploy steps are scoped) — it's a
third deployable alongside "frontend" and "backend," not an afterthought
folded into the frontend step.
## Cost / free-tier findings
Provider: **Supabase**, since the app already codes directly against its
SDK/Auth/RLS model (`src/data/Supabase*.ts`) — switching providers would mean
rewriting those repositories.
Current Supabase Free plan limits (confirmed against supabase.com/pricing,
not from training data — re-verify if this doc is read much later):
| Resource | Free limit |
|---|---|
| Database size | 500 MB |
| Egress | 10 GB/mo (5 GB cached + 5 GB uncached) |
| Monthly active users | 50,000 |
| Edge Function invocations | 500,000/mo |
| File storage | 1 GB |
| Realtime concurrent connections | 200 |
| Inactivity | paused after 1 week idle, max 2 active projects |
**Conclusion: this app is storage-bound, not bandwidth-bound.** File storage,
realtime, and edge function limits are all non-issues (no file uploads, no
`supabase.channel(...)` usage anywhere in `src/`, and the one edge function
is admin-only/rare). Egress is generous relative to what this app transfers
per session. The one limit likely to actually bite is **database size**
(500 MB) — driven less by user-count/traffic than by long-term accumulation
in `diagrams` + `diagram_snapshots`.
**Specific risk found**: `SupabaseDiagramRepository.ts` (`maybeWriteSnapshot`,
~line 70-93) writes a new version-history snapshot row every 5 minutes of
active editing, with **no retention/expiry** — they accumulate indefinitely.
A user who edits diagrams regularly for months will grow the DB more than a
burst of new signups would. **Suggested follow-up (not yet done): add
snapshot retention** — e.g. keep the last N snapshots per diagram, or prune
anything older than ~90 days. This is the highest-leverage lever for keeping
DB size (and therefore cost/free-tier headroom) under control.
Rough scale ballpark discussed: solo/small-team use won't come close to any
limit for years. Somewhere in the ~300–1,500 regular-active-user range with
several diagrams each is roughly where the 500MB DB cap would start to bite,
well before the 10GB egress cap or 50k MAU cap would matter. Treat this as a
rough estimate, not a measured number — worth checking real diagram JSON
size in Studio once there's real usage.
Estimated cost: **$0/mo** to start (Supabase Free + Vercel/Netlify/Cloudflare
Pages free tier + Resend free tier for auth email), **$25/mo** once you want
Supabase Pro (no project auto-pause, higher limits across the board).
## CI/CD plan: Gitea + Woodpecker, pushbutton staging/prod
Repo is self-hosted on Gitea (`git.halfbinary.net/aarbit/av-planner`) with
Woodpecker CI already in use elsewhere. No `.woodpecker.yml` exists yet in
this repo.
**Desired flow**: PR merges to `main` → pipeline runs lint/typecheck/build
and stops → a human clicks a "Deploy" button to push to staging, and
separately clicks it again to promote to production. Not fully automatic on
merge — deploys to both environments are manual/pushbutton.
**Mechanism (confirmed against current Woodpecker docs)**: Woodpecker has a
built-in **deploy event** for exactly this. Enable **"Allow deployments"** in
the repo's Woodpecker project settings, and any successful pipeline run gets
a **Deploy** button in the UI. Clicking it re-runs that pipeline with
`event: deploy` and a `deploy_to` string you type (e.g. `staging` or
`production`), exposed to steps as `$CI_PIPELINE_DEPLOY_TARGET`. Steps gate
on it, e.g.:
```yaml
steps:
- name: deploy-staging
when:
- event: deploy
evaluate: 'CI_PIPELINE_DEPLOY_TARGET == "staging"'
- name: deploy-prod
when:
- event: deploy
evaluate: 'CI_PIPELINE_DEPLOY_TARGET == "production"'
```
**What needs to exist**:
1. **Two Supabase projects** — staging and prod. This exactly fills the free
tier's "2 active projects" cap (see above) — no free-tier headroom left
for a third project if one is ever wanted later.
2. **Two frontend hosting targets** — two sites/deployments (SaaS host or
self-hosted alongside Gitea/Woodpecker — not yet decided which).
3. **Known wrinkle**: because Vite bakes `VITE_SUPABASE_URL`/`VITE_SUPABASE_ANON_KEY`
in at build time, you can't build once and promote the identical artifact
to both environments — each deploy target needs its own build with its
own env vars baked in. Not hard, just means "deploy" reruns the build
rather than reusing one artifact. A non-issue at this app's scale.
4. **Migrations run per-target** via Supabase CLI, scripted per environment:
```bash
supabase link --project-ref $SUPABASE_PROJECT_REF
supabase db push
supabase functions deploy admin-user-action
```
5. **Secrets needed in Woodpecker** (scoped per target): `SUPABASE_ACCESS_TOKEN`,
staging/prod `SUPABASE_PROJECT_REF`, staging/prod `VITE_SUPABASE_URL` /
`VITE_SUPABASE_ANON_KEY`, plus whatever the frontend host needs (API
token, or SSH key if self-hosted).
6. **Google OAuth**: needs its own authorized redirect URI added in Google
Cloud console for each of the staging and prod domains — one-time setup,
easy to forget.
**Difficulty estimate discussed**: roughly a focused afternoon — nothing here
fights the tooling (no containers to orchestrate, no server process to run).
Breakdown: `.woodpecker.yml` (~1–2 hrs), second Supabase project + migration
check (~30 min), frontend hosting target wiring (~30–60 min), secrets +
"Allow deployments" toggle (~15 min), end-to-end test/debug (~1–2 hrs).
## Open decisions (not yet made)
- Which frontend host: SaaS (Vercel/Netlify/Cloudflare Pages) vs self-hosted
alongside the existing Gitea/Woodpecker infra.
- Where the marketing site (`site/`, see above) lives relative to the app —
leaning subdomain (`diagrav.com` + `app.diagrav.com`, per the links already
in `site/index.html`) over same-domain/different-path, but not finalized —
and whether it gets its own line in the eventual `.woodpecker.yml` or
piggybacks on the frontend deploy step.
- Whether/when to add diagram-snapshot retention (the storage-risk item
above) — not done yet, just identified.
- Actual `.woodpecker.yml` has not been written yet — this doc captures the
plan/shape only, per the user's request to keep this conversation
discussion-only and not make repo changes here.