# Sprint 2 — Verification Log

**Date:** 2026-06-02
**Scope:** Six P1s from `Review/ui-nielsen-audit.md` plus the S3.3 mobile stat-grid fix.
**Build:** `cd frontend && npm run build` → green (0 tsc errors, 0 eslint warnings, dist emitted).

## Files changed

| File | Change |
|---|---|
| `frontend/src/pages/Dashboard.tsx` | B6: meal-card title `truncate` → `line-clamp-2`; image 56→40 on mobile. |
| `frontend/src/pages/MealDetail.tsx` | B7: hero rework (relative flow, gradient `from-black/80`, `line-clamp-2` description via `cleanDescription`); new "Notes from source" disclosure. |
| `frontend/src/lib/utils.ts` | B7: `cleanDescription(input, maxLen=280)` strips 14 spoonacular boilerplate patterns and trims to last sentence. |
| `frontend/src/pages/Pantry.tsx` | B8: aisle/unit → `<Select>` from canonical lists; ingredient name marked `*`; B10: scroll-hint gradient + `role="region"`. |
| `frontend/src/types/index.ts` | B8: `PANTRY_AISLES` enum + `PantryAisle` type. |
| `backend/alembic/versions/0015_normalize_pantry_aisles.py` | B8: migration — `CASE LOWER` mapping, temp-table backup, downgrade raises. |
| `backend/scripts/dry_run_aisle_migration.sql` | B8: read-only dry-run (counts rows that *would* change). |
| `frontend/src/pages/ShoppingList.tsx` | B9: `AISLE_LABEL` map + `aisleDisplay()`; S3.3: 3-col grid on all viewports with compact mobile sizing. |
| `frontend/src/pages/Recipes.tsx` | B11: `applied`/`pending` filter state, Apply/Reset buttons, active-count chip on Filters button, `role="region"`. |

## How to deploy on the remote host (the database is in a container)

The dev DB runs inside `mealplanner-db-1`. No host `psql` is required. All commands run via `docker compose exec` from the project root.

```bash
cd ~/MealPlanner
git pull

# 1. (Optional but strongly recommended) Take a persistent backup of
#    aisle values BEFORE the migration runs. This stays in the DB
#    even after the migration session ends.
docker compose exec -T db psql -U mealplanner -d mealplanner \
  -f /dev/stdin < backend/scripts/persist_aisle_backup.sql

# 2. Dry-run preview — counts rows that would change, no writes.
docker compose exec -T db psql -U mealplanner -d mealplanner \
  -f /dev/stdin < backend/scripts/dry_run_aisle_migration.sql
# Expected output (example):
#       tbl      | rows_to_change | distinct_old_values
#  --------------+----------------+---------------------
#   grocery_item |          10539 |                  16
#   ingredient   |          10657 |                  27

# 3. If the dry-run row counts look sane, apply the migration:
docker compose exec backend alembic upgrade head
# Expected: "Running upgrade 0014 -> 0015, normalize_pantry_aisles"

# 4. Frontend rebuild + restart (Sprint 2 UI changes are already merged).
docker compose -f docker-compose.yml up -d --build frontend

# 5. Smoke-check the live site
curl -s -o /dev/null -w "%{http_code}\n" http://100.108.208.56:8082/
# Expect: 200
```

If the dry-run row count is high (>1k on ingredient) and you want to inspect what would change before applying, uncomment the second query at the bottom of `dry_run_aisle_migration.sql` and re-run.

## Manual smoke checks

- `/` (mobile 390 px): Generate button visible on every empty slot; new meal title clamps to 2 lines without `B..`.
- `/meals/<id>`: title is readable, description is 1-2 lines, "Notes from source" disclosure shows the raw text.
- `/pantry` (add form): Unit and Aisle are dropdowns; ingredient name shows `*`.
- `/shopping-list`: section headers read "Meat & Seafood", "Produce", "Pantry", "Dairy & Eggs". On mobile, the 3 stat cards are side-by-side in one row.
- `/recipes`: open Filters, set 2, collapse — Filters button shows `Filters (2)`. Reset clears, chip disappears.
- `/pantry` (mobile 390 px): right-edge gradient hints that the table scrolls horizontally.

## Migration safety

- Run dry-run first to count affected rows per table.
- Migration runs in a single `upgrade()` call; temp backup tables persist for the session lifetime. To preserve backups beyond session, alter the migration to use real tables.
- `downgrade()` is intentionally `NotImplementedError`. Restore from a pre-migration snapshot if rollback is needed.
