docs(fase-7): plan — collective-flow E2E coverage + ambrosio deploy notes

- plan/fase-7-collective-flow-tests.md: 14 new Playwright tests (O-01..O-04
  onboarding, I-01..I-05 invitation acceptance, MC-01..MC-05 admin manage)
  plus shared fixtures/db.ts helpers (resetEva, seedExpiredInvitation,
  seedUsedInvitation, restoreSeedMembership). Risks + scope-out documented.
- CLAUDE.md: add "Fase 6+ production deploy to ambrosio" section with the
  files / config / discovered fixes for the prod stack; link to the new plan
  from the Project Status line; add gotchas #16 (injectManifest filename
  hardcoded in @vite-pwa/sveltekit 0.6.8) and #17 (supabase/postgres doesn't
  bootstrap its internal roles on a fresh volume).
This commit is contained in:
2026-04-14 21:39:26 +02:00
parent 74e919aa71
commit 17bc344986
2 changed files with 123 additions and 2 deletions

View File

@@ -8,11 +8,11 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Status
**Fase 05 complete. Fase 6 complete (deploy readiness: PWA injectManifest SW + manifest + iOS meta tags + placeholder icons + Kong rate-limit on invitations + dev JWT rotation + prod env template + rls-audit.sh + Justfile lighthouse/rls-audit/test-rate-limit). 236 tests green: 34 pgTAP + 140 Vitest integration + 15 Vitest unit + 46 Playwright + 1 Vitest rate-limit (gated, run via `just test-rate-limit`). 3 skipped in `just test-all` (2 Realtime presence — upstream `handle_out/3` bug; 1 rate-limit — gated). Push notifications + CI Docker + final production icons deliberately out of scope. ✅**
**Fase 05 complete. Fase 6 complete (deploy readiness: PWA injectManifest SW + manifest + iOS meta tags + placeholder icons + Kong rate-limit on invitations + dev JWT rotation + prod env template + rls-audit.sh + Justfile lighthouse/rls-audit/test-rate-limit). Production stack deployed to ambrosio (OVH VPS) at https://erosi.oier.ovh (app + Supabase) + https://auth.oier.ovh (Keycloak); Let's Encrypt certs via host Caddy; PWA strategy switched to generateSW to unblock the prod build (see gotcha #16). Fase 7 is the active plan: 14 Playwright tests filling the collective-flow UI gap (onboarding / invitation acceptance / admin-manage) — see `plan/fase-7-collective-flow-tests.md`. 236 tests green: 34 pgTAP + 140 Vitest integration + 15 Vitest unit + 46 Playwright + 1 Vitest rate-limit (gated, run via `just test-rate-limit`). 3 skipped in `just test-all` (2 Realtime presence — upstream `handle_out/3` bug; 1 rate-limit — gated). Push notifications + CI Docker + final production icons deliberately out of scope. ✅**
- `README.md` — full development plan, confirmed tech stack, Justfile reference, and technical warnings
- `analysis/analisis-funcional.md` — complete functional specification (domain model, use cases, data models, business rules)
- `plan/fase-*.md` — phase-by-phase implementation plans (Fase 0 through Fase 6)
- `plan/fase-*.md` — phase-by-phase implementation plans (Fase 0 through Fase 7; Fase 7 is the active plan)
- `design/slate_collective/DESIGN.md` + `design/*/screen.png` — "Monolith Editorial" direction + per-screen Stitch mockups (desktop + mobile variants). Consult before changing UI.
### Fase 1 — what has been built
@@ -74,6 +74,26 @@ just test-e2e # Playwright E2E tests (requires just dev running)
just test-e2e-headed # Playwright with visible browser (debugging)
```
### Fase 6+ — production deploy to ambrosio (2026-04-14)
Live URLs: **https://erosi.oier.ovh** (app + Supabase API, single-domain) + **https://auth.oier.ovh** (Keycloak). OVH VPS, Ubuntu 24, Docker 29.
- `infra/docker-compose.erosi.yml` — full stack bound to `127.0.0.1` only (db, auth, rest, realtime, storage, imgproxy, kong, keycloak, app). `studio` + `meta` intentionally dropped for prod.
- `infra/caddy/erosi.Caddyfile` — snippet appended to the host's `/etc/caddy/Caddyfile` between `# BEGIN colectivo-erosi` / `# END colectivo-erosi` markers. Host Caddy 2.11.2 handles TLS (Let's Encrypt via tls-alpn-01). `erosi.oier.ovh` routes `/auth/v1/*`, `/rest/v1/*`, `/realtime/v1/*`, `/storage/v1/*`, `/graphql/v1/*`, `/pg/*` → Kong :8000; everything else → SvelteKit :3000. `auth.oier.ovh` → Keycloak :8090 with `X-Forwarded-Port` explicitly (Caddy auto-sets Proto/Host/For).
- `infra/scripts/deploy-erosi.sh` — idempotent redeploy: rsync → generate secrets on first run (via `infra/scripts/rotate-jwt.sh`) → substitute `__KEYCLOAK_CLIENT_SECRET__` in `keycloak/realm-export.erosi.json` → docker compose build + up → run pending migrations → re-install Caddy snippet → reload.
- `keycloak/realm-export.erosi.json` — prod realm: `registrationAllowed: true`, redirect URIs scoped to `https://erosi.oier.ovh/*`, client secret placeholder that the deploy script substitutes with a per-install random value. No dev users.
- `.env.erosi.example` — committed template. The real `/opt/colectivo/.env` on ambrosio is 600 and stays on the server.
- UFW allows `80/tcp`, `443/tcp`, `22/tcp`. All other ports remain DENY. Docker services listen on `127.0.0.1` only, so Kong / Keycloak / the app are unreachable from the public internet except via Caddy.
**Prod-specific fixes that had to be made for the stack to boot cleanly on a fresh volume:**
- `infra/db-init/00-role-passwords.sh` — rewrote as idempotent bootstrap (see gotcha #17): creates all Supabase service roles if absent, enables `pg_cron` + `pgcrypto` + `uuid-ossp`, creates `auth` / `storage` / `graphql_public` / `_realtime` / `realtime` schemas + empty `supabase_realtime` publication + `keycloak` database + role, sets `supabase_auth_admin` search_path to `auth`. The old `00-role-passwords.sql` (ALTER-only) has been removed.
- `apps/web/vite.config.ts` — PWA strategy switched `injectManifest``generateSW` (gotcha #16).
- Keycloak container uses `start --import-realm` (no `--optimized`) so Quarkus re-builds with the Postgres driver on first boot; with `--optimized` it silently falls back to H2 and fails with "jdbc:h2 URL format error".
**Keycloak admin password:** generated per-install — retrieve with `ssh ambrosio 'grep KEYCLOAK_ADMIN_PASSWORD /opt/colectivo/.env'`. Console at https://auth.oier.ovh/admin.
**Not yet configured on ambrosio:** SMTP (Resend), automated backups (script exists at `infra/scripts/backup.sh`, not scheduled), Lighthouse run against prod URL, final production icons.
### Fase 6 — what has been built
**6.1 PWA `injectManifest` migration**
@@ -111,6 +131,10 @@ just test-e2e-headed # Playwright with visible browser (debugging)
15. **`@vite-pwa/sveltekit` does not auto-register the service worker.** The plugin provides a virtual module (`virtual:pwa-register`, `virtual:pwa-register/svelte`) but you have to call `registerSW()` (or `useRegisterSW()` for Svelte bindings) yourself. Without that call, the SW file is generated + served but never registered, and `navigator.serviceWorker.getRegistration()` returns `undefined`. The canonical place is `apps/web/src/routes/+layout.svelte`'s `onMount()`.
16. **`@vite-pwa/sveltekit` 0.6.8 `injectManifest` hardcodes the source SW filename.** The plugin looks for the compiled SW at `.svelte-kit/output/client/service-worker.js` regardless of `filename: 'sw.ts'`. SvelteKit blocks Workbox imports from `src/service-worker.[jt]s` (gotcha #10), so there's no way to actually feed the plugin. **Workaround:** use `strategies: 'generateSW'` instead — the plugin auto-generates a precache-only SW equivalent to our minimal `src/sw.ts`. `vite.config.ts` currently uses this. Found while building for the ambrosio deploy (2026-04-14); same build was broken on `main` silently.
17. **supabase/postgres v15.1.1.78 does NOT create the Supabase internal roles on a fresh volume.** The image ships with custom extensions + configs but no role-bootstrap script. Dev "works" because its volume was initialised long ago, grandfathering the roles (`authenticator`, `anon`, `authenticated`, `service_role`, `supabase_admin`, `supabase_auth_admin`, `supabase_storage_admin`, `supabase_replication_admin`, `supabase_realtime_admin`, `pgbouncer`, `dashboard_user`). On a fresh volume only `postgres` exists. `infra/db-init/00-role-passwords.sh` now bootstraps them idempotently (create-if-missing) + enables `pg_cron` + creates the empty `supabase_realtime` publication + sets `supabase_auth_admin` search_path to `auth` (without this GoTrue migrations create `factor_type` in `public` and later migrations explode). See `plan/fase-6-deploy-prep.md` §6+ context for the full list of deltas discovered during the ambrosio bring-up.
### Fase 5 — what has been built (complete in scope)
**5.8 big-button create (match /notes)**