Skip to content

2026-01-01: Targets Persistence

Task Metadata

  • 📆 Date: 2026-01-01
  • 🚥 Status: Complete

1. Context

  • Goal: Persist user-defined nutrition targets (Calories, Protein, Carbs, Fat) and display settings (Units) to the backend. Currently, these are stored in localStorage, meaning they do not sync across devices and are lost if the browser cache is cleared.
  • Trigger: Master Plan "Phase 1: Foundation". Essential for a consistent multi-device experience (e.g., Desktop to Mobile).

2. Approach

  • Schema Update: Introduce a new "Configuration" entity in DynamoDB (Single Table Design).
    • PK: USER#<sub_id>
    • SK: CONFIG#TARGETS (Single item per user)
    • Attributes: Calories (N), Protein (N), Carbs (N), Fat (N), DisplayUnit (S: 'kcal'|'kj').
  • API Layer (AppSync):
    • Query: getUserTargets (Fetch specific SK).
    • Mutation: updateUserTargets (Update attributes).
  • Frontend:
    • Hook: Update useSettings to use React Query (useQuery / useMutation) instead of localStorage.
    • Optimistic Updates: Ensure the UI updates instantly when the user saves settings.
  • Key Decisions:
    • Migration Strategy: If the user has existing targets in localStorage but no remote targets (first sync), we will automatically migrate the local values to the backend to preserve their setup.
    • Cleanup: Once the application switches to the new "Remote First" mode, the legacy localStorage keys (nutrition_targets, display_unit) will be ignored and eventually cleared to prevent state drift.
    • Conflict Resolution: If both localStorage and Remote targets exist, Remote targets win. The server is the single source of truth.
    • Schema Design: Using CONFIG#TARGETS isolates configuration from meal data, preventing pollution of the meal history and allowing for fast, single-item lookups.

3. Impact Analysis

  • Files to Modify:
    • docs/architecture/data_model.md (Document new CONFIG entity).
    • appsync/ (New resolvers for config).
    • graphql/schema.graphql (New Query/Mutation types).
    • frontend/src/services/ApiMealService.js (Add target methods).
    • frontend/src/hooks/useSettings.js (Refactor from localStorage to ReactQuery).
    • frontend/src/components/SettingsModal.jsx (Ensure it handles async saving state).
  • Risks:
    • Latency: Settings modal might feel slower if we wait for network. Mitigation: Optimistic updates.
    • Offline: If offline, user can't save settings? Mitigation: React Query offline support handles the mutation queue.

4. Execution Plan

  • Step 1: Backend Implementation
    • Update schema.graphql with UserTargets type and operations.
    • Create appsync/getUserTargets.js and appsync/updateUserTargets.js.
    • Deploy backend updates (sam build && sam deploy).
  • Step 2: Service Integration
    • Update ApiMealService.js to call the new GraphQL operations.
  • Step 3: Frontend Refactor
    • Rewrite useSettings.js to use useQuery (fetching CONFIG#TARGETS) and useMutation.
    • Implement staleTime and gcTime strategies consistent with useMeals.
  • Step 4: Verify
    • Test saving on one device and viewing on another.
    • Test offline behavior.

5. Execution Notes

(AI to add details discovered during implementation)

  • Tech Debt / Future Improvements:
    • Attribute Casing: We are using PascalCase (Calories, DisplayUnit) in DynamoDB for consistency with existing Meal records, despite the JS/React stack preferring camelCase.
    • Action: Added "Unify Casing" task to Phase 1 to migrate the entire DB to camelCase and remove mapping layers.

6. User Approval & Key Learnings

User Feedback: The "Local State vs. React Query State" pattern used in the SettingsModal is interesting. Driving inputs directly from mutations caused race conditions and UI resets.

Critical Discovery: This feature revealed a major gap in our development process. Manual verification is no longer sufficient; we need a formal Layered Testing Strategy to catch race conditions and state synchronization bugs automatically.

  • Key Learning: Form Handling with React Query.
    • Pattern: Buffer inputs in useState (Local State) for instant UI feedback.
    • Sync: Only call mutation.mutate (Server State) on specific events like "Save" or "Blur".
    • Why: Prevents "jumping" inputs caused by optimistic updates refetching or settling mid-typing.