Day Detail UX Overhaul: PDF Export, Shopping Lists, and Killing Duplicate Components
Day Detail UX Overhaul: PDF Export, Shopping Lists, and Killing Duplicate Components
This PR was one of those satisfying ones where you fix a mess, ship real features, and end up with a codebase that's noticeably cleaner. Here's what changed in the MealPlan AI day detail view and why.
The Duplication Problem
Before this change, meal cards existed in three separate implementations — one for each layout mode (Shared, Individual, Hybrid). They had drifted apart over time with slightly different styles, different prop shapes, and inconsistent behavior. Any bug fix had to be applied three times.
The fix was straightforward: extract a single BaseMealCard component that all three layouts consume. One component, one place to fix bugs, one source of truth for how a meal card looks and behaves.
This kind of refactor is easy to defer forever because it's not a user-facing feature. But it was causing real pain — fixing the per-meal nutrition display bug (it was incorrectly showing daily totals instead of per-meal values) required touching all three implementations. That was the nudge to just do it.
Nutrition Display — CSS Variables Instead of Hardcoded Colors
Nutrition colors (protein blue, carbs orange, fat yellow, calories red) were scattered as hardcoded hex values across a dozen components. Dark mode made this worse — you'd need separate overrides everywhere.
Replaced all of them with a CSS variable system defined in globals.css:
`css
:root {
--nutrition-protein: #3b82f6;
--nutrition-carbs: #f97316;
--nutrition-fat: #eab308;
--nutrition-calories: #ef4444;
}
`
Now NutritionProgressBar and every other component just references the variable. Dark mode overrides live in one place.
I also added NutritionProgressBar — a small component that shows macro progress against real targets pulled from the participant's profile, not just raw grams. Much more useful at a glance.
Shopping Lists
Users kept asking for a way to take the plan to a grocery store. The implementation aggregates ingredients across all meals in a day (or the full plan), deduplicates them, and sums quantities where units match.
Two export options:
- Copy to clipboard — one tap, paste into any notes app
- Download as .txt — clean formatted list, works offline
nutrition-utils.ts and handles the annoying edge cases: "200g chicken breast" + "150g chicken breast" becomes "350g chicken breast", but "1 tbsp olive oil" + "2 tsp olive oil" stays separate since unit conversion isn't worth the complexity right now.
PDF Export
This was the most involved piece. The PDF generator (lib/generate-meal-plan-pdf.ts, ~900 lines) supports four export modes:
1. Complete plan — everything, all days, all meals 2. Recipes only — ingredients + instructions per meal 3. Shopping list — aggregated grocery list 4. Nutrition report — macro breakdowns by day and participant
The detail I'm most happy with: the table of contents has clickable internal page links. Each entry jumps directly to its section. Took longer than expected to implement with the PDF library but it makes the output feel professional rather than just a long printout.
The cover page includes the plan name, date range, participant count, and meal-plan.app branding. Each page has a footer with the page number and plan title.
Mobile Swipe Navigation
Day detail browsing on mobile was awkward — you had to go back to the plan overview to switch days. Added swipe left/right gesture navigation with useSwipeNavigation hook. Swipe right goes to the previous day, swipe left to the next. Works alongside the existing day selector tabs.
Skeleton Loading States
Replaced the generic spinner with skeleton screens that match the actual day detail layout. The skeletons mirror the card grid, nutrition bars, and section headers so the page feels stable while data loads rather than jumping into place.
---
The PR touched 64 files and added ~4,000 lines net — mostly the PDF generator and the new components. The refactor to BaseMealCard actually removed about 800 lines across the layout files, which is always a good sign.
Agent Builders Are Changing How I Ship Code — Here's My Actual Workflow
NextRebranding a Live Product to Melio: 250 String Replacements, Zero Downtime