# Fase 2b — what has been built

- `supabase/migrations/007_realtime_publication.sql` — adds shopping_items + shopping_lists to `supabase_realtime` publication with `REPLICA IDENTITY FULL`
- `apps/web/src/lib/stores/realtimeSync.ts` — `subscribeToList(listId, onEvent)` postgres_changes subscription helper; `applyItemEvent` reducer for INSERT/UPDATE/DELETE
- `/lists/[id]/+page.svelte` — wired to realtimeSync with pendingTempIds echo-deduplication. Mutations use client-generated UUIDs so offline retries are idempotent (PK-conflict = success)
- `apps/web/src/lib/sync/queue.ts` — `SyncQueue` class (IDB-backed pending_ops, FIFO flush, MAX_ATTEMPTS retry, PK-conflict-as-success)
- `apps/web/src/lib/sync/index.ts` — app-wide SyncQueue singleton; `enqueueOp`, `hydrateSyncState`, auto-flush on `window.online`
- `apps/web/src/lib/stores/syncStatus.ts` — derived store: `offline | syncing | synced` from `navigator.onLine` + `pendingOpsCount`
- `apps/web/src/lib/components/SyncBanner.svelte` — renders the offline/syncing indicator; mounted in list detail + session views
- `apps/web/src/routes/(app)/lists/[id]/session/+page.svelte` — Modo Compra full-screen view (fixed positioning overlays the sidebar), 56px toggles, flip animation, confirm modal, completeList → redirect
- `apps/web/messages/*.json` — `sync_offline`, `sync_syncing`, `list_start_session`
- `packages/test-utils/src/realtime-helpers.ts` — `subscribePostgresChanges(client, opts)` with `waitFor(predicate, ms)` — awaits SUBSCRIBED handshake, dedups events, leaks nothing between tests
- Vitest unit harness in `apps/web/` — `vitest.config.ts` (jsdom env) + `vitest.setup.ts` (fake-indexeddb/auto) + `pnpm --filter @colectivo/web test:unit`
- Realtime config gotchas documented in `project_realtime_config` memory: tenant name, DB_USER=supabase_admin, `SEED_SELF_HOST: "true"`, pre-created `realtime` schema, publication + REPLICA IDENTITY FULL
