Files
collective-lists/CLAUDE.md
Oier Bravo Urtasun 7d91705fc2 feat(fase-1): auth, collective management, and DB migrations
- 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>
2026-04-12 15:29:29 +02:00

8.5 KiB

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)

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: CollectiveCollectiveMember, ShoppingListShoppingItem, TaskListTask, 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.