The last-attempt lookup runs on the zero-rows path, which is exactly the path a
fresh account takes. When it threw, the whole nutrients read returned 500 — so an
account with nothing to show got an error instead of an empty page, and the
feature that exists to explain an empty surface was the thing preventing it from
rendering.
Wrapped so a failed ledger read degrades to the plain empty state and is recorded
for the operator rather than shown to the person. The route's shape assertion now
carries `lastAttempt: null` explicitly, so a field cannot join this response
without someone deciding it should.
GET /api/nutrients had two correctness bugs:
- `daysWithData` incremented on every row whose day differed from the
PINNED latest-day field, so a second source's row on an already-seen
older day (water logged via both Apple Health and a manual entry on
the same day) opened a phantom second "day". Track a per-nutrient
set of distinct day keys instead.
- The window floor used a UTC day key against a local-timezone `day`
column, off by up to a day at the edge for non-UTC users. Anchor on
the caller's own local "today" instead, mirroring the sibling
`GET /api/nutrients/daily` route.
Supplement-style intake written to Apple Health by nutrition apps —
vitamins, minerals, water, caffeine — can now sync as daily totals into a
dedicated NutrientIntakeDay store (migration 0241): composite (user, day,
nutrient) identity, re-post replaces, no soft delete, no rollup coupling.
A code-side catalog carries the 26 nutrient codes with canonical units,
plausibility caps, and EFSA dietary reference values (direction-aware;
chromium cites NIH ODS where EFSA declined a DRV).
Deliberately not a nutrition feature: no diary, no calories, no dashboard
surface. POST /api/nutrients/batch is gated behind the new opt-in
nutrients module (default off — with the module off the server refuses
ingest entirely), rate-limited, idempotency-wrapped, with per-entry
unit-mismatch and out-of-range skips. GET /api/nutrients serves a
catalog-ordered summary; the only visible surface is a read-only card
under Settings → Sources. Coach/correlation access is a later slice; the
iOS reading side is coordinated in the companion-app tracker.