Files
EventSnap/FOLLOWUPS.md
MechaCat02 309c25bc06 feat(frontend): UX review followups — primitives + a11y/UX fixes across 4 passes
New shared primitives:
- Toaster + toast-store, ConfirmSheet, Modal, focusTrap action,
  pullToRefresh action, avatarPalette + initials helper, Skeleton,
  HeartBurst, haptics, export-status store with onClearAuth hook

Critical UX/a11y:
- Replaced window.confirm with branded ConfirmSheet
- Focus management + Escape on every modal (PIN, Lightbox,
  Onboarding, ContextSheet, data-mode sheet, leave-confirm,
  HTML guide, host/admin ban + PIN-display modals)
- Sheet backdrops are real buttons with aria-label
- Silent ApiError catches now surface via global Toaster

Major polish:
- Dark-mode parity on HashtagChips + avatars (shared palette)
- Conditional Export tab in BottomNav (badge dot when ZIP ready)
- Back chevrons on /recover (history-aware) and /export
- Upload composer discard confirmation when content is staged
- Camera segmented Photo/Video shutter
- PIN auto-submit on 4th digit, paste-flash-free (controlled input)
- Welcome-back toast on /feed after PIN recovery

Minor:
- Skeleton states on feed; pull-to-refresh with live drag indicator
- Haptics on like / capture / submit / PIN-copy / onboarding complete
- Comment 500-char counter; quota "Fast voll" / "Limit erreicht" labels
- Onboarding pip ≥24px tap targets; long-press hint step
- overscroll-behavior lock on <html> while feed mounted
- teardownExportStatus wired via onClearAuth (covers 401 + explicit logout)
- ConfirmSheet per-instance titleId; Modal requires titleId or ariaLabel

Tests (7 new Playwright specs):
- 01-auth/pin-auto-submit, 01-auth/back-chevron
- 03-feed/confirm-sheet-delete, 03-feed/toast-on-failure
- 09-mobile/focus-trap, 09-mobile/sheet-escape,
  09-mobile/upload-cancel-confirm

FOLLOWUPS.md captures the deferred AT inert containment work
with acceptance criteria + implementation sketches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 22:50:28 +02:00

3.6 KiB

Follow-ups

Tracked work that was deferred during the multi-round UI/UX review pass. Each item has a clear acceptance criterion so a future pass can land it without re-deriving the context.

A11y — assistive-tech containment inside modals

Problem. Open modals (LightboxModal, ConfirmSheet, Modal, OnboardingGuide, join PIN modal, account data-mode sheet, export HTML guide) trap keyboard Tab via focusTrap, but VoiceOver rotor / TalkBack arrow-key navigation can still escape into the page content behind the dialog. Screen-reader users hear the wrong context.

Why deferred. A naive sibling-walk that sets inert on direct children of the modal's parent silences the global <Toaster> (aria-live="polite" region mounted in +layout.svelte) and the <BottomNav> while a modal is open — breaking toast announcements and the visible nav state. SvelteKit has no built-in portal mechanism, so dialogs render inside the route tree alongside the Toaster.

Acceptance criterion. With any modal open:

  • VoiceOver rotor (iOS Safari) and TalkBack swipe navigation (Android Chrome) cannot leave the dialog subtree.
  • Toasts that fire while a modal is open are still announced.
  • Nested modals (e.g. ConfirmSheet opened from inside ContextSheet) maintain correct containment when the inner closes.

Sketch of an approach. One of:

  1. Portal pattern. Render dialogs into a dedicated <div id="modal-root"> that's a sibling of the main app root in app.html. focusTrap then sets inert on the main root, leaving the modal root and the toast region (also moved to its own portal root) untouched.
  2. Opt-out marker. Walk siblings and inert them, but skip any node carrying a data-modal-passthrough attribute. Mark <Toaster> with it. Document the contract.
  3. Stack-aware containment. Maintain a module-level stack of open dialog nodes; the topmost owns the inert state, popped dialogs restore the previous layer. Avoids the nested-modal restoration bug.

Approach 1 is the cleanest long-term but the highest blast radius. Approach 2 is the smallest patch.

Files to touch.

Smaller nits, optional

  • Auto-submit on retried 4th digit. recover/+page.svelte, join/+page.svelte — after a wrong PIN, deleting one digit and retyping triggers an immediate submit. Backend's 3-attempts/15-min lockout makes this safe; could feel hair-trigger after a typo. Consider gating the second auto-submit per input session behind an explicit button press.
  • Onboarding pip tap target on the vertical axis. OnboardingGuide.svelte — current p-2.5 yields ~26 px height, meets WCAG 2.2 AA (≥24 px) but below iOS HIG / Material's 44 / 48 dp recommendation. Bumping to p-3 is the easy improvement; further increases start crowding the row.
  • Migrate bespoke focus-trapped dialogs to <Modal>. Join PIN modal, OnboardingGuide, LightboxModal, HTML guide, account data-mode sheet — all currently roll their own shell with focusTrap. They're correct, just not using the canonical primitive. Migrate when <Modal> gains features (e.g. the inert work above) you'd want everywhere.