3 Commits
Author SHA1 Message Date
admin d78bd1864e feat(ui): URL week selector + aisle-migration 0015 cast fix (Sprint 5 F5)
F5 — Persistent week selector in URL (the audit's F5 / H7 finding).

Backend:
- GET /api/meals and GET /api/shopping-list now accept an optional
  ?week_start=YYYY-MM-DD query param. When set, the response is the
  MealPlan for that week (any status). When omitted, behaviour is
  unchanged: meals returns the latest plan; shopping-list returns
  the latest approved/locked plan with fallback to latest.
- No new dependencies; uses FastAPI's Optional[date] Query type
  which auto-validates the YYYY-MM-DD format.
- Files: backend/app/api/meals.py:30-57, shopping_list.py:27-60.

Frontend:
- New week helpers in lib/utils.ts: isoMonday(), parseIsoDate(),
  shiftIsoDate(), formatIsoDate(). All UTC-based to match the
  backend's date column. isoMonday returns the ISO date of the
  Monday of a given date's week.
- api/index.ts: meals.getPlanned(weekStart?) and
  shoppingList.get(weekStart?) take an optional ISO date string.
  Axios drops undefined params, so callers can omit them.
- Dashboard: useSearchParams('week') reads the URL; if absent or
  invalid, falls back to this week's Monday (so the default URL is
  empty). The queryKey now includes weekStart, so navigating weeks
  fetches the right plan. A new segmented control in the header
  (chevron-left | 'This week' / 'Current' jump button | chevron-
  right) lets the user step weeks; the jump button highlights
  primary-50 when the displayed week IS the current week. 'This
  week' clears the ?week param. Mutations (move/approve/deny/
  delete/generate) now invalidate ['mealPlan', weekStart] so the
  right week refetches.
- ShoppingList: same URL sync, same segmented control, same
  weekStart in queryKey. The 'no plan' empty state branches on
  isCurrentWeek: 'No shopping list yet' (current) vs 'No plan for
  that week' (any other week). The local-storage check-state key
  naturally isolates per week (it uses shoppingList.week_start_date
  which is the server's view of the current plan's week).

Migration 0015 cast fix:
- Discovered while smoke-testing on the local dev DB: the
  CASE expression in 0015_normalize_pantry_aisles.py failed
  with 'operator does not exist: text = boolean' on the
  varchar(100) aisle column. Root cause: the CASE branches were
  inferred as different types (string vs NULL) so the SET
  target type couldn't be unified.
- Fix: explicit ::varchar(100) cast on the CASE expression.
  Also simplified the WHEN '' branch (was NULLIF(...) IS NULL
  with implicit bool comparison). Tested on local dev DB:
  alembic upgrade head now succeeds; the 21196 rows that the
  Sprint 2 dry-run predicted actually normalize correctly.
  This means Sprint 2's deploy was blocked on the same bug
  (the deployment host would have hit the same error).
- Verified via curl: /api/shopping-list?week_start=2026-05-15
  returns 25 items with aisles 'Meat & Seafood', 'Pantry',
  'Produce', 'Dairy & Eggs' (the canonical labels the migration
  produces). Pre-migration aisles like 'meat_seafood' are gone.

Build: tsc 0 errors, vite 0 errors. 7 files, +196/-22.
2026-06-04 12:30:49 -07:00
admin f5fb7558c4 fix(migration): simplify aisle migration + add persistent backup script
- Drop the empty batch_alter_table block and the meaningless
  set_config call from migration 0015. Temp tables still persist
  for the migration's session (Alembic's transactional_ddl).
- New backend/scripts/persist_aisle_backup.sql creates
  public.ingredient_aisle_backup_0015 and
  public.grocery_item_aisle_backup_0015 permanent tables for
  operators who want a recoverable record beyond the migration.
- Update Review/sprint2-verification.md, Review/ui-nielsen-audit.md
  and fix-ui-audit.md with the correct container-based deploy
  steps: docker compose exec db psql -U mealplanner -d mealplanner
  -f /dev/stdin < ...sql. Host psql is not available on the
  deployment host; the db runs inside the container.
2026-06-03 17:41:59 -07:00
admin ccc70aaf72 feat(ui): close 6 P1 audit findings + 1 bonus mobile fix (Sprint 2)
- Dashboard MealCard: title truncate -> line-clamp-2, image shrinks to
  40x40 on <md to give the title room (B6).
- MealDetail: hero reworked to normal flow with stronger gradient;
  description runs through new cleanDescription() helper that strips
  14 spoonacular SEO patterns and trims to the last full sentence.
  Raw description moved to a 'Notes from source' disclosure (B7).
- Pantry: free-text aisle/unit replaced with <Select> populated from
  the new PANTRY_AISLES canonical enum; ingredient name field marked
  required. New PANTRY_AISLES export + PantryAisle type in types (B8).
- backend: alembic 0015_normalize_pantry_aisles maps free-text
  ingredient.aisle and grocery_item.aisle to canonical labels in a
  single transaction; downgrade raises (restore from snapshot).
  backend/scripts/dry_run_aisle_migration.sql is the read-only
  preview helper.
- ShoppingList: human-readable AISLE_LABEL map replaces raw snake_case
  aisle keys; 3-col stat grid with compact mobile sizing (B9 + S3.3).
- Pantry table: role/aria-label region and a right-edge white
  gradient hint at mobile horizontal overflow (B10).
- Recipes: pending/applied filter split, Apply and Reset buttons,
  active-count chip on the Filters button, role=region + aria-label
  on the panel (B11).
- Review/sprint2-verification.md and fix-ui-audit.md updated.

Build: npm run build (tsc + vite) green. tsc emits 0 errors.

Co-located audit + plan docs kept in sync: Review/ui-nielsen-audit.md
gains a Sprint 2 status block; fix-ui-audit.md has implementation
notes for each Sprint 2 task.
2026-06-03 17:36:26 -07:00