Enable postgres_changes broadcasts on shopping_items / shopping_lists and prove the path end-to-end with Vitest Realtime tests and a pgTAP configuration check. Infra changes that were silently blocking events - Migration 007: shopping_items + shopping_lists added to supabase_realtime publication with REPLICA IDENTITY FULL (UPDATE/DELETE payloads need the full row so list_id-based filters work and RLS can evaluate DELETE against OLD) - Realtime service: DB_USER=supabase_admin (superuser; supabase_replication_admin lacks CREATE on the `realtime` schema that the service auto-creates per tenant) - Realtime service: SELF_HOST_TENANT_NAME=realtime so the seed tenant name matches the default supabase-js resolves for localhost URLs (was realtime-dev → TenantNotFound) Tests - pgTAP 004_realtime_publication.sql — P-01..P-04: publication membership and REPLICA IDENTITY FULL for both shopping tables - Vitest realtime-postgres-changes.test.ts — R-01 INSERT broadcast, R-02 UPDATE carries full row, R-03 list_id filter isolates events - test-utils realtime-helpers.ts — subscribePostgresChanges() returns a waitFor(predicate, timeout) helper that captures the SUBSCRIBED handshake before returning so mutations issued right after are not lost - createClientAs now calls realtime.setAuth(token) so the server evaluates RLS against the test user's JWT - Justfile test-db now globs supabase/tests/*.sql so new pgTAP files are picked up automatically Totals: 54→57 Vitest, 12→16 pgTAP, 15 Playwright = 88 tests green. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
78 lines
5.6 KiB
Markdown
78 lines
5.6 KiB
Markdown
### Fase 2b — Sincronización en Tiempo Real y Modo Compra
|
||
**Estado: 🚧 En curso — tests 2b.0 verdes (Realtime infra lista)**
|
||
**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
|
||
|
||
**Prep infra (previa a los tests):**
|
||
|
||
- [x] Migración `007_realtime_publication.sql` — añade `shopping_items` y `shopping_lists` a la publicación `supabase_realtime`; pone `REPLICA IDENTITY FULL` en ambas
|
||
- [x] Docker Compose: `DB_USER: supabase_admin` (superuser) + `SELF_HOST_TENANT_NAME: realtime` para Realtime (ver memoria `project_realtime_config`)
|
||
|
||
**Vitest (`packages/test-utils/tests/`):**
|
||
|
||
- [x] `realtime-postgres-changes.test.ts` — R-01 INSERT broadcast, R-02 UPDATE con fila completa, R-03 filtro `list_id` aísla eventos de otras listas ✅
|
||
- [ ] `realtime-presence.test.ts` — dos clientes (Ana + Borja) entran en la misma lista; ambos aparecen en el canal Presence; salir del canal elimina al usuario
|
||
- [ ] `realtime-isolation.test.ts` — Ana y David (guest del mismo colectivo) reciben eventos; Eva (fuera del colectivo) NO recibe nada (RLS)
|
||
- [ ] `sync-queue.test.ts` (unit, con mock de `idb`) — cada mutación escribe en `pending_ops` antes de llamar a Supabase; al recibir la confirmación se elimina de la cola; si falla se mantiene para reintento
|
||
- [ ] `sync-flush.test.ts` (unit) — al disparar el evento `online` se 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]/session` muestra el layout full-screen sin sidebar
|
||
- [ ] `session.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 a `completed`, redirige a `/lists`
|
||
- [ ] `realtime.test.ts` — dos contextos de navegador: Ana crea ítem, Borja lo ve sin refrescar; Ana marca ítem, el avatar de Borja aparece en Presence
|
||
- [ ] `offline.test.ts` — O-01 con `context.setOffline(true)`: mutaciones locales + banner "sin conexión"; O-02 al volver online: sync pendiente + banner desaparece
|
||
|
||
**pgTAP (`supabase/tests/`):**
|
||
|
||
- [x] `004_realtime_publication.sql` — P-01..P-04: publication + REPLICA IDENTITY FULL asegurados ✅
|
||
|
||
#### 2b.1 Sincronización Realtime
|
||
|
||
- [ ] Suscripción a `Postgres Changes` de Supabase Realtime sobre `shopping_items` filtrada por `list_id`
|
||
- [ ] Suscripción a `Presence` de 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_at` remoto > 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_ops` antes de llamar a Supabase
|
||
- [ ] Service Worker (`@vite-pwa/sveltekit` + Workbox):
|
||
- **NOTA TÉCNICA:** SvelteKit intercepta `src/service-worker.ts` y bloquea imports externos (solo permite `$service-worker` y `$env/static/public`). Para usar Workbox con `injectManifest` hay que nombrar el fichero `src/sw.ts` (SvelteKit no lo reconoce como SW) y configurar `strategies: 'injectManifest'`, `filename: 'sw.ts'` en `vite.config.ts`. Actualmente el plugin usa `generateSW` (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_ops` al recuperar conexión
|
||
- Fallback manual `online` event (Safari no soporta Background Sync API)
|
||
- [ ] Fallback manual de sync: al detectar `online` en 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ón` cuando 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`.
|
||
|
||
---
|