diff --git a/CLAUDE.md b/CLAUDE.md index 3cc6bd5..7576cb6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,7 +6,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co **Fase 0–5 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.limonia.net (app + Supabase, external Keycloak); edge proxy now off-stack — internal Caddy fans kong/app on host port 3000, whatever external proxy fronts the host terminates TLS and forwards plain HTTP to ambrosio:3000 (originally NetBird's self-hosted Traefik, now proxy-agnostic); PWA strategy switched to generateSW to unblock the prod build (see the "injectManifest hardcoded filename" gotcha below). Fase 7 complete: 12 new Playwright tests fill the collective-flow UI gap (O-01..O-03 onboarding, I-01..I-05 invitation acceptance, MC-02..MC-05 admin-manage) — writing them surfaced + fixed three latent bugs in the product code (atomic `create_collective()` RPC, pendingInvitationToken restore after login, manage page not reloading on late $currentCollective hydration). Plus migration 013 (auth.users role/aud default trigger) — found after the first real self-registration on prod via Keycloak→GoTrue left the user with empty `role`/`aud` columns, breaking every REST call with `role "" does not exist`. Post-fase-7 prod auth hardening (2026-04-20..21, see `docs/history/fase-8-auth-logout.md`): migration 014 extends the role/aud guard to UPDATE (GoTrue's pop-ORM resends role='' right after the INSERT and clobbered the INSERT trigger's backfill); `logout()` now does RP-initiated Keycloak end-session + lands on a new public `/logged-out` route, which both actually ends the Keycloak SSO cookie (so the next login re-prompts) and closes a PKCE-verifier race that produced "PKCE code verifier not found in storage" on prod. 259 tests green: 45 pgTAP + 140 Vitest integration + 15 Vitest unit + 59 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. ✅** -**MVP2 — COMPLETE (7/7 ✅, 2026-05-18).** Branch `mvp2`. Original cycle (fases 9–14) shipped 2026-05-18; **Fase 15 (common items management)** shipped same day after reopening the cycle. **Fase 9: dark mode + polish** (migrations 015–016 + pgTAP 009–010 + dark-mode plumbing + max-w-2xl reading column + sync_conflicts table/queue logging + WebKit Playwright project; see `docs/history/fase-9-polish-dark-mode.md`). **Fase 10: spec completion (H06/H07/H08 + trash restore + account deletion + sidebar-create + reset-from-detail + Accept-Language)** — migrations 017–021 + 4 new pgTAP suites + 6 new Playwright specs; see `docs/history/fase-10-spec-completion.md`. **Fase 11: item tags + drag-reorder** — migration 022 + pgTAP 014 + tags store + chips/picker + filter bar + realtime re-sort; see `docs/history/fase-11-item-tags-importance.md`. **Fase 12: section visibility (feature flags)** — migration 023 + pgTAP 015 + JSONB `feature_flags` on `users`+`collectives` + `public.section_enabled()` SQL helper with COALESCE precedence; see `docs/history/fase-12-section-visibility.md`. **Fase 13: server administration** — migrations 024+025 (server_admins + admin_actions + server_settings + 9 SECURITY DEFINER admin RPCs) + pgTAP 016+017 + `/admin` route group + last-admin guard + audit-append-only via missing-policies pattern + `section_enabled()` extended with server layer + `SERVER_ADMIN_EMAIL` bootstrap (prod needs manual re-run after the operator's first Keycloak login); see `docs/history/fase-13-server-admin.md`. **Fase 14: PWA hardening** — `useRegisterSW` + `UpdateToast` (auto-update toast) + offline chip + queued-op badge + `/settings/sync-conflicts` review route + `__APP_VERSION__`/`__APP_COMMIT__`/`__BUILD_TIME__` defines baked at build via Dockerfile `GIT_SHA` build-arg (host-side `git rev-parse --short HEAD` in `deploy-erosi.sh`) + "Acerca de" version chip in `/settings`; no migration; see `docs/history/fase-14-pwa-hardening.md`. **Fase 15: common items management** — migration 026 + pgTAP 018 + `item_frequency.weight integer` column, written only via admin-only SECURITY DEFINER RPCs (`set_item_frequency_weight` + `purge_item_frequency`) — existing all-deny client policies on `item_frequency` stay in place. Suggestion query reorders by `weight desc, use_count desc, last_used_at desc`. UI 3-way segmented control (Hide / Normal / Boost → -50 / 0 / 50) in `/collective/manage` (admin write, member read-only, guest hidden). `fetchSuggestions` gains `excludeNames` (capped at 200 names to bound URL length on PostgREST `not.in.()` filter) to drop items already on the active list from the dropdown; see `docs/history/fase-15-common-items.md`. **Final test totals (`just test-all`): 177 pgTAP + 179 Vitest integration + 47 Vitest unit + 93 Playwright + 1 gated rate-limit = 497 tests green; 3 skipped (2 Realtime presence — upstream `handle_out/3` bug `supabase/realtime#1617`; 1 rate-limit — gated). Delta vs MVP close: +132 pgTAP + +39 integration + +32 unit + +34 e2e = +237 tests across MVP2 fases 9–15.** Fase 7+8 already shipped as MVP cleanup, not MVP2. +**MVP2 — in progress (7/8 ✅).** Branch `mvp2`. Cycle reopened: fases 9–15 shipped 2026-05-18; **Fase 16 (loading spinner)** pending. **Fase 9: dark mode + polish** (migrations 015–016 + pgTAP 009–010 + dark-mode plumbing + max-w-2xl reading column + sync_conflicts table/queue logging + WebKit Playwright project; see `docs/history/fase-9-polish-dark-mode.md`). **Fase 10: spec completion (H06/H07/H08 + trash restore + account deletion + sidebar-create + reset-from-detail + Accept-Language)** — migrations 017–021 + 4 new pgTAP suites + 6 new Playwright specs; see `docs/history/fase-10-spec-completion.md`. **Fase 11: item tags + drag-reorder** — migration 022 + pgTAP 014 + tags store + chips/picker + filter bar + realtime re-sort; see `docs/history/fase-11-item-tags-importance.md`. **Fase 12: section visibility (feature flags)** — migration 023 + pgTAP 015 + JSONB `feature_flags` on `users`+`collectives` + `public.section_enabled()` SQL helper with COALESCE precedence; see `docs/history/fase-12-section-visibility.md`. **Fase 13: server administration** — migrations 024+025 (server_admins + admin_actions + server_settings + 9 SECURITY DEFINER admin RPCs) + pgTAP 016+017 + `/admin` route group + last-admin guard + audit-append-only via missing-policies pattern + `section_enabled()` extended with server layer + `SERVER_ADMIN_EMAIL` bootstrap (prod needs manual re-run after the operator's first Keycloak login); see `docs/history/fase-13-server-admin.md`. **Fase 14: PWA hardening** — `useRegisterSW` + `UpdateToast` (auto-update toast) + offline chip + queued-op badge + `/settings/sync-conflicts` review route + `__APP_VERSION__`/`__APP_COMMIT__`/`__BUILD_TIME__` defines baked at build via Dockerfile `GIT_SHA` build-arg (host-side `git rev-parse --short HEAD` in `deploy-erosi.sh`) + "Acerca de" version chip in `/settings`; no migration; see `docs/history/fase-14-pwa-hardening.md`. **Fase 15: common items management** — migration 026 + pgTAP 018 + `item_frequency.weight integer` column, written only via admin-only SECURITY DEFINER RPCs (`set_item_frequency_weight` + `purge_item_frequency`) — existing all-deny client policies on `item_frequency` stay in place. Suggestion query reorders by `weight desc, use_count desc, last_used_at desc`. UI 3-way segmented control (Hide / Normal / Boost → -50 / 0 / 50) in `/collective/manage` (admin write, member read-only, guest hidden). `fetchSuggestions` gains `excludeNames` (capped at 200 names to bound URL length on PostgREST `not.in.()` filter) to drop items already on the active list from the dropdown; see `docs/history/fase-15-common-items.md`. **Final test totals (`just test-all`): 177 pgTAP + 179 Vitest integration + 47 Vitest unit + 93 Playwright + 1 gated rate-limit = 497 tests green; 3 skipped (2 Realtime presence — upstream `handle_out/3` bug `supabase/realtime#1617`; 1 rate-limit — gated). Delta vs MVP close: +132 pgTAP + +39 integration + +32 unit + +34 e2e = +237 tests across MVP2 fases 9–15.** Fase 7+8 already shipped as MVP cleanup, not MVP2. + +**Pending in MVP2 (📋): Fase 16 — Loading spinner** (`plan/fase-16-spinner.md`). Single SVG-circle `Spinner.svelte` component (sizes sm/md/lg = 16/24/40px, color via `currentColor`, `motion-reduce:animate-none`, `role="status"` + sr-only `m.loading()` for a11y) integrated at 5 sites that already render `m.loading()` text or own a `loading` flag (auth splash + search + notes archive + create-collective modal + collective/manage sub-sections). No DB migration. Adds 5 unit + 3 e2e tests. ## Documentation Map diff --git a/README.md b/README.md index 362ed66..deb8cd1 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ | Fase 6 — Deploy readiness | ✅ Completa (PWA generateSW + manifest + iconos placeholder + Kong rate-limit en invitaciones + rotación JWT dev + rotate-jwt.sh + `.env.erosi.example` + rls-audit.sh + `just lighthouse`). Rate-limit auth retirado — Keycloak brute-force nativa cubre el caso. Push notifications y iconos finales fuera de scope. | | Fase 7 — Collective-flow E2E tests | ✅ Completa (12 Playwright nuevos O-01..MC-05; 3 bugs latentes corregidos: RPC atómica `create_collective`, restore de `pendingInvitationToken` tras login, hidratación tardía de `$currentCollective` en manage; migración 013 trigger `role/aud` default). | | Fase 8 — Prod auth hardening | ✅ Completa (migración 014 extiende `role/aud` guard a UPDATE; `logout()` RP-initiated Keycloak end-session + ruta pública `/logged-out`). | -| **MVP2** — Fase 9 → 15 | ✅ Completa (rama `mvp2`, 2026-05-18) | +| **MVP2** — Fase 9 → 16 | 🔄 7/8 (fases 9–15 ✅ 2026-05-18; Fase 16 📋) | | ↳ Fase 9 — Polish + Dark Mode | ✅ | | ↳ Fase 10 — Spec completion | ✅ | | ↳ Fase 11 — Item tags + importance UX | ✅ | @@ -27,6 +27,7 @@ | ↳ Fase 13 — Server administration | ✅ | | ↳ Fase 14 — PWA hardening | ✅ | | ↳ Fase 15 — Common items management | ✅ | +| ↳ Fase 16 — Loading spinner | 📋 | | Suite de tests (pgTAP + Vitest + Playwright) | ✅ **497 tests verdes** (177 pgTAP + 179 integración + 47 unit + 93 E2E) + 1 rate-limit gated (`just test-rate-limit`). 3 skipped en `just test-all` (2 Realtime presence — upstream `handle_out/3` bug; 1 rate-limit — gated). | --- @@ -61,6 +62,7 @@ Las fases ya cerradas (0, 1, 2a) reflejan este patrón retroactivamente: la secc | **MVP2 — Fase 13** | [fase-13-server-admin.md](plan/fase-13-server-admin.md) | 2–3 semanas | | **MVP2 — Fase 14** | [fase-14-pwa-hardening.md](plan/fase-14-pwa-hardening.md) | 3–5 días | | **MVP2 — Fase 15** | [fase-15-common-items.md](plan/fase-15-common-items.md) | 3–5 días | +| **MVP2 — Fase 16** | [fase-16-spinner.md](plan/fase-16-spinner.md) | 1 día | **Total estimado MVP (Fase 0–6): 11–16 semanas. MVP2 (Fase 9–14, rama `mvp2`): 10–14 semanas. Fase 7+8 entre MVP y MVP2 como cleanup post-launch.** diff --git a/plan/fase-10-spec-completion.md b/plan/fase-10-spec-completion.md index 6a3185d..737437c 100644 --- a/plan/fase-10-spec-completion.md +++ b/plan/fase-10-spec-completion.md @@ -1,6 +1,6 @@ ### Fase 10 — Spec completion ("cerrar la laguna del `analisis-funcional`") -**MVP2 · 2/7.** Segunda fase del ciclo MVP2 (rama `mvp2`, fases 9 → 15). +**MVP2 · 2/8.** Segunda fase del ciclo MVP2 (rama `mvp2`, fases 9 → 16). **Estado: ✅ Completa (2026-05-18). Cierra las 8 piezas de "completion debt" del audit 2026-04-22. Migraciones 017–021, pgTAP 010–013, 6 specs E2E + 2 integración + 12 unit. Ver `docs/history/fase-10-spec-completion.md`.** diff --git a/plan/fase-11-item-tags-importance.md b/plan/fase-11-item-tags-importance.md index 82a6f5b..87e785f 100644 --- a/plan/fase-11-item-tags-importance.md +++ b/plan/fase-11-item-tags-importance.md @@ -1,6 +1,6 @@ ### Fase 11 — Item tags + importance UX -**MVP2 · 3/7.** Tercera fase del ciclo MVP2 (rama `mvp2`, fases 9 → 15). +**MVP2 · 3/8.** Tercera fase del ciclo MVP2 (rama `mvp2`, fases 9 → 16). **Estado: 📋 Planificada. Cierra dos huecos en la experiencia del item de compra: clasificación libre por tags (scope colectivo) y reordenación explícita por arrastrar (importance).** diff --git a/plan/fase-12-section-visibility.md b/plan/fase-12-section-visibility.md index 16b910c..e781474 100644 --- a/plan/fase-12-section-visibility.md +++ b/plan/fase-12-section-visibility.md @@ -1,6 +1,6 @@ ### Fase 12 — Section visibility (feature toggles user + colectivo) -**MVP2 · 4/7.** Cuarta fase del ciclo MVP2 (rama `mvp2`, fases 9 → 15). +**MVP2 · 4/8.** Cuarta fase del ciclo MVP2 (rama `mvp2`, fases 9 → 16). **Estado: 📋 Planificada. Permite a cada usuario ocultar secciones top-level que no usa, y a admins de colectivo ocultarlas para todo el colectivo. Pre-requisito para Fase 13 (server admin reusa el mismo modelo a nivel servidor).** diff --git a/plan/fase-13-server-admin.md b/plan/fase-13-server-admin.md index 7778e26..f2ca212 100644 --- a/plan/fase-13-server-admin.md +++ b/plan/fase-13-server-admin.md @@ -1,6 +1,6 @@ ### Fase 13 — Server administration -**MVP2 · 5/7.** Quinta fase del ciclo MVP2 (rama `mvp2`, fases 9 → 15). +**MVP2 · 5/8.** Quinta fase del ciclo MVP2 (rama `mvp2`, fases 9 → 16). **Estado: 📋 Planificada. Mayor salto de scope hasta ahora — introduce un rol global ortogonal a los roles de colectivo, un área `/admin` nueva, RPCs con privilegios elevados, y un audit log. Requiere Fase 12 para los toggles de visibilidad a nivel servidor.** diff --git a/plan/fase-14-pwa-hardening.md b/plan/fase-14-pwa-hardening.md index b7dbef5..675eaaf 100644 --- a/plan/fase-14-pwa-hardening.md +++ b/plan/fase-14-pwa-hardening.md @@ -1,6 +1,6 @@ ### Fase 14 — PWA hardening (auto-update + offline polish + version chip) -**MVP2 · 6/7.** Última fase del ciclo MVP2 (rama `mvp2`, fases 9 → 15). +**MVP2 · 6/8.** Última fase del ciclo MVP2 (rama `mvp2`, fases 9 → 16). **Estado: 📋 Planificada. Pulido del PWA que Fase 6 dejó funcional pero crudo. Sin migraciones de DB. Depende de Fase 9.3 (wiring de `sync_conflicts`) para el badge de conflictos.** diff --git a/plan/fase-15-common-items.md b/plan/fase-15-common-items.md index ba2c46e..53efde4 100644 --- a/plan/fase-15-common-items.md +++ b/plan/fase-15-common-items.md @@ -1,6 +1,6 @@ ### Fase 15 — Common items management (weighted suggestions + exclude-on-list) -**MVP2 · 7/7.** Añadida después del cierre de MVP2 (2026-05-18) para integrar dos peticiones concretas sobre items frecuentes; reabre el ciclo con una fase extra. +**MVP2 · 7/8.** Añadida después del cierre de MVP2 (2026-05-18) para integrar dos peticiones concretas sobre items frecuentes; reabre el ciclo con una fase extra. **Estado: 📋 Planificada.** Atiende dos peticiones concretas sobre el flujo "añadir item a una lista": diff --git a/plan/fase-16-spinner.md b/plan/fase-16-spinner.md new file mode 100644 index 0000000..1883d24 --- /dev/null +++ b/plan/fase-16-spinner.md @@ -0,0 +1,122 @@ +### Fase 16 — Loading spinner (visual feedback) + +**MVP2 · 8/8.** Añadida después del cierre de Fase 15 para cubrir la falta de feedback visual durante operaciones asíncronas. + +**Estado: 📋 Planificada.** Atiende una petición concreta: cuando algo carga, hoy se muestra solo texto `Loading…`. Reemplazar por un círculo animado simple y reutilizable. + +**Objetivo: un componente `Spinner` minimal (SVG circle + animación CSS), integrado en los 5 sitios actuales que ya muestran `m.loading()` o un estado `loading`. Nada de skeletons, nada de transiciones complejas. La UX es: si tarda >100ms, hay un círculo girando. Punto.** + +--- + +**Estado actual (auditado 2026-05-18):** +- `apps/web/src/routes/(app)/+layout.svelte:151` — splash de auth con `{m.loading()}`, sin indicador visual. +- `apps/web/src/routes/(app)/search/+page.svelte:122` — texto plano `m.loading()` durante búsqueda. +- `apps/web/src/routes/(app)/notes/archive/+page.svelte` — `let loading = $state(true)`, sin spinner. +- `apps/web/src/lib/components/CreateCollectiveModal.svelte:109` — botón muestra `m.loading()` cuando `creating=true`, sin spinner. +- Stores con flag dedicado: `authLoading`, `notesLoading`, `tagsLoading`, `commonItemsLoading`. Buena base para integración futura. + +**Decisiones clave:** +- **SVG + CSS keyframes**, no dependencias. Stroke-dasharray con animación `rotate(360deg)` infinita. +- Props: `size = 'sm' | 'md' | 'lg'` (16 / 24 / 40 px), `class = ''` para Tailwind overrides. Color hereda `currentColor` para integrar con el theme system (Fase 9). +- Render bloque: `