Commit Graph

4 Commits

Author SHA1 Message Date
6a02d6d5ab fix(fase-15): cap excludeNames at 200 + update D-06 for excluded suggestions
Two regressions surfaced when running the full e2e suite against the
shared dev DB (which accumulates hundreds of items in the seed list
over the lifetime of the test stack):

  - fetchSuggestions URL ballooned past Kong's ~16 KiB request URI cap
    when excludeNames carried 300+ names, freezing the query and
    breaking the realtime + reorder-items + section-visibility tests
    (which co-locate two browser contexts hitting the same list). Cap
    at 200 names; past that the filter degrades to a no-op (the
    dropdown may surface a few names already on the list, which is
    fine UX for a long-running shared list).
  - items.test.ts D-06 (frequency suggestions visible) was asserting
    on "milk" which is in the seed list — Fase 15 correctly suppresses
    it now. Route the test through a freshly-created empty list so
    the strip is deterministic.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 12:16:15 +02:00
1cb408acc2 feat(fase-5.10): /lists/[id] row redesign — buttons + swipe-toggle + overlay
Replaces the checkbox/hover-delete model with an explicit gesture+overlay
flow on /lists/[id].

Row anatomy (new)
  [drag-handle]  [name as button]  [qty stepper  − N +]
  - No checkbox in normal mode.
  - Checked items render with strikethrough + muted text.
  - Drag handle always visible on mobile; revealed on hover on desktop
    (md:opacity-0 md:group-hover:opacity-100). The handle owns the drag
    (pointerdown → dragEnabled=true), so row swipes + taps keep working.

Swipe gestures (symmetric toggle, no delete)
  unchecked + swipe RIGHT → mark checked (green success reveal)
  checked   + swipe LEFT  → mark unchecked (neutral reveal)
  opposite direction for each state is a no-op (snaps back).
  SWIPE_COMMIT_THRESHOLD=120 (was 200 for delete); REVEAL_WIDTH=96.
  Symmetry means undo is the inverse swipe — no undoQueue on toggles.

Double-tap overlay (name + delete + confirm)
  Short tap: no-op. Two taps within DOUBLE_TAP_WINDOW_MS (300 ms) open a
  full-screen dialog (bottom sheet on mobile) with:
    - X close button (top-right)
    - name input
    - Delete button (red) → scheduleUndoable → UndoToast
    - Confirm button (primary) → updateItem({ name })
  Click outside / Escape / X all dismiss without saving.

Tests
  items.test.ts
    D-03 rewritten: double-tap opens overlay, value matches, X closes.
    D-04 rewritten: overlay Delete removes the row; toast locator scoped
      out of itemRow so it doesn't shadow the assertion.
  realtime.test.ts
    R-E-02 updated: Ana renames via overlay → Borja sees new name over
      Realtime UPDATE. (The check/uncheck path is now gestural only and
      chromium touch emulation is too flaky to drive it in E2E; Vitest
      integration R-02 still covers the UPDATE-row-payload invariant.)
  mobile-swipe-delete.test.ts stays describe.skip — will be renamed and
  rewritten for the new semantics once WebKit install lands in CI.

i18n
  list_qty_decrease, list_qty_increase, list_reorder_handle,
  list_edit_item, list_confirm, list_close — en/es.

Verification
  just test-e2e → 40 passed, 2 skipped (same as pre-change).
  Type-check clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 19:20:55 +02:00
5a63e5fc4a feat(dev): expose full auth stack on LAN IP for on-device preview
The dev stack previously mixed two hostnames (`localhost` in some places,
`keycloak` in others). That only worked because the laptop's /etc/hosts
maps `keycloak` to 127.0.0.1. Phones, tablets, or a second laptop had no
such mapping, so the OAuth flow broke the moment you tried to log in from
any non-laptop device.

Switched the SPA, Kong, GoTrue, and Keycloak client to all agree on a
single external host — the laptop's LAN IP (192.168.1.167) — so the full
Keycloak → GoTrue → Supabase → SvelteKit round-trip works from any device
on the same Wi-Fi.

Changes
  .env (root, ungitignored): PUBLIC_SUPABASE_URL / PUBLIC_KEYCLOAK_URL /
    PUBLIC_APP_URL now use the LAN IP (updated locally; tracked via
    apps/web/.env.development below).
  apps/web/.env.development: mirror the LAN IP so `$env/static/public`
    resolves the same on laptop and phone.
  apps/web/vite.config.ts: `server.host: true` — bind Vite to 0.0.0.0.
  apps/web/playwright.config.ts: `baseURL: PUBLIC_APP_URL ?? localhost`
    so E2E uses the same origin the stack is configured with.
  apps/web/tests/fixtures/login.ts:
    - wait for the collective button in the sidebar to render before
      returning (LAN-IP latency surfaced a pre-existing race in
      `handleCreate` where Enter fired before `$currentCollective`
      hydrated, silently no-op'ing).
    - derive expected app origin from PUBLIC_APP_URL.
  apps/web/tests/e2e/items.test.ts: scope D-04 delete assertion to the
    `[role="listitem"]` locator — with undoQueue enabled the toast text
    "Deleted <name>" also matches `getByText(itemName)` and shadowed the
    original check.
  infra/docker-compose.dev.yml:
    - GOTRUE_EXTERNAL_KEYCLOAK_URL and REDIRECT_URI now read from
      PUBLIC_KEYCLOAK_URL / PUBLIC_SUPABASE_URL so they follow the same
      host as the rest of the stack.
    - NEW: GOTRUE_URI_ALLOW_LIST with `/auth/callback` on both the LAN
      IP and localhost. Without an explicit allow-list GoTrue rejected
      `redirect_to=.../auth/callback` and fell back to SITE_URL root,
      stranding `?code=` at `/` and re-triggering signIn → infinite loop.
  keycloak/realm-export.json: `colectivo-web` client gains LAN-IP
    redirectUris and webOrigins (alongside the existing localhost
    entries). Persisted + live-applied via admin API.
  .gitignore: add .claude/ (per-project scheduler runtime state).

Verification
  just test-all → 224 passed, 4 skipped:
    34 pgTAP
    140 Vitest integration + 2 skipped (Realtime presence, upstream bug)
    11 Vitest unit
    39 Playwright + 2 skipped (mobile-swipe touch, WebKit-only)
  Phone sanity check: open http://192.168.1.167:5173 on a LAN device,
  log in as a seed user, full RLS-respecting session.

Caveats
  LAN IP (192.168.1.167) is DHCP-assigned — if it rotates, rerun the
  Keycloak admin API update (realm-export.json needs a new entry) and
  update the three PUBLIC_* URLs. Consider a Tailscale magic-DNS name
  as a stable replacement for the prod-deploy sprint.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 16:09:32 +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