Skip to content

[2026-01-05] Date Selector in Menu

Task Metadata

  • 📆 Date: 2026-01-05
  • 🚥 Status: Approved

1. Context

  • Goal:
    1. Allow users to select a specific Date and Time when logging a meal.
    2. 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: logTimestamp state in useMealStaging (Date | null). Passes explicit timestamp to addMeal.
  • 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).
  • Technical Changes:
    • Update useMealStaging for timestamp logic.
    • Update ApiMealService.addMeal to handle explicit timestamps.
    • Refactor MealList to perform grouping.
    • Aggregation: Ensure UserDate calculation in backend/frontend handles the time correctly.

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.md to define the "Meal Blocks" (Breakfast/Lunch/Dinner) and "Time Display Logic".
    • Ensure docs/architecture/data_model.md (4 AM Rule) is cross-referenced.
  • Step 1: Date/Time Selector UI
    • Implement DateTimePicker toggle in MealInput.
    • Wire up useMealStaging to track logTimestamp.
  • Step 2: API Integration
    • Pass timestamp to addMeal.
    • Verify backend aggregation (UserDate).
  • Step 3: Meal Grouping Logic
    • Implement groupMealsByPeriod(meals) utility based on ux_definitions.md.
    • Update MealList to render sections.
    • Add empty state placeholders.
  • Step 4: Polish
    • Update PendingMealCard macronutrient colors (Emerald/Amber/Purple) to match ProgressBar for visual consistency.
  • Step 5: Bug Fix (Missing GraphQL Variable)
    • Update frontend/src/graphql/operations.ts to include $createdAt in ADD_MEAL mutation.
    • Add regression tests for GraphQL operations and integration flow.

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.ts was passing the createdAt variable, the GraphQL mutation string in operations.ts did not declare $createdAt in its signature.
  • Result: The variable was dropped before reaching the server, causing the backend to default to "Now".
  • Fix: Updated the ADD_MEAL mutation string to correctly declare and map the $createdAt variable.
  • Prevention: Added a new test suite frontend/src/graphql/__tests__/operations.test.ts to 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?

  1. 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.
  2. Zero Bundle Size:
    • Date picker libraries (like react-datepicker or mui/pickers) are notoriously heavy. By using the browser's built-in input, we added 0kb to our bundle.
  3. 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> with visibility: hidden (or opacity: 0 in earlier iterations) inside the container.
  • We used a custom onClick handler on the container to trigger inputRef.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-1 and flex-[2] to ensure the Quantity input and Time Selector scale gracefully on all screen sizes.
  • Lucide Icons: Swapped standard emojis for lucide-react icons (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.