Skip to content

Architecture: UI Data Validation

This document outlines the rules and behaviors for data input in the Frontend UI, serving as the contract between the User Interaction and the Backend Schema.


1. User Targets (Settings)

Data Types & Constraints

  • Calories (Energy):

    • Type: Float (stored as kcal).
    • UI Input: Number.
    • Min Value: 0.
    • Meaning of 0: No target set / Target disabled.
    • Empty Input: Allowed in UI (stored as "" in local state).
    • Normalization: On Save, converted to Float (or 0 if empty).
    • Display Logic:
      • Initial values: If 0, displays "".
      • User Input: Displays exactly what the user types (allows 0, 05, 2000.5 etc. while editing).
  • Macros (Protein, Carbs, Fat):

    • Type: Float (grams).
    • UI Input: Number (Step: 5g for better UX).
    • Min Value: 0.
    • Meaning of 0: No target set.
    • Empty Input: Allowed in UI (stored as "" in local state).
    • Normalization: On Save, converted to Float (or 0 if empty).
    • Display Logic:
      • Initial values: If 0, displays "".
      • User Input: Displays exactly what the user types.
    • Storage: Always stored as Floats.

Unit Conversion (Presentation Layer)

The application supports toggling between Calories (kcal) and Kilojoules (kJ). This is a presentation-only transformation for the Energy field.

  • Storage: Always Kcal.
  • Conversion Factor: 1 kcal = 4.184 kJ.
  • Rounding: Rounded to the nearest integer for display, but stored with precision.
  • Input Logic:
    • When user types in kJ mode, the value is divided by 4.184 before being stored in state as kcal.
    • Note: This can lead to minor precision loss (e.g., typing 1000 kJ -> 239.005 kcal -> displays as 1000 kJ).

2. Validation Philosophy

  • Loose UI, Strict Backend: We allow the user to type freely in the UI (stored as strings in local state). This ensures that if a user types 0 or clears the field, the UI reflects their action exactly.
  • Normalization on Save: Data is sanitized and converted to Floats only when clicking Done/Save. Empty strings or invalid inputs are normalized to 0.
  • Explicit "No Value": We use 0 to represent the absence of a numeric target, rather than null.
  • Backend Alignment: The backend ignores null values, reinforcing the reliance on 0 as the explicit "clear" signal from the UI.

3. Advanced Nutrient Tracking

Unit Immutability

To prevent historical data corruption (e.g., summing grams and milligrams), the Unit of a nutrient is permanent once defined in the Registry.

  • Rule: A nutrient definition (Key + Unit) cannot be edited.
  • Change Workflow:
    1. Disable: The user sets the status of "Sodium (mg)" to ARCHIVED.
    2. Create: The user creates a new nutrient "Sodium (g)".
  • UI Constraints: The "Unit" dropdown is disabled for any existing registry item.

Snapshot Protocol (Performance)

To avoid expensive "Read-Before-Write" checks on every addMeal call, the Frontend provides a validation snapshot.

  • Mechanism: The addMeal mutation accepts a registrySnapshot argument (JSON array of strings).
  • Source: The Frontend derives this list from the currently loaded UserTargets.
  • Backend Behavior: The resolver uses this list to filter the incoming extendedNutrients map. Any key NOT in the snapshot is silently discarded.
  • Trust Model: This is a "Trust but Verify" optimization. It prevents AI hallucinations from polluting the DB with random keys, without incurring a DB Read cost.

Optimistic Locking (Settings)

The UserTargets entity ("God Row") is protected against concurrent overwrites (e.g., editing on Phone while Tablet is open).

  • Attribute: version (Number).
  • Protocol:
    1. Read: Frontend fetches version with getUserTargets.
    2. Write: Frontend sends expectedVersion with updateUserTargets.
    3. Conflict: If DB.version !== expectedVersion, the write fails.
    4. Resolution: The UI must catch this error, refresh the settings, and ask the user to re-apply their changes.