Sprint 1 of the UI/UX audit (Review/ui-nielsen-audit.md).
- RecipeDetail: drop .trim() on ingredient line so unit and name no longer fuse
('2 canBlack Beans' -> '2 can Black Beans').
- MealDetail: align ingredient field name to backend ('qty' not 'quantity'),
add 'ingredient.name' fallback for the missing nested name from API.
- MealDetail: '$N/A per serving' -> '$X.XX' or 'No estimate'.
- App: add /recommended alias to /recipes/recommended, plus a catch-all
NotFound page so unrecognised URLs no longer render blank.
- Dashboard: remove 'hidden md:*' on empty meal slots so mobile users can
tap Generate. Bump empty-slot button to 44px min-height (a11y).
- EmptyState: accept an optional 'to' prop for Link-wrapped actions.
- types: extend RecipeIngredient with optional notes and nested ingredient.
Backend:
- POST /api/ingredients now checks name_lower and aliases before inserting
- Returns existing ingredient on 409 instead of throwing error
Frontend:
- Removed fragile 409-recovery logic from Pantry.tsx handleAdd
- Added aliases field to Ingredient type for case-insensitive matching
Fixes pantry add for ingredients like 'Carrots' whose canonical name is 'Carrot'
- backend: expose POST /api/ingredients on public router so frontend can create ingredients without admin token
- frontend/api: point listIngredients and createIngredient to /api/ingredients
- frontend/pantry: replace ingredient dropdown with searchable text input + fuzzy matching + auto-create
- Dashboard: stack days vertically on mobile and suppress empty meal slots
- Nav: allow wrapping on narrow screens
- MealDetail: responsive hero sizing and padding
- Create backend/app/api/orchestrate.py — new router for workflow steps
(scrape, generate, email, reminder, deadline, finalize) without admin auth.
- Remove orchestrate endpoints from backend/app/api/admin.py.
- Register orchestrate router in main.py under /api/orchestrate.
- Update frontend api/index.ts to call /orchestrate/{step} instead of
/admin/orchestrate/{step}.
This lets family members trigger vote emails without an admin bearer token.
- Add triggerOrchestrate() to API client calling POST /admin/orchestrate/{step}
- Replace dead <button> in Dashboard with VoteEmailButton component:
onClick calls triggerOrchestrate('email'), shows toast spinner + success/error
- backend/app/security.py: require_session() now auto-authenticates by
returning the first family_profile_id from the DB. No cookie or password
needed. Falls back to "bootstrap" sentinel if no FamilyProfile exists.
Admin routes (require_admin) still protected by bearer token.
- frontend/src/api/index.ts: removed 401→/login redirect interceptor
- frontend/src/App.tsx: removed Sign out button, removed /login route and
Login page import
- Login page kept on disk (unused) for potential future re-enablement
- Add types for all API models (MealPlan, Recipe, Ingredient, etc.)
- Add API client with mealPlannerApi wrapper for all endpoints
- Implement Dashboard with weekly meal plan grid view
- Implement Pantry page with add/remove functionality
- Implement MealDetail page with recipe display
- Implement ShoppingList page with aisle grouping
- Add ShoppingList route to App.tsx
- Add vite-env.d.ts for Vite env type support
Backend (FastAPI):
- docker-compose with all 4 services
- FastAPI app with health endpoints
- SQLAlchemy models for all tables
- Placeholder API endpoints for all routes
- Config and database modules
- requirements.txt with all dependencies
Frontend (React):
- package.json with React, Tailwind, React Query, React Router
- Vite config with API proxy
- Tailwind and TypeScript configs
- Basic App with routing skeleton
- Placeholder pages (Dashboard, MealDetail, Pantry)
Infrastructure:
- nginx config for reverse proxy
- Dockerfile for backend and frontend