Public Access
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:
@@ -0,0 +1,40 @@
|
||||
# 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.ts` → `toast.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
|
||||
|
||||
```bash
|
||||
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.
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user