[2026-01-05] Date Selector in Menu
Task Metadata
Date: 2026-01-05
Status: Approved
1. Context
- Goal:
- Allow users to select a specific Date and Time when logging a meal.
- Visually group the daily meal list into Breakfast, Lunch, and Dinner blocks with empty state indicators.
- Issue Reference: Closes #26.
- Trigger: User request for better control over logging and clearer daily structure.
- Constraints:
- Default to Now: Selector initializes to "Now" (dynamic).
- UI Compactness: Markers and selectors must be unobtrusive.
- Meal Blocks: Simple fixed cutoffs.
- Breakfast: 04:00 - 11:30
- Lunch: 11:30 - 17:00
- Dinner: 17:00 - 04:00 (Next Day)
- Empty States: If a block has no meals, show a subtle "Nothing logged" or empty placeholder.
2. Approach
- Part 1: Date/Time Selector
- UI: Compact "Now" indicator toggling to a native datetime picker. "Reset" button to revert to "Now".
- Logic:
logTimestampstate inuseMealStaging(Date | null). Passes explicit timestamp toaddMeal.
- Part 2: Meal List Grouping
- Logic:
- Create a utility to bucket meals based on their time relative to the user's 4AM start of day.
- Sort meals within buckets.
- UI Changes (
MealList.tsx):- Instead of a flat list, render 3 sections.
- Headers: Subtle caps (e.g., "BREAKFAST").
- Empty State: If a bucket is empty, render a minimal placeholder (e.g., dashed line or opacity-reduced text).
- Logic:
- Technical Changes:
- Update
useMealStagingfor timestamp logic. - Update
ApiMealService.addMealto handle explicit timestamps. - Refactor
MealListto perform grouping. - Aggregation: Ensure
UserDatecalculation in backend/frontend handles the time correctly.
- Update
3. Impact Analysis
- Files to Modify:
frontend/src/features/dashboard/components/PendingMealCard.tsx(Selector).frontend/src/features/dashboard/components/MealList.tsx(Grouping).frontend/src/hooks/useMealStaging.ts.frontend/src/utils/dateUtils.ts(Time block helpers).docs/architecture/data_model.md(Add Time Domain definitions).frontend/src/graphql/operations.ts(Fix: Add missing createdAt variable).
- Risks:
- Timezones: Hardcoded hours (11:30, 17:00) must be compared against the meal's local time (relative to user), not UTC directly, though our app mostly handles local time display.
- Visual Noise: Too many headers might clutter the view.
4. Execution Plan
- Step 0: Architecture Documentation
- Create
docs/architecture/ux_definitions.mdto define the "Meal Blocks" (Breakfast/Lunch/Dinner) and "Time Display Logic". - Ensure
docs/architecture/data_model.md(4 AM Rule) is cross-referenced.
- Create
- Step 1: Date/Time Selector UI
- Implement
DateTimePickertoggle inMealInput. - Wire up
useMealStagingto tracklogTimestamp.
- Implement
- Step 2: API Integration
- Pass timestamp to
addMeal. - Verify backend aggregation (
UserDate).
- Pass timestamp to
- Step 3: Meal Grouping Logic
- Implement
groupMealsByPeriod(meals)utility based onux_definitions.md. - Update
MealListto render sections. - Add empty state placeholders.
- Implement
- Step 4: Polish
- Update
PendingMealCardmacronutrient colors (Emerald/Amber/Purple) to matchProgressBarfor visual consistency.
- Update
- Step 5: Bug Fix (Missing GraphQL Variable)
- Update
frontend/src/graphql/operations.tsto include$createdAtinADD_MEALmutation. - Add regression tests for GraphQL operations and integration flow.
- Update
5. Execution Notes
Bug Fix: Missing GraphQL Variable
During verification, we identified a bug where custom timestamps were being ignored by the backend.
- Discovery: Although
ApiMealService.tswas passing thecreatedAtvariable, the GraphQL mutation string inoperations.tsdid not declare$createdAtin its signature. - Result: The variable was dropped before reaching the server, causing the backend to default to "Now".
- Fix: Updated the
ADD_MEALmutation string to correctly declare and map the$createdAtvariable. - Prevention: Added a new test suite
frontend/src/graphql/__tests__/operations.test.tsto verify the structure of critical GraphQL operations.
The Native Date Picker Decision
For the Date & Time selection component, we consciously chose to use the Native HTML <input type="datetime-local"> over a custom React library or strict component.
Why Native?
- Mobile-First UX:
- On iOS, the native picker offers the familiar "Drum" or Calendar modal that is heavily optimized for touch.
- On Android, it opens the system standard clock/calendar which users are already trained to use.
- Custom JS-based pickers often struggle with touch targets, z-indexing, and keyboard popping issues on mobile.
- Zero Bundle Size:
- Date picker libraries (like
react-datepickerormui/pickers) are notoriously heavy. By using the browser's built-in input, we added 0kb to our bundle.
- Date picker libraries (like
- Accessibility (a11y):
- Native inputs are compliant by default with screen readers and keyboard navigation (Tab/Space/Enter).
Implementation Detail
To maintain our custom UI design while using the native picker:
- We rendered the
<input>withvisibility: hidden(oropacity: 0in earlier iterations) inside the container. - We used a custom
onClickhandler on the container to triggerinputRef.current.showPicker(). - Result: The user sees a styled "Blue Pill" or "Text Link", but clicking it opens the robust system picker.
Meal Card Redesign
During implementation, we found the original PendingMealCard layout was too cramped when adding the time selector. We refactored it to:
- Vertical Stack: Moved macros to their own row.
- Proportional Layout: Used
flex-1andflex-[2]to ensure the Quantity input and Time Selector scale gracefully on all screen sizes. - Lucide Icons: Swapped standard emojis for
lucide-reacticons (Clock, X) for a more professional polish. - Consistent Colors: Updated macro pills to use Emerald (Protein), Amber (Carbs), and Purple (Fat) to match the dashboard progress bars.