- SQL migrations: users, collectives, collective_members, collective_invitations,
full RLS policies, is_active_member/is_member/is_admin helpers,
accept_invitation SECURITY DEFINER function, admin auto-promote trigger,
avatars storage bucket
- Auth refactor: replace keycloak-js with Supabase OAuth (GoTrue OIDC proxy,
Option B). signInWithOAuth({ provider: 'keycloak' }) + PKCE flow
- GoTrue config: GOTRUE_EXTERNAL_KEYCLOAK_URL + GOTRUE_EXTERNAL_KEYCLOAK_REDIRECT_URI
added to docker-compose.dev.yml; Keycloak realm updated with GoTrue callback URI
- New routes: /auth/callback, /invitation/[token], /(app)/collective/manage
- Functional onboarding: create collective or join via invite link
- Full settings page: display name (debounced), avatar (initials/emoji/upload),
language switcher, Keycloak account console link
- Components: Avatar.svelte (initials/emoji/upload with fallback),
ImageCropper.svelte (cropperjs, 1:1 crop, lazy-loaded)
- i18n: added all Fase 1 message keys (en + es); renamed 'delete' → 'action_delete'
(JS reserved word); fixed plugin-m-function-matcher major-version URL format
- Database types: manually seeded packages/types/src/database.ts from migrations
- .env.development: committed public dev defaults for SvelteKit type checking
- CLAUDE.md: documented /etc/hosts requirement for keycloak hostname
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
177 lines
8.5 KiB
Markdown
177 lines
8.5 KiB
Markdown
# 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
|
|
|
|
**Fase 0 complete. Fase 1 in progress.**
|
|
|
|
- `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)
|
|
|
|
## Local Dev: Required /etc/hosts Entry
|
|
|
|
GoTrue (Supabase Auth) and the browser both must resolve `keycloak` to reach the Keycloak container. Add this line to `/etc/hosts` on the development machine:
|
|
|
|
```
|
|
127.0.0.1 keycloak
|
|
```
|
|
|
|
Without this, the OAuth redirect URL (`http://keycloak:8080/...`) built by Keycloak's discovery document is unreachable from the browser.
|
|
|
|
## 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 <file> # 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 → GoTrue → Supabase)
|
|
|
|
**Strategy: Option B — GoTrue as OIDC proxy.**
|
|
The app authenticates with Keycloak (PKCE), then exchanges the Keycloak token with GoTrue for a Supabase JWT. Supabase RLS uses `auth.uid()` which resolves to the GoTrue user UUID (mapped from Keycloak `sub`). GoTrue maintains `auth.users`.
|
|
|
|
Key decisions:
|
|
- **Self-registration enabled** in Keycloak — users can create accounts on the Keycloak login screen.
|
|
- **Invitations are link-only** — no email sent. Admin generates a link and shares it manually.
|
|
- **Multiple collectives per user** — a user can belong to several collectives and switch between them in the sidebar.
|
|
|
|
Logout must invalidate both sides: call `keycloak.logout()` (invalidates Keycloak session) AND clear the Supabase client session 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`.
|