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.
This commit is contained in:
2026-06-03 18:09:35 -07:00
parent f5fb7558c4
commit e90a9d6683
8 changed files with 199 additions and 39 deletions
+15 -1
View File
@@ -21,7 +21,7 @@ The app looks polished on the surface (Tailwind palette, clean cards, working to
> **Sprint 1 status (commit `f3e4a44`, deployed by user 2026-06-02):** Items 1, 2, 3, 4, 5 all addressed in the frontend source. Live at `100.108.208.56:8082/`. Verification screenshots in `/tmp/opencode/mp-review/screenshots/fix-sprint1/`.
>
> **Sprint 2 status (commit pending, ready for deploy):** All six P1s plus the S3.3 mobile shopping-list stat-grid fix are addressed in source.
> **Sprint 2 status (commit `ccc70aa`, deploy helper `f5fb755`):** All six P1s plus the S3.3 mobile shopping-list stat-grid fix are addressed in source.
> - **B6** Dashboard `MealCard` title: `truncate` → `line-clamp-2`; image shrinks to 40×40 on `<md` to give title more room.
> - **B7** `MealDetail` hero: title/description no longer overlap; description stripped of spoonacular SEO copy via `lib/utils.cleanDescription`; raw text moved to a "Notes from source" disclosure.
> - **B8** `Pantry` aisle/unit: free-text → canonical `Select` from `PANTRY_AISLES` enum (`types/index.ts`). `Ingredient name` field now marked `*` required. Backend migration `0015_normalize_pantry_aisles.py` normalizes `ingredient.aisle` and `grocery_item.aisle` to canonical labels. Dry-run SQL helper at `backend/scripts/dry_run_aisle_migration.sql`.
@@ -50,6 +50,20 @@ The app looks polished on the surface (Tailwind palette, clean cards, working to
> docker compose -f docker-compose.yml up -d --build frontend
> ```
> **Sprint 3 status (commit pending, ready for deploy):** All P2s plus the a11y sweep.
> - **B12** Native `confirm()` deleted for both delete sites. `lib/toast.tsx` (renamed from `.ts` for JSX) gains a new `showToast.undo(message, onUndo, ms=5000)` helper. `Dashboard.handleDelete` captures the full item, deletes, then surfaces an Undo toast that re-fires `generateItem(planId, dayOfWeek, mealType)` to refill the slot. `Pantry.handleRemove` is fully reversible: re-adds via `pantry.add` with the original `ingredient_id`/`quantity`/`unit`. Per-row loading state via new `removeId` state.
> - **B13** `Navigation` link text gets `whitespace-nowrap`; padding reduced to `px-2 sm:px-3` so all 4 links fit on one line down to ~360 px.
> - **S3.4** Confirmed `ErrorBoundary` is already mounted at `App.tsx:42` (verified `components/ErrorBoundary.tsx`).
> - **S3.5** A11y sweep: `<nav aria-label="Primary">`, `aria-current="page"` on the active nav link, `<main id="main-content">` for skip-link targets, `Badge` component extended with optional `icon` and `aria-label` props. Approval-status Badge on the meal card now passes `aria-label="Approval status: approved"` etc.
> - **S3.3** Mobile shopping-list stat cards already done in Sprint 2 (3-col grid with compact mobile sizing).
>
> Deploy:
> ```bash
> cd ~/MealPlanner
> git pull
> docker compose -f docker-compose.yml up -d --build frontend
> ```
---
## Findings mapped to Nielsen's 10 Heuristics