Commit Graph

9 Commits

Author SHA1 Message Date
ee962991e1 docs(fase-5): plan mobile UX rewrite ("Monolith Editorial")
- Add plan/fase-5-mobile-ux.md with 7-chunk breakdown:
    5.0 mobile tests (Playwright mobile-shell/masthead/swipe-delete + Vitest helpers)
    5.1 responsive shell (MobileTopBar + BottomTabBar + MobileDrawer)
    5.2 ScreenMasthead + px-4/md:px-6 pass
    5.3 /lists mobile cards
    5.4 /lists/[id] rows + red-zone swipe-delete
    5.5 undoQueue store + UndoToast
    5.6 scrollable frequency chips
    5.7 tasks/notes/search shell + masthead
    5.Z verify 211 + new M-series green
- Explicit gap analysis vs. current app + deliberate exclusions
    (no priority badges, no voice search, no fake meta like "Dairy · Weekly")
- Index design/ directory: Stitch mockups per screen (mobile + desktop variants),
  plus the "Monolith Editorial" DESIGN.md directing tonal shifts, masthead
  typography, glassmorphism, and the no-line rule
- Relocate DESIGN.md under design/slate_collective/DESIGN.md (old entry point) —
  the current design direction lives at design/DESIGN.md
- Reference design/ from CLAUDE.md + add Fase 5 to README phase index

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 13:36:52 +02:00
2806e06db2 feat(fase-4): global search + RLS isolation audit (211 tests green)
4.0 Tests primero
  Vitest: search.test.ts (10 — S-01..S-04), rls-audit.test.ts (42 —
    3 intruders × 14 cross-collective ops proving zero leakage)
  pgTAP: 007_search_tsvector.sql (9 — columns, GIN indexes, function
    signature, generated-vector invariant)
  Playwright: search.test.ts (2 — SR-01 happy-path, SR-02 filter toggle)

4.1 Búsqueda global
  Migration 010_search_vectors.sql: STORED GENERATED tsvector columns
    on shopping_items/tasks/notes + GIN indexes + search_result_type
    enum + search_in_collective() SECURITY INVOKER function (RLS-aware,
    trash-excluded per RN-08) + GRANT EXECUTE to anon/authenticated.
    Uses `simple` config (no stemmer — bilingual en/es).
  Store apps/web/src/lib/stores/search.ts — RPC wrapper
  /search: debounced input (300ms, ≥2 chars), type filter chips with
    multi-toggle, results grouped per type with data-testid hooks for
    Playwright, deep-link per type (shopping_item → /lists/[id], task →
    /tasks/[id], note → /notes/[id])

4.4 Security
  rls-audit.test.ts replaces the curl-based manual audit with a
    parametrised matrix — zero cross-collective reads/writes under
    three different attacker roles

Realtime flake hardening
  Add 250ms settle after SUBSCRIBED in realtime-helpers.ts — the Phoenix
    socket reports SUBSCRIBED slightly before the backend finishes
    propagating the filter to the WAL subscription, so mutations fired
    immediately after subscribe could miss the filter under load.

4.Z Verification
  just test-all → 211 verdes, 2 skipped
    34 pgTAP (was 25, +9 search)
    140 Vitest integration (was 88, +10 search +42 rls-audit; 2 skipped)
    6 Vitest unit (unchanged)
    31 Playwright (was 29, +2 search)
  Deferred (prod-deploy scope):
    PWA install/push (needs real iOS/Android hardware)
    Kong rate-limit plugin config
    JWT_SECRET / ANON_KEY rotation
    Lighthouse PWA ≥ 90 manual validation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 12:47:38 +02:00
104eeba02e feat(fase-3): Tareas + Notas — TDD complete (148 tests green)
3.0 Tests primero
  pgTAP: 005_tasks_rls.sql (5 — completion CHECK), 006_notes_trash_purge.sql (4 — purge SQL inline + pin/archive CHECK)
  Vitest RLS: rls-tasks.test.ts (14), rls-notes.test.ts (15)
  Playwright: tasks.test.ts (3), notes.test.ts (4)

3.1 Tareas
  Migration 008_tasks.sql: task_lists + tasks + task_list_collective_id() helper
  CHECK constraint: is_completed ⇔ completed_by ⇔ completed_at
  RLS by role + cross-collective isolation
  Store apps/web/src/lib/stores/tasks.ts (optimistic CRUD + reorder)
  /tasks overview (grid + sticky create input + 5s polling)
  /tasks/[id] detail with svelte-dnd-action reorder, flip animation,
    inline edit (dblclick), "Completed by …" attribution via lazy-loaded
    collective_members

3.2 Notas
  Migration 009_notes.sql: notes + note_color enum (8) + pin/archive CHECK
    + 7d trash window in RLS + fn_notes_touch trigger + pg_cron purge job
  Store apps/web/src/lib/stores/notes.ts (CRUD + pin/archive/trash/duplicate)
  /notes board (pinned section testid notes-pinned + 30s polling)
  /notes/[id] editor (title + textarea, 500ms debounced autosave,
    color picker, pin/archive/duplicate/trash actions)
  /notes/archive (restore + send to trash)
  /notes/trash (restore + permanent delete)

3.Z Verification
  just test-all → 148 verdes, 2 skipped:
    25 pgTAP (was 16, +9)
    88 Vitest integration (was 59, +29; 2 presence skipped)
    6 Vitest unit (unchanged)
    29 Playwright (was 22, +3 tasks +4 notes)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:28:19 +02:00
e7a961a66d feat(fase-2b): Realtime sync + offline queue + Modo Compra
Fase 2b closes the critical-path product differentiator. Two sessions on the
same list see each other's changes in real time, mutations survive network
drops and sync when reconnected, and a dedicated full-screen shopping view
optimises the in-store experience.

2b.1 Realtime
- `$lib/stores/realtimeSync.ts` wraps `postgres_changes` with an `applyItemEvent`
  reducer (INSERT/UPDATE/DELETE → next items[]) and a `subscribeToList` helper
  that awaits the SUBSCRIBED handshake before returning
- `/lists/[id]` subscribes in onMount, unsubscribes in onDestroy. Uses a
  `pendingTempIds` set to deduplicate own-mutation echoes against optimistic
  rows (matches by name+created_by+sort_order)
- Client-generated UUIDs for new inserts make the path idempotent: if the
  server response is lost and we retry, the second POST hits PK-conflict
  which the queue treats as success
- CHECKED section div now carries role="listitem" so Playwright locators
  follow the item across sections

2b.2 Offline queue
- `$lib/sync/queue.ts` — SyncQueue class backed by IndexedDB (idb), FIFO flush,
  MAX_ATTEMPTS=5 retry budget, PK-conflict-as-success short-circuit
- `$lib/sync/index.ts` — app-wide singleton, window.online listener flushes
  automatically, hydrateSyncState() at page load restores pendingOpsCount
- `$lib/stores/syncStatus.ts` — derived store (offline | syncing | synced)
  tracking navigator.onLine + queue depth
- SyncBanner component renders the offline/syncing indicator in the detail
  and session views
- handleAdd enqueues on failure instead of reverting, so an offline mutation
  keeps its optimistic row and syncs on reconnect

2b.3 Modo Compra
- `/lists/[id]/session/+page.svelte` — full-screen overlay (fixed inset-0
  z-50) that covers the sidebar while keeping the normal auth routing.
  56×56 toggles, flip animation shuffling items between TO BUY / CHECKED,
  Finish Shopping confirmation modal → completeList → goto('/lists')
- Link from `/lists/[id]` header (data-testid=start-session) with the
  new `list_start_session` message

Testing infra
- apps/web gets its own Vitest config (jsdom + fake-indexeddb/auto) and a
  new `pnpm test:unit` script. `just test-all` now chains pgTAP → integration
  → unit → e2e so a single command is the gate.
- packages/test-utils/tests/sync-queue.test.ts (placeholder scaffold) removed —
  replaced by `apps/web/src/lib/sync/queue.test.ts` co-located with the module

Totals: 103 tests green
  16 pgTAP
  59 Vitest integration (in packages/test-utils)
   6 Vitest unit (in apps/web — the SyncQueue)
  22 Playwright E2E (5 auth + 4 lists + 6 items + 2 realtime + 2 offline + 3 session)
   2 skipped (realtime-presence — upstream bug, unchanged)

Documented in plan/fase-2b and CLAUDE.md.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 03:32:15 +02:00
f396897cb5 test: full test suite (Vitest + pgTAP + Playwright) + TDD plan restructure
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>
2026-04-13 01:40:16 +02:00
ce1bcfb7a6 feat(design): apply Monolith Editorial design system to all Fase 1 screens
- Remove all 1px border separators (sidebar border-r, page header border-b,
  section border-t, member list divide-y, invite card border). Separation is
  now achieved via background color shifts and whitespace per the "No-Line" rule.
- Fix sidebar: bg-surface-raised vs bg-background creates tonal separation.
- Correct dark background to #020617 (slate-950) per spec.
- Add surface-container-low, text-primary/secondary/muted, destructive tokens.
- Fix CSS variable color space: all tokens are RGB triplets; switch Tailwind
  config and app.css from hsl() to rgb() to prevent yellow/warm color bleed.
- Replace emoji nav icons with Lucide icons at 16px / 1.5 stroke-width.
- Apply Masthead typography (uppercase Label-MD + Title-LG) to all page headers
  and section labels across lists, tasks, notes, search, settings, manage.
- Destructive action (sign out) uses bg-destructive per spec.
- Add `just serve` command: builds and runs the prod Node server at :3000
  against the dev Docker stack for production build testing.
- Add nav_collective i18n key (en + es).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 22:09:25 +02:00
b297d253d9 fix(auth+pwa): resolve stuck-loading race condition and PWA build error
Auth: `getSession()` in a SvelteKit load function races with Supabase's async
localStorage init and can return null for a valid session, triggering a spurious
login() redirect. Moved auth redirect to (app)/+layout.svelte via $effect, which
correctly waits for onAuthStateChange to fire. Also fixed collective data never
loading on page refresh (INITIAL_SESSION event, not SIGNED_IN).

PWA: SvelteKit blocks Workbox imports in src/service-worker.ts, preventing
@vite-pwa/sveltekit from finding the compiled SW output. Switched to generateSW
strategy (plugin auto-generates the SW). Custom SW deferred to Fase 2b using
src/sw.ts (a filename SvelteKit does not intercept).

Docs: added gotchas 6–8 to CLAUDE.md covering both root causes so they are not
reintroduced. Updated plan/fase-2b with the sw.ts workaround note.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 21:44:14 +02:00
973f9e413c Fase 0: scaffold monorepo, SvelteKit skeleton, and dev infrastructure
- Turborepo + pnpm workspaces with apps/web and packages/types
- SvelteKit app: Tailwind, Paraglide i18n (en/es), keycloak-js, Supabase client,
  auth/collective stores, PWA service worker skeleton, all route placeholders
- packages/types: domain types (User, Collective, ShoppingList, etc.) and
  database.ts placeholder for generated types
- Justfile with dev, db-*, kc-*, build, backup, restore, deploy recipes
- infra/docker-compose.dev.yml: Postgres 15, GoTrue, PostgREST, Realtime v2.83,
  Storage, Kong (port 8001), Studio, Keycloak 24
- infra/docker-compose.prod.yml, kong.yml, db-init scripts, backup/deploy scripts
- keycloak/realm-export.json with colectivo realm and 5 dev test users
- supabase/config.toml and seed.sql with sample collective and items
- GitHub Actions: ci.yml (lint+typecheck+build) and deploy.yml (GHCR + SSH)
- .env.example documenting all required variables
- Fixed docker-compose issues: Studio image tag, Kong port conflict (8001),
  internal role passwords init script, Realtime METRICS_JWT_SECRET/APP_NAME

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 14:37:51 +02:00
f5903ef442 Add planning docs, design system, and Stitch mockups
- CLAUDE.md: project guidance (stack, domain model, auth, i18n, gotchas)
- DESIGN.md: full design system spec derived from Stitch mockups (slate
  monochrome, Inter, shadcn-svelte, screen patterns for all 5 views)
- README.md: development plan with Justfile commands
- analysis/: functional spec (domain model, use cases, business rules)
- plan/: phase-by-phase implementation plans (Fase 0–4)
- .stitch/designs/: generated mockups (lists, shopping session, notes,
  tasks, list detail with frequency chips)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 06:35:07 +02:00