diff --git a/README.md b/README.md index 5270f2f..75d7f6a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# AV Planner +# Diagrav Plan out AV/network installs: define devices and their ports, wire them together on a canvas, and get a bill of materials (devices + cables, with lengths) for diff --git a/deployment-plan.md b/deployment-plan.md new file mode 100644 index 0000000..cddf6b9 --- /dev/null +++ b/deployment-plan.md @@ -0,0 +1,188 @@ +# 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. diff --git a/index.html b/index.html index 106969e..99753f4 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ -
+ Diagrav is a visual planning tool for anyone wiring together audio/video gear or a + DAWless synth setup. Drag devices onto a canvas, connect their ports, and let the app + catch incompatible connections and total up your shopping list — before you've spent a dollar. +
+
+ Built for the moment before a purchase — when you're still figuring out what actually connects to what.
+Place devices from a library of ports and connectors, then draw cables between them exactly like you would on a whiteboard — except it remembers everything.
+Every connector type knows what it physically mates with. Try to wire an XLR into a 1/4" jack and the app tells you why not, instead of finding out at the store.
+Every cable and device on the canvas rolls up into a bill of materials — grouped by cable type and length, with running costs, so you know exactly what to order.
+A stereo pair or an 8-channel snake is really one purchase, not several. Bundle the individual runs together so the shopping list counts it the way you'd actually buy it.
+Connector types, cable types, and device templates are shared across everyone using the app — with your own private additions for anything niche or proprietary.
+Share a diagram with view or edit access, keep a version history as it evolves, and pick up right where you left off.
++ Drop a device onto the canvas, expand it to see its individual ports, and drag a + connection from one port to another. Every wire is a real object — it knows which + two ports it joins, what cable type it needs, and how long a run it is. +
+
+ + As you wire things up, Diagrav keeps a running bill of materials in the side + panel — every cable type you'll need, grouped by length, with a running cost total. + Bundled cables count as the single item you'll actually order. +
+
+ + Browse built-in devices and connectors by category or manufacturer, search across + everything at once, and drop in your own custom gear when something isn't in the + catalog yet — synths, mixers, adapters, whatever your rack actually has. +
+
+ Plan the wiring first. Buy the right cables once.
+ Sign up +