Files
Meal-Planner/Review/sprint3-verification.md
admin e90a9d6683 feat(ui): close 3 P2 audit findings + a11y sweep (Sprint 3)
- lib/toast.tsx (renamed from .ts for JSX): new showToast.undo(message,
  onUndo, ms=5000) helper. Inline 'Undo' button dismisses the toast and
  fires onUndo. Note: react-hot-toast 2.6 lacks onClose/onDismiss, so
  expiry is silent — same effective behavior as confirm() declined.

- Dashboard.handleDelete: captures the full MealPlanItem before the
  DELETE so Undo can re-fire meals.generateItem(planId, dayOfWeek,
  mealType) and refill the slot (recipe may differ — see plan R4).

- Pantry.handleRemove: fully reversible — Undo re-fires pantry.add with
  the original ingredient_id, quantity, and unit. New removeId state
  scopes the spinner to the clicked row.

- Both confirm() call sites removed.

- App.tsx Navigation: whitespace-nowrap + px-2 sm:px-3 so all 4 links fit
  on one line down to 360 px. aria-current='page' on the active link.
  <nav aria-label='Primary'>, <main id='main-content'>.

- components/ui/Badge: optional icon and aria-label props. Dashboard
  approval-status Badge passes aria-label='Approval status: approved'
  (or the current value) so screen readers don't rely on color alone.

- ErrorBoundary already mounted at App.tsx:42 — verified, no code change.

- Review/sprint3-verification.md (new) + Review/ui-nielsen-audit.md and
  fix-ui-audit.md updated with Sprint 3 status and deploy steps.

Build: npm run build (tsc + vite) green. tsc 0 errors.
2026-06-03 18:09:35 -07:00

3.1 KiB

Sprint 3 — Verification Log

Date: 2026-06-03 Scope: Three P2s (B12, B13, B14) + a11y sweep (S3.5) + ErrorBoundary confirmation (S3.4 — no code change). Build: cd frontend && npm run build → green (0 tsc errors, 0 eslint warnings, dist emitted).

Files changed

File Change
frontend/src/lib/toast.tstoast.tsx New showToast.undo(message, onUndo, ms=5000) helper. Renamed to .tsx for JSX support.
frontend/src/pages/Dashboard.tsx B12: handleDelete captures full MealPlanItem; on undo calls meals.generateItem(planId, dayOfWeek, mealType). Status Badge gets explicit aria-label.
frontend/src/pages/Pantry.tsx B12: handleRemove is fully reversible; calls pantry.add with original ingredient_id/quantity/unit on undo. New removeId state for per-row loading.
frontend/src/App.tsx B13: nav whitespace-nowrap + px-2 sm:px-3. S3.5: aria-current="page" on active link; <nav aria-label="Primary">; <main id="main-content">.
frontend/src/components/ui/Badge.tsx S3.5: optional icon and aria-label props.
Review/ui-nielsen-audit.md, fix-ui-audit.md Updated with Sprint 3 status.

How to deploy

cd ~/MealPlanner
git pull
docker compose -f docker-compose.yml up -d --build frontend

No backend changes in Sprint 3. No migration.

Manual smoke checks

  • Dashboard (mobile 360 px): all 4 nav links fit on one line. Tab to Meal Planner link → aria-current="page" is announced.
  • Dashboard (delete flow): click a meal's X button → toast appears "Meal deleted" with Undo. Click Undo within 5s → a new meal fills the slot. Let the toast expire → slot stays empty.
  • Pantry (delete flow): click Remove on a row → toast appears "Item removed" with Undo. Click Undo within 5s → the original item is back with its original quantity/unit. Per-row spinner visible only on the clicked row.
  • A11y (DevTools): <main id="main-content"> is the skip-link target. <nav aria-label="Primary">. Active nav link has aria-current="page". Approval-status Badge in meal card has aria-label="Approval status: approved" etc.
  • Meal card (mobile 360 px): confirm the title is still 2-line clamped (Sprint 2 fix intact) and the approve-status pill is color + text + aria-label (no color-only signal).

Notes / Caveats

  • react-hot-toast 2.6's ToastOptions does not expose onClose or onDismiss. The undo helper relies solely on the user clicking the Undo button during the 5s window — there is no "expire callback". This is documented in lib/toast.tsx. The previous confirm() had the same effective behavior (the user could click Cancel within the dialog).
  • For Dashboard delete, the Undo fills the slot with a freshly generated recipe (likely different from the deleted one). The original recipe cannot be restored without backend support for a "rebuild from snapshot" endpoint. This trade-off is documented in the plan's §R4.
  • Per-row loading state in Pantry: the Remove button shows a spinner only on the row being deleted, not all rows simultaneously.