Skip to content

[2026-01-06] Advanced Nutrient Tracking & Prompt Optimization

Task Metadata

  • 📆 Date: 2026-01-06
  • 🚥 Status: Completed

Objective

Goal:

  1. Flexible Nutrient Tracking: Extend the data model to support arbitrary, user-defined nutrients (e.g., Fiber, Sodium) beyond the core macros.
  2. Prompt Optimization: Simplify the AI system prompt to be more efficient and model-agnostic, dynamically injecting the user's specific nutrient tracking list.
  • Trigger: User request to track Fiber/Sodium and improve prompt compatibility.
  • Reference: ADR 001: Flexible Nutrient Schema
  • Constraints:
    • Resource Efficiency: Minimize DynamoDB GetItem requests by using Frontend-Assisted Validation (passing snapshots to resolvers).
    • Backward Compatibility: Core macros must remain top-level.
    • Data Integrity: Unit Immutability (units cannot be changed once a nutrient is created).
    • Concurrency: Optimistic Locking on settings to prevent data loss on the "God Row".

Technical Strategy

We will use Metadata-Driven Maps where CONFIG#TARGETS acts as the schema registry and the frontend provides validation snapshots to save on RCU.

  1. Registry: Nutrient definitions (key, unit, label) are permanent once created.
  2. Snapshotting: Frontend passes the current "Active Key List" to addMeal to validate AI response without a DB lookup.
  3. Aggregation: Resolver rounds to 2 decimal places to prevent float drift.
  4. Versioning: Every settings update checks the version attribute to handle concurrent edits.
  • Key Decisions:
    • Combined Settings: Single row for targets + registry + history.
    • Frontend Validation: Trust-but-verify approach to save cost while blocking reserved keywords.
    • Report-Only: Accepting that extended nutrients won't be searchable via GSI.

Testing Strategy

  • Must Test: Optimistic locking failure handling in Frontend.
  • Must Test: Unit conversion edge case (ensuring users can't change units).
  • Must Test: "The Math Ghost" regression: Log -> Disable -> Delete.

Risk Analysis

  • Conflict Resolution: Users on two devices might see "Settings Update Failed" if they edit simultaneously.
  • Math Drift: Minor precision loss over years (mitigated by rounding).
  • Files to Modify:
    • graphql/schema.graphql (Update UserTargets, add registrySnapshot input)
    • appsync/addMeal.js (Implement snapshot-based filtering)
    • frontend/src/hooks/useSettings.ts (Implement optimistic locking logic)
    • docs/architecture/data_model.md (Add nutrientRegistry, extendedNutrients map, and version attribute)
    • docs/architecture/ui_data_validation.md (Define Unit Immutability, Snapshot Protocol, and Version Conflict rules)
    • docs/architecture/ux_definitions.md (Define dynamic summary scaling and Registry/Archival display logic)
    • docs/architecture/testing_strategy.md (Add 'Concurrency & Immutability' to the Must-Test criteria)
    • docs/architecture/decisions/001_flexible_nutrient_schema.md (Finalize as Technical Specification)

Execution Plan

  • Step 1: Schema & Backend Foundation
    • Update docs/architecture/data_model.md with new attributes.
    • Add version to UserTargets.
    • Implement registrySnapshot parameter in addMeal.
    • Update appsync/addMeal.js with rounding and keyword filtering.
  • Step 2: Settings & Optimistic Locking
    • Update docs/architecture/ui_data_validation.md with locking/validation rules.
    • Update updateUserTargets resolver to check version.
    • Update Settings UI to handle "Locked Units" and conflict errors.
  • Step 3: Prompt Engineering
    • Dynamic prompt generator that injects label (unit) for each active nutrient.
  • Step 4: Dashboard & UI Polish
    • Update docs/architecture/ux_definitions.md for dynamic columns.
    • Dynamic column rendering and summary display.
  • Step 5: Verification & Testing
    • Update docs/architecture/testing_strategy.md with new test scenarios.
    • Implement Unit Tests:
      • promptUtils.ts (Verify dynamic prompt generation).
      • mealParser.ts (Verify parsing of extended nutrients).
      • addMeal.js (Verify resolver logic for filtering/rounding).
    • Implement Integration Tests:
      • useMeals (Verify optimistic updates for extended nutrients).
    • New Build Artifact: Created scripts/evaluate_appsync.sh and task test:appsync:eval to catch runtime syntax errors locally.
    • Finalize docs/architecture/decisions/001_flexible_nutrient_schema.md.
    • Run full test suite.

Execution Notes

  • 2026-01-08: Implemented backend schema changes, resolvers (addMeal, updateUserTargets), and frontend infrastructure (types, ApiMealService, useMeals, useSettings).
  • 2026-01-08: Implemented promptUtils.ts to dynamically generate the AI prompt based on the user's registry.
  • 2026-01-10: Incident: UI Flashing/Reverting on Settings Update.
    • Problem: When adding a new macro, the UI would show it momentarily then revert (flash).
    • Discovery: Layer 3 (Hook) tests revealed two issues:
      1. useSettings.ts mutation logic was not performing a deep merge on nutrientRegistry, causing optimistic updates to overwrite the entire registry with partial data.
      2. onSettled was triggering an immediate invalidateQueries, which caused a refetch that often returned stale data before the mutation's write had fully propagated in the backend (or before the cache was updated with the mutation's result).
    • Fix:
      • Refactored onMutate to perform a deep merge of the registry.
      • Updated onSettled to manually update the cache with the actual response from the server before invalidating, ensuring the "latest" data is always present.
      • Hardened the updateUserTargets.js resolver response mapping to be more consistent.
    • Verification: Created frontend/src/hooks/__tests__/useSettings.test.tsx to explicitly test optimistic state retention. All tests passed.
  • 2026-01-10: Incident: Nutrient Registry lost on Persistence.
    • Problem: Even when mutations succeeded, the nutrientRegistry would return as null in the UI and stay null after refresh.
    • Discovery: Analysis of CloudWatch logs revealed two critical oversights in updateUserTargets.js:
      1. Double-Parse Bug: AWSJSON inputs are automatically parsed by AppSync. Re-parsing with JSON.parse() on an object caused the value to be corrupted/cleared.
      2. Missing Casing Fallback: The resolver lacked fallbacks for legacy PascalCase attributes, causing null returns if the DB state was not standardized.
    • Fix:
      • Implemented a "Safe Parse" pattern in updateUserTargets.js and addMeal.js (only parse if type is string).
      • Added robust casing fallbacks to both Query and Mutation resolvers.
      • Note on returnValues: Attempted to add returnValues: 'ALL_NEW', but discovered it is unsupported in the APPSYNC_JS 1.0.0 runtime (causes a Code error). Relied on safe parsing and casing fallbacks instead.
      • Updated updateUserTargets.test.js to catch these regressions.
  • 2026-01-10: Incident: UI not rendering Registry despite successful Parse.
    • Problem: Even after fixing the backend, the UI still didn't show the nutrients.
    • Discovery: ApiMealService.ts was attempting to parse the nutrientRegistry in-place on the GraphQL response object. Some clients provide immutable or proxy-wrapped objects where in-place modification is lost or ignored by TanStack Query's cache.
    • Fix: Updated ApiMealService.ts to return a new object with the parsed registry, ensuring the cache receives a fresh, correctly-typed object.
  • 2026-01-10: Incident: String-Hell Rehydration.
    • Problem: Nutrient registry kept reverting to a JSON string on refresh, breaking the UI.
    • Discovery: The persistent cache (localStorage) was re-hydrating stale string data from previous sessions before the API fetch could run.
    • Fix: Implemented a "Final Defense" using React Query's select option in useSettings.ts. This ensures data is definitively transformed into an object every time it is accessed from the cache.
  • 2026-01-10: Feature: Historical Nutrient Visibility.
    • Updated DailyTotals to show nutrients if they are Active OR have a non-zero total for that day.
    • Archived nutrients with data are now styled with 60% opacity and an "(Arc)" label, preserving historical context.
  • 2026-01-10: Feature: Nutrient Suggestions & History Restore.
    • Added "Quick Add" buttons for common nutrients (Fiber, Sodium, Sugar, Sat. Fat) to the settings UI.
    • Suggestions automatically filter out items already in the registry.
    • Added a "History" section to settings for one-tap restoration of archived nutrients with their original units.
  • 2026-01-10: Advanced Nutrient Polish & Mobile Optimization.
    • Compact UI: Redesigned DailyTotals to move labels and numeric data inside the progress bars, significantly reducing vertical height.
    • Readability (Dual-Layer Text): Implemented a sophisticated CSS clipping strategy in ProgressBar.tsx to render two layers of text (zinc background / white foreground). Text "color-shifts" dynamically as the bar fills, ensuring perfect contrast.
    • Tighter Spacing: Optimized spacing (space-y-2) and standardized bar heights (h-6 macros, h-5 extended) for a denser, more professional mobile dashboard.
    • Layout Simplification: Defaulted to the Vertical Layout as the single source of truth; removed redundant horizontal layout code and preference hooks.
    • Visual Polish:
      • Rounded logged meal calories to whole numbers to remove floating point artifacts.
      • Unified "Macro Pills" styling across MealItem and PendingMealCard.
      • Implemented dynamic color palette: Protein (Rose), Fiber (Emerald), Sodium (Sky), Sugar (Pink).
  • 2026-01-10: Stability & Task Optimization.
    • iOS Fix: Refactored date/time picker to use a transparent overlay, resolving issues where iPhone would block the system picker.
    • Workflow Speed: Optimized Taskfile.yml with sources directives, enabling instant skipping of up-to-date tests, linting, and type-checking.
    • Loading Shield: Added a "Loading Shield" to Dashboard.tsx to prevent TypeErrors during initial load.
    • Verification: Final test count: 88 passing (31 backend, 57 frontend). Includes 4 new component/hook integration tests.
  • 2026-01-10: Incident: Persistent AppSync JS Deployment Errors.
    • Problem: Deployment repeatedly failed with "The code contains one or more errors," even when logic passed local Vitest and official linter.
    • Discovery: APPSYNC_JS runtime has silent bans not caught by standard linting (e.g., passing functions as arguments inside .forEach() is forbidden).
    • Surgical Fix: Identified via aws appsync evaluate-code that util.toJson() and util.parseJson() are invalid in APPSYNC_JS 1.0.0. Switched to native JSON.stringify() and JSON.parse().
    • Success Protocol: Replaced all closures with for...of loops and moved to strict let/const with incremental object construction.
    • Final Result: Backend successfully deployed to TEST environment via task deploy:test. Total test count: 66 passing (at time of incident).

User Approval & Key Learnings

Key Learnings

  • Performance vs Security: Decided to pass a "Registry Snapshot" from the frontend to valid AI data instead of a "Read-Before-Write" DB hit, prioritizing cost efficiency.
  • Unit Integrity: Identified that changing units on a live tracking key would corrupt aggregate history; enforced Unit Immutability.
  • Runtime Realities: The APPSYNC_JS runtime validator is more primitive than the engine itself. Native JS globals (JSON, Math) are more reliable than util helpers for standard tasks.
  • The Persistence Trap: Found that PersistQueryClientProvider can re-hydrate stale JSON strings from localStorage into an otherwise type-safe application. Solved using React Query select as a definitive transformation layer.
  • Atomic Realities: Discovered that updating nested maps in DynamoDB (SET map.key) fails if the map doesn't exist yet. Switched to flat atomic counters with if_not_exists for 100% reliability on new days.
  • UX Density: Moving text inside bars proved to be the superior mobile strategy, allowing 5+ metrics to fit comfortably above the fold.

Final Approval: Feature complete and hardened. (User to mark as Complete)

Context Memory (AI-Only)

Summary for Future Context

The Flexible Nutrient Schema is implemented using a Metadata-Driven Map in DynamoDB. The CONFIG#TARGETS item acts as the registry for user-defined nutrients (Fiber, Sodium, etc.). Frontend provides a registrySnapshot to mutations to avoid extra DB lookups. Critical Development Rule: When writing APPSYNC_JS resolvers, avoid util.toJson/parseJson (use native JSON instead) and avoid .forEach() or any function-passing (use for...of). Always verify new resolvers using aws appsync evaluate-code.