# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Code Language **All code must be written in English.** This applies to variable names, function names, type names, database column names, SQL identifiers, comments, and any other code artifact. The UI may display text in any language, but the code layer is always English. ## Project Status This repository is in the **planning phase**. No application code exists yet. The repo contains: - `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 4) Start by reading these documents before implementing anything. ## Planned Stack | Layer | Technology | |---|---| | Frontend / PWA | SvelteKit 2 + `@vite-pwa/sveltekit` + Workbox | | Auth / IdP | Keycloak self-hosted (OIDC/OAuth2) | | Backend / BaaS | Supabase self-hosted (GoTrue + Realtime + Storage + Kong) | | Database | PostgreSQL 15 (via Supabase) | | Offline storage | IndexedDB via `idb` | | Monorepo | Turborepo + pnpm workspaces | | Infra | Docker Compose (dev + prod) + nginx (prod, managed externally) | | CI/CD | GitHub Actions | ## Planned Repository Layout ``` apps/web/src/lib/ supabase.ts # Supabase singleton client auth.ts # keycloak-js OIDC client + session helpers stores/ # Svelte global stores sync/ # offline queue + conflict resolution components/ # reusable UI components apps/web/src/routes/ # SvelteKit file-based routes packages/types/src/ database.ts # types generated from Supabase schema (never edit manually) domain.ts # domain types (Collective, ShoppingList, Item, etc.) supabase/ migrations/ # versioned SQL migrations seed.sql # dev test data (5 Keycloak users + sample collective) functions/ # Edge Functions (invitations, etc.) config.toml # Supabase CLI config + Keycloak OIDC keycloak/ realm-export.json # "colectivo" realm — imported on dev startup infra/ docker-compose.dev.yml docker-compose.prod.yml scripts/backup.sh / backup-cron.sh / restore.sh / deploy.sh ``` ## Development Commands (Justfile) ```bash just dev # start docker-compose.dev.yml + SvelteKit dev server just dev-stop # stop local environment just db-reset # drop + migrate + seed (dev) just db-migrate # apply pending migrations (dev) just db-seed # apply supabase/seed.sql to the running dev db just db-types # regenerate TS types from Supabase schema → packages/types just kc-export # export Keycloak realm → keycloak/realm-export.json just kc-open # open Keycloak Admin Console (localhost:8080) just deploy # run infra/scripts/deploy.sh (prod) just backup supabase # immediate manual backup just restore supabase # restore a specific dump just logs # docker compose logs -f (prod) ``` ## Local Dev Endpoints | Service | URL | Notes | |---|---|---| | SvelteKit app | http://localhost:5173 | | | Supabase Studio | http://localhost:54323 | | | Supabase API (Kong) | http://localhost:8001 | Port 8000 reserved by other services | | Keycloak Admin | http://localhost:8080/admin | admin / admin (dev only) | | PostgreSQL | localhost:5432 | postgres / postgres (dev only) | ## Domain Model The central organizing unit is the **Collective** (household group), not the individual user. All content belongs to the Collective. **Roles:** `admin` (full CRUD + member management) | `member` (full CRUD on content) | `guest` (read-only) **Core entities:** `Collective` → `CollectiveMember`, `ShoppingList` → `ShoppingItem`, `TaskList` → `Task`, `Note`, `CollectiveInvitation` **Key field names (English):** `collective_id`, `list_id`, `is_checked`, `checked_by`, `checked_at`, `created_by`, `created_at`, `completed_at`, `sort_order`, `display_name`, `avatar_type`, `avatar_emoji`, `avatar_url`, `language` **Key business rules:** - Only one active shopping session per list at a time. - Completing a list keeps items as history; it can be "reset" (uncheck all → back to active). - Trash retains deleted items/notes for 7 days before permanent deletion. - If the sole admin deletes their account, the system auto-promotes the oldest member before allowing deletion. ## Auth Architecture (Keycloak → Supabase) Auth flows through Keycloak as the OIDC IdP. Supabase validates Keycloak-issued JWTs. Supabase RLS uses `auth.uid()` which reads the `sub` claim from the JWT (must be a UUID v4 — Keycloak default). The Keycloak RS256 public key must be set as `JWT_SECRET` in Supabase. **Rotating Keycloak signing keys requires updating Supabase `JWT_SECRET` simultaneously.** Logout must invalidate both sides: call `keycloak.logout()` (invalidates Keycloak session) AND clear the Supabase client locally. ## Sync Strategy | Content | Mechanism | Target latency | |---|---|---| | Items in active shopping session | WebSocket (Supabase Realtime) | < 1 second | | Lists, tasks (outside session) | SSE / polling | < 5 seconds | | Notes | Polling | < 30 seconds | **Offline-first:** all operations execute locally first (IndexedDB), then sync in background. Conflict resolution is last-write-wins. Conflicts are logged to a `sync_conflicts` table for debugging — no CRDT in MVP. ## Critical Platform Gotchas **iOS Safari / PWA:** - `keycloak-js` silent token refresh uses iframes (`checkLoginIframe`) — Safari blocks third-party cookies in iframes. **Set `checkLoginIframe: false`** and use `updateToken()` manually instead. - Background Sync API is not supported in Safari. Implement a manual fallback using the `online` event. - Push notifications require iOS 16.4+ and the PWA must be installed to the home screen. - Test the shopping session mode on a real iPhone during development, not only in DevTools. **nginx (prod) — WebSocket for Realtime:** - The `/realtime/` location block **must come before** the `/` block. - `proxy_read_timeout 3600s` is required on the Realtime block — without it, nginx closes WebSocket connections after 60 seconds, forcing continuous reconnects during active shopping sessions. - CSP `connect-src` must include both `https://` and `wss://` for the API domain. **Keycloak behind nginx:** - `X-Forwarded-Host` and `X-Forwarded-Port` headers are mandatory. Without them, Keycloak builds redirect URIs with the internal port (8080) instead of 443, breaking the OIDC flow. **Supabase Realtime self-hosted:** - Check `MAX_REPLICATION_SLOTS` in PostgreSQL and `REALTIME_MAX_CONNECTIONS` before going to production. - Presence subscriptions are more resource-intensive than Postgres Changes — limit them to lists with an active session. ## Dev Test Users Defined in `keycloak/realm-export.json` and `supabase/seed.sql`. All use password `test1234`. | User | Email | Role in test collective | |---|---|---| | `ana` | ana@dev.local | Admin | | `borja` | borja@dev.local | Member | | `carmen` | carmen@dev.local | Member | | `david` | david@dev.local | Guest | | `eva` | eva@dev.local | (no collective — for onboarding testing) | ## Internationalisation Library: **Paraglide JS** (`@inlang/paraglide-sveltekit`). Compile-time, zero runtime overhead, tree-shaken per language. - Message files: `messages/en.json` (default) and `messages/es.json` - Language detection order: `users.language` (if logged in) → `Accept-Language` header → `en` - Language switching: update `users.language` in Supabase + call `setLanguageTag()` — no page reload - **Never hardcode visible user-facing strings in components.** All UI text goes through Paraglide messages. Supported languages: `en`, `es`. ## TypeScript Types `packages/types/src/database.ts` is **generated** by `just db-types` (`supabase gen types typescript`). Never edit it manually. Domain-level types go in `packages/types/src/domain.ts`.