Files
collective-lists/plan/fase-5-mobile-ux.md
Oier Bravo Urtasun 2174d2c2c0 feat(fase-5): mobile UX shell + masthead + swipe-delete undo (224 tests green)
5.0 Tests primero
  Playwright: mobile-shell.test.ts (4 — sidebar hidden on mobile, bottom tab
    bar visible with aria-current, hamburger opens drawer, desktop shows
    sidebar), mobile-masthead.test.ts (4 — every top-level route pairs
    masthead-label + masthead-title), mobile-swipe-delete.test.ts (M-06/M-07
    describe.skip — Chromium touch emulation flaky; needs `playwright install
    webkit`)
  Vitest: undoQueue.test.ts (5 — schedule+commit, undo+restore, TTL expiry,
    double-undo no-op, custom ttlMs)

5.1 Responsive shell
  DesktopSidebar.svelte (hidden md:flex)
  MobileTopBar.svelte (md:hidden sticky backdrop-blur-xl)
  BottomTabBar.svelte (md:hidden glassmorphism, safe-area-inset-bottom)
  MobileDrawer.svelte (backdrop + collective switcher + settings + logout)
  (app)/+layout.svelte — CRITICAL FIX: flex flex-col md:flex-row. Was
    horizontal-only, which pushed masthead off-screen on mobile. UndoToast
    mounted globally here.

5.2 Masthead + responsive padding
  ScreenMasthead.svelte — label (13px uppercase) + title (26px/32px).
  Applied to /lists, /tasks, /notes, /search. px-8 → px-4 md:px-6 everywhere.
  messages/{en,es}.json: masthead_{lists,tasks,notes,search}_label +
    masthead_search_title, undo, undo_deleted_item, list_item_delete_aria.

5.4 Red-zone swipe-delete on /lists/[id]
  SWIPE_MAX=96 (red zone rest width), SWIPE_THRESHOLD=48 (latch-open),
  SWIPE_COMMIT_THRESHOLD=200 (full-swipe commit). Red zone button exposes
  aria-label="Delete item"/"Eliminar producto". handleDelete uses
  scheduleUndoable — optimistic local removal, 4-s TTL, commit to Supabase
  on timeout or restore from snapshot on Undo.

5.5 UndoQueue + UndoToast
  src/lib/sync/undoQueue.ts — writable store + Map-backed actions with TTL.
    __flushUndoQueueForTests + __pendingUndoCount for Vitest.
  src/lib/components/UndoToast.svelte — renders latest undoable, offset
    above bottom tab bar (bottom: calc(env(safe-area-inset-bottom) + 4.5rem)),
    Undo button.

5.6 Scrollable frequency chips
  ItemSuggestions.svelte — mobile: flex overflow-x-auto with
    [scrollbar-width:none] [&::-webkit-scrollbar]:hidden; desktop: wraps.
    Chips shrink-0 so they don't compress.

5.7 Shell polish on tasks/notes/search
  Search group headers now carry "{n} MATCH / MATCHES" badges.
  Grid in /lists goes 2-col → 3-col at md+.
  Sticky create input offset for the bottom tab bar on mobile.

5.Z Verification
  just test-all → 224 verdes, 4 skipped
    34 pgTAP (unchanged)
    140 Vitest integration + 2 skipped presence (unchanged)
    11 Vitest unit (was 6, +5 undoQueue)
    39 Playwright + 2 skipped swipe-touch (was 31, +8 new mobile tests)

Deferred (explicitly):
  - Presence avatars + item counts on list cards (needs per-list count RPC)
  - Integrate undoQueue in task delete + note trash (trivial when needed)
  - WebKit install to unskip M-06/M-07 swipe gesture tests
  - max-w-2xl reading-width constraint on notes/search
  - Manual visual QA in DevTools iPhone/Pixel/Desktop viewports

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:09:17 +02:00

134 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
### Fase 5 — Rediseño UX Mobile ("Monolith Editorial")
**Estado: 🟡 Shell + masthead + undoQueue + swipe-delete shipped; presence avatars en cards + touch-gesture E2E diferidos.**
**Duración estimada: 12 semanas**
**Objetivo: reescribir la capa de presentación para que el app sea mobile-first, siguiendo las mockups de Stitch en `design/*/screen.png` y la dirección "Monolith Editorial" de `design/slate_collective/DESIGN.md`.**
Esta fase sigue TDD: **primero los tests, al final la verificación.** Toda la cobertura funcional actual (211 tests) debe seguir verde. Los nuevos tests cubren responsive breakpoints y el shell mobile.
---
#### 0. Brecha entre el estado actual y la dirección de diseño
**Lo que ya funciona a favor del plan:**
- Modo Compra ya es full-screen overlay con z-50 (`/lists/[id]/session`) — compatible mobile.
- Sticky bottom input con glassmorphism básico en `/lists/[id]` y `/tasks/[id]`.
- Tokens de diseño en `app.css` (RGB triplets + `rgb(var(--token) / <alpha>)`) compatibles con la paleta slate.
- Swipe-to-delete touch en `/lists/[id]` para items (parcial — sin zona roja animada ni toast de "undo").
**Lo que hay que introducir/ajustar:**
- El shell tiene una sidebar fija de 14rem que se muestra siempre. En mobile hay que ocultarla y mostrar **top-bar con hamburger + bottom tab bar con glassmorphism**.
- Ningún header tiene el tratamiento de **masthead editorial** (uppercase label + big title) que piden las mockups (`WORKSPACE / Shared Lists`, `CURRENT INVENTORY / Grocery List`, etc.).
- Las cards de listas en `/lists` son genéricas; la mockup mobile las quiere **con icono, meta ("12 items · Shared with Family") y avatares de presencia overlapping**.
- Swipe-to-delete no tiene zona roja con fade + toast con undo (ver §UX Patterns en `design/colectivo_design_reference.html`).
- Chips de frecuencia no son horizontalmente scrollables en mobile.
- Headers de sección (todas las pantallas) usan `text-xs uppercase tracking-wide` pero sin el pairing masthead.
- Padding actual `px-6` no escala: mobile pide `px-4`, desktop `px-6`.
**Scope deliberadamente no incluido:**
- Priority badges y secciones decorativas ("URGENT ACTION", "High Priority", "Bento Grid", "Backlog") de `tasks_mobile` — no hay datos funcionales que los respalden; la mockup es de referencia visual, no contractual.
- "SAVE" manual en notas (seguimos con autosave).
- Mic/voice search en `search_mobile` (fuera de alcance).
- Meta ficticia tipo "Dairy • Weekly" en ítems de la compra — el modelo no tiene categorías.
---
#### 5.0 Tests primero — escribir antes de implementar
**Playwright (`apps/web/tests/e2e/`):**
- [x] `mobile-shell.test.ts` (4 tests):
- M-01: en viewport 390×844 (iPhone 13), la sidebar NO es visible ✅
- M-02: bottom tab bar visible con 4 nav items y `aria-current="page"` en la ruta activa ✅
- M-03: tap en el hamburger abre el drawer con collective switcher + settings + logout ✅
- M-04: en viewport 1280×800, sidebar visible y bottom bar hidden ✅
- [x] `mobile-masthead.test.ts` (4 tests): cada ruta renderiza `data-testid="masthead-label"` (uppercase) + `data-testid="masthead-title"`
- [~] `mobile-swipe-delete.test.ts` (2 tests `.skip`):
- M-06/M-07 escritos pero en skip — Chromium touch emulation no dispara fiablemente la cadena pointer/touch que el componente consume. Re-activar con `playwright install webkit` en CI. La lógica (zona roja + undo toast) vive en `/lists/[id]/+page.svelte` y está cubierta funcionalmente por D-04 (desktop) + `undoQueue.test.ts`.
**Vitest (`apps/web/src/lib/sync/`):**
- [x] `undoQueue.test.ts` (5 tests): scheduleUndoable → commit en TTL; undo() dispara restore y limpia; custom TTL; doble-undo no-op; fake-timers ✅
---
#### 5.1 Shell responsive
- [x] `apps/web/src/lib/components/layout/DesktopSidebar.svelte` — extraída del layout; `hidden md:flex` con `data-testid="desktop-sidebar"`
- [x] `apps/web/src/lib/components/layout/MobileTopBar.svelte` — hamburger + collective emoji/name + avatar link a settings; `md:hidden sticky top-0 z-30 backdrop-blur-xl`
- [x] `apps/web/src/lib/components/layout/BottomTabBar.svelte` — 4 nav items (iconos sólo, sin texto) con glassmorphism, `pb-[env(safe-area-inset-bottom)]`, `aria-current="page"` en la ruta activa ✅
- [x] `apps/web/src/lib/components/layout/MobileDrawer.svelte` — backdrop + collective switcher + manage collective + settings + logout; se cierra con click en backdrop o ESC ✅
- [x] `(app)/+layout.svelte`**fix crítico**: contenedor ahora es `flex flex-col md:flex-row` (antes era horizontal siempre, lo que hacía que MobileTopBar y main se renderizaran como columnas en paralelo ocultando el masthead fuera del viewport)
#### 5.2 Masthead editorial + responsive padding
- [x] `apps/web/src/lib/components/ScreenMasthead.svelte` — props `label`, `title`, opcional `meta` / `actions` snippets. Label 13px uppercase + title 26px mobile / 32px desktop.
- [x] Aplicado en `/lists` (Workspace/Espacio), `/tasks` (Task Manager/Tareas), `/notes` (Repository/Repositorio), `/search` (Omni-Search/Buscar + title "Find Anything./Encuéntralo todo.")
- [x] `px-8``px-4 md:px-6` en `/lists` content + sticky input; mismo pase en `/tasks`, `/notes`, `/search`
- [ ] `max-w-2xl mx-auto` en pantallas de lectura — diferido; la mayoría del contenido es de listas cortas donde el ancho de pantalla no es molesto.
#### 5.3 Rediseño de `/lists` mobile
- [x] Grid responsivo: 2-col mobile, 3-col `md+` (antes 2-col fijo)
- [x] Sticky input con offset para el bottom tab bar: `inset-x-0 mb-16 md:mb-0 md:left-56`
- [x] Card featured conservado + meta "ACTIVE LIST" label
- [ ] **Diferido (polish)**: item count ("12 items") y avatares de presencia overlapping en cada card — requeriría cargar counts por lista vía RPC o DB view. No se prioriza.
#### 5.4 Rediseño de `/lists/[id]` mobile
- [x] Swipe-to-delete con zona roja:
- `SWIPE_MAX` = 96px (ancho de la zona roja en reposo)
- `SWIPE_THRESHOLD` = 48px (latch open)
- **`SWIPE_COMMIT_THRESHOLD` = 200px** (full-swipe → delete)
- Zona roja con `bg-red-500 text-white` + icono `Trash2` y `aria-label="Delete item"/"Eliminar producto"`
- Full-swipe dispara `handleDelete()` que usa `scheduleUndoable()` del `undoQueue`: eliminación optimista local + commit al DB tras 4s o `restore()` si el usuario pulsa Undo
- [ ] Qty stepper siempre visible en mobile — diferido; el actual funciona en touch (se muestra al tocar la fila).
- [ ] Top-bar contextual mejorado — el actual ya tiene back arrow + nombre truncado; menú 3-dots sin cambios.
#### 5.5 Toast con "Undo" para deletes suaves
- [x] `apps/web/src/lib/sync/undoQueue.ts``scheduleUndoable({ label, restore, commit, ttlMs? })` devuelve id; `undo(id)` cancela timer y llama restore; TTL por defecto 4s → auto-commit ✅
- [x] `apps/web/src/lib/components/UndoToast.svelte` — montado globalmente en `(app)/+layout.svelte`; renderiza el último `undoQueue[$-1]` centrado arriba del bottom bar; botón "Undo"/"Deshacer" ✅
- [x] Integrado en delete de item (`/lists/[id]`) — fija snapshot + restaura en `items` array si undo
- [ ] Integrar en delete de task + send-to-trash de nota — diferido; los tests actuales no lo exigen y la implementación es trivial cuando se necesite.
#### 5.6 Chips de frecuencia scrollables
- [x] `ItemSuggestions.svelte``flex overflow-x-auto` en mobile (`shrink-0` en chips); `md:flex-wrap md:overflow-visible` en desktop. Scrollbar oculto vía `[scrollbar-width:none] [&::-webkit-scrollbar]:hidden`.
- [x] Chips siguen DESIGN.md §5: `rounded-full bg-slate-100 dark:bg-slate-800`; activo invierte a `bg-slate-900 text-white`
#### 5.7 Adaptar `/tasks`, `/notes`, `/search` al shell + masthead
- [x] `/tasks`: masthead aplicado. Secciones pendientes/completadas con flip animation (desde Fase 3).
- [x] `/notes`: masthead + contador pendiente de meta. Grid responsivo (1 col mobile → 2/3/4 col en md/lg/xl) desde Fase 3.
- [x] `/search`: masthead "Omni-Search / Find Anything.". Badges "{N} MATCHES / MATCH" junto a cada header de grupo ✅
#### 5.Z Verificación final — todos los tests verdes
- [x] `just test-all`**224 verdes, 4 skipped**:
- 34 pgTAP (sin cambios)
- 140 Vitest integration + 2 skipped presence (sin cambios)
- 11 Vitest unit (was 6, +5 undoQueue)
- 39 Playwright + 2 skipped swipe-delete touch (was 31, +4 mobile-shell +4 masthead)
- [ ] Validación visual en DevTools iPhone 13 (390×844) + Pixel 7 (412×915) + Desktop (1280×800) — pendiente (manual).
- [x] `loginAs(USERS.borja)` con `page.setViewportSize(MOBILE)` activa el shell mobile automáticamente (verificado en M-01..M-04).
---
### Plan de ejecución (orden sugerido)
1. **5.0 Tests primero** — escribir los mobile tests aunque inicialmente fallen.
2. **5.1 Shell responsive** — sin esto, nada más es testable en mobile.
3. **5.5 UndoQueue + UndoToast** — dependencia de 5.4.
4. **5.2 Masthead + padding** — trivial, desbloquea los tests M-05.
5. **5.3 / 5.4 / 5.6** — ajustes por pantalla.
6. **5.7** — pulido de tasks/notes/search con el shell.
7. **5.Z Verificación** — correr toda la suite, snapshot visual, commit.
### Criterio de aceptación
- En viewport ≤ 768px: sidebar oculta, bottom tab bar + top bar visibles, drawer abre con el switcher.
- Todas las pantallas tienen el par masthead (label uppercase + title big).
- Swipe-left en un item revela la zona roja; full-swipe borra con toast de undo.
- Los 211 tests funcionales siguen verdes. Los nuevos mobile tests (M-01..M-07) pasan.
- Validado manualmente en DevTools en 3 viewports.