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>
This commit is contained in:
38
apps/web/src/lib/components/ScreenMasthead.svelte
Normal file
38
apps/web/src/lib/components/ScreenMasthead.svelte
Normal file
@@ -0,0 +1,38 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
let {
|
||||
label,
|
||||
title,
|
||||
meta,
|
||||
actions
|
||||
}: {
|
||||
label: string;
|
||||
title: string;
|
||||
meta?: Snippet;
|
||||
actions?: Snippet;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<header class="flex items-start gap-4 px-4 pb-4 pt-6 md:px-6 md:pb-6 md:pt-8">
|
||||
<div class="min-w-0 flex-1">
|
||||
<p
|
||||
data-testid="masthead-label"
|
||||
class="mb-1 text-[13px] font-semibold uppercase tracking-[0.05em] text-text-secondary"
|
||||
>
|
||||
{label}
|
||||
</p>
|
||||
<h1
|
||||
data-testid="masthead-title"
|
||||
class="text-[26px] md:text-[32px] font-semibold leading-tight tracking-[-0.02em] text-text-primary"
|
||||
>
|
||||
{title}
|
||||
</h1>
|
||||
{#if meta}
|
||||
<div class="mt-1.5 text-[13px] text-text-secondary">{@render meta()}</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if actions}
|
||||
<div class="flex flex-shrink-0 items-center gap-1.5">{@render actions()}</div>
|
||||
{/if}
|
||||
</header>
|
||||
Reference in New Issue
Block a user