Add a 4-layer test stack covering RLS, triggers, and UI flows for Fases 0–2a, then restructure every plan file so future fases start with tests and end with a verification gate. Test suite - packages/test-utils: Vitest integration tests signing HS256 JWTs via jose so each test acts as a specific seed user (createClientAs + createAdminClient) - supabase/tests: pgTAP for accept_invitation(), item_frequency trigger, and promote-on-admin-leave; each file self-installs pgtap extension - apps/web/tests: Playwright E2E with live Keycloak login per test (storageState caching doesn't rehydrate Supabase's session state reliably) - just test-all chains the three suites; test-db forwards POSTGRES_PASSWORD as PGPASSWORD with ON_ERROR_STOP=1 so failures abort the chain Supabase auth gotcha - PostgREST queries inside onAuthStateChange deadlock on GoTrue's navigator.locks auth lock (getAccessToken → getSession → initializePromise waits on the same lock that's held during event dispatch). Fix is two defenses: a pass-through lock in $lib/supabase, and a setTimeout(0) defer in root +layout.svelte to push loadUserCollectives out of the callback's microtask chain. Either alone is insufficient; both together unblock the Playwright suite. Env key rotation - apps/web/.env.development had a stale demo anon key signed with a different secret than root .env; Vite inlined that into the browser bundle so Kong (which uses the root .env value) rejected every request with 401. Aligned the two files and added a memory entry to flag this for the next rotation. Plan restructure (TDD) - Every fase now opens with X.0 Tests primero and closes with X.Z Verificación final. Completed fases (0, 1, 2a) show the pattern retroactively with the tests that currently cover them; pending fases (2b, 3, 4) list the tests to write before implementation. Docs - CLAUDE.md status line reports 54 + 12 + 15 = 81 green tests, adds gotchas #11 (auth-lock deadlock) and #12 (no storageState caching) - README.md adds a TDD methodology section and the test-all command - .gitignore excludes Playwright's generated reports and auth state 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5.5 KiB
5.5 KiB
Fase 2b — Sincronización en Tiempo Real y Modo Compra
Estado: ⏳ Pendiente
Duración estimada: 2–3 semanas
Objetivo: el diferencial del producto. Es la fase más compleja técnicamente.
Esta fase sigue TDD: primero los tests, al final la verificación. Ninguna tarea de implementación se da por hecha hasta que todos los tests pasan (just test-all).
2b.0 Tests primero — escribir antes de implementar
Vitest (packages/test-utils/tests/):
realtime-presence.test.ts— dos clientes (Ana + Borja) entran en la misma lista; ambos aparecen en el canal Presence; salir del canal elimina al usuariorealtime-postgres-changes.test.ts— Ana hace INSERT/UPDATE/DELETE enshopping_items; Borja (suscrito al canal) recibe el evento en < 1srealtime-isolation.test.ts— Ana y David (guest del mismo colectivo) están suscritos a la misma lista; Eva (fuera del colectivo) NO recibe nada aunque intente suscribirse (RLS ensupabase_realtime.broadcast)sync-queue.test.ts(unit, con mock deidb) — cada mutación escribe enpending_opsantes de llamar a Supabase; al recibir la confirmación se elimina de la cola; si falla se mantiene para reintentosync-flush.test.ts(unit) — al disparar el eventoonlinese flushea la cola respetando el orden original; colisiones resueltas con last-write-wins
Playwright (apps/web/tests/e2e/):
session.test.ts— S-01: abrir Modo Compra en/lists/[id]/sessionmuestra el layout full-screen sin sidebarsession.test.ts— S-02: marcar un ítem lo mueve a la sección CHECKED con animación (no se duplica)session.test.ts— S-03: botón "Finish shopping" → confirmación modal → lista pasa acompleted, redirige a/listsrealtime.test.ts— R-01 (dos contextos de navegador): Ana crea ítem, Borja lo ve sin refrescar la páginarealtime.test.ts— R-02: Ana marca ítem, avatar de Borja aparece en el indicador de Presenceoffline.test.ts— O-01: concontext.setOffline(true), añadir/marcar ítems sigue funcionando localmente y aparece el banner "sin conexión"offline.test.ts— O-02: al volver online (setOffline(false)), los cambios pendientes se sincronizan y el banner desaparece
pgTAP (supabase/tests/):
004_realtime_rls.sql— verificar querealtime.messagesrespeta las políticas RLS heredadas deshopping_items(un miembro sólo ve cambios de sus colectivos)
2b.1 Sincronización Realtime
- Suscripción a
Postgres Changesde Supabase Realtime sobreshopping_itemsfiltrada porlist_id - Suscripción a
Presencede Supabase Realtime para indicador de miembros presentes en lista - Store Svelte
realtimeSync:- Gestiona el ciclo de vida de las suscripciones (subscribe/unsubscribe al entrar/salir de la lista)
- Aplica cambios remotos al estado local sin causar bucles
- Detecta conflicto last-write-wins: si el
updated_atremoto > local, aplica remoto y muestra aviso visual
2b.2 Offline-First
- Instalación y configuración de
idb(wrapper de IndexedDB) - Esquema local en IndexedDB:
pending_ops,lists_cache,items_cache - Cola de operaciones pendientes: cada mutación se escribe en
pending_opsantes de llamar a Supabase - Service Worker (
@vite-pwa/sveltekit+ Workbox):- NOTA TÉCNICA: SvelteKit intercepta
src/service-worker.tsy bloquea imports externos (solo permite$service-workery$env/static/public). Para usar Workbox coninjectManifesthay que nombrar el ficherosrc/sw.ts(SvelteKit no lo reconoce como SW) y configurarstrategies: 'injectManifest',filename: 'sw.ts'envite.config.ts. Actualmente el plugin usagenerateSW(SW auto-generado, precaching básico). - Cache-first para assets estáticos
- Network-first con fallback a caché para rutas de la app
- Background sync para flush de
pending_opsal recuperar conexión - Fallback manual
onlineevent (Safari no soporta Background Sync API)
- NOTA TÉCNICA: SvelteKit intercepta
- Fallback manual de sync: al detectar
onlineen el evento del navegador, flush de la cola (cubre Safari que no soporta Background Sync API) - Indicador de estado de sincronización en la UI:
synced | syncing | offline
2b.3 Modo Compra
- Ruta
/lists/[id]/session— UI dedicada al Modo Compra:- Ítems en formato grande (mínimo 64px de altura), optimizado para pulsar con el pulgar
- Reordenamiento automático al marcar: pendientes arriba, marcados abajo (animación fluida con
flip) - Indicador de presencia: avatares de miembros que tienen la lista abierta simultáneamente
- Banner
sin conexióncuando no hay red (los cambios se guardan en local) - Botón prominente "Finish shopping" — confirmación modal → lista pasa a
completed - Notificación push (si el usuario ha dado permiso) a otros miembros al finalizar
- Rendimiento: 60fps en gama media, tap < 100ms (validar en Chrome DevTools con throttling)
2b.Z Verificación final — todos los tests verdes
just test-all→ 0 failures, incluyendo los nuevos de 2b.0- Prueba manual de sincronización: dos dispositivos, misma lista, cambios visibles en < 1 segundo
- Prueba manual de offline: desconectar red, mutar, reconectar, verificar sync
Criterio de aceptación: dos personas en el mismo supermercado, con cobertura inestable, pueden marcar ítems desde sus móviles y verse los cambios mutuamente en tiempo real. Si se pierde la conexión, los cambios locales se sincronizan al recuperarla. Toda la suite pasa con just test-all.