Skip to content

Track supplement doses separately from plans - #2225

Merged
Asherlc merged 14 commits into
mainfrom
codex/issue-2064
Jul 28, 2026
Merged

Asherlc merged 14 commits into
mainfrom
codex/issue-2064

Conversation

@Asherlc

@Asherlc Asherlc commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Summary

  • replace fictional scheduled supplement food entries with immutable definition versions and append-only planned/taken/skipped/unknown dose events
  • scope materialization to the requested user and bounded effective dates, preserving provenance and never inferring a dose was taken
  • add only current explicitly taken doses to canonical nutrition, while preserving the exact installed-client supplements.list and supplements.save V1 projections
  • expose current status and event history on web and mobile with matching loading, error, empty, and populated states
  • remove historical auto-supplements food rows through the forward migration and document the durable schema contract

Root Cause

The internal auto-supplements provider ignored the requested user and materialized every scheduled definition as a confirmed food entry, so a plan was indistinguishable from consumed nutrition and one user's sync could recreate another user's fictional intake.

Validation

  • Node 26.5.0
  • pnpm tsc --noEmit
  • pnpm --dir packages/server exec tsc --noEmit
  • pnpm --dir packages/web exec tsc --noEmit
  • pnpm --dir packages/mobile exec tsc --noEmit
  • Biome, exact-version, suppression, workflow-download, analytics-policy, migration-policy, mobile-telemetry, and web-story checks
  • prior feature-head pnpm test: 889 files / 14,102 tests passed; 2 files / 21 tests skipped
  • exact-head affected unit/web tests: 14 files / 495 tests passed
  • exact-head affected mobile tests: 2 files / 11 tests passed
  • real PostgreSQL migration, cross-user, canonical-overlay, integration, mutation, and E2E coverage must pass on the exact PR head in CI because local Docker capacity is unavailable

Compatibility

  • the existing V1 supplement list/save procedures retain their exact definition-only request, success, and error shapes
  • the occurrence/history procedures are additive
  • the mobile persisted-query cache was already versioned by Fix overlapping nutrition source totals #2220, so upgraded clients do not restore older procedure payloads

Fixes #2064


Summary by cubic

Separate supplement plans from real doses using immutable schedules and append‑only dose events. Adds dose history on web/mobile, a 7‑day occurrences API, and only counts current taken doses toward nutrition. Fixes #2064.

  • New Features

    • APIs: supplements.occurrences (7‑day per‑user with counts) and supplements.recordDose (conflict‑safe taken/skipped); uses shared nutrition cache invalidation.
    • Stable schedules with immutable fitness.supplement_definition versions and row-based fitness.supplement_definition_nutrient; timezone‑bounded materialization with provider provenance; V1 supplements.list/save shapes unchanged.
    • Web/mobile SupplementDoseEventsPanel to review history and record doses; recording refreshes occurrences and daily nutrition; auto‑supplements materializes occurrences only and never creates food entries.
  • Migration

    • Run drizzle/0061_supplement_dose_events.sql to replace legacy supplements with fitness.supplement, fitness.supplement_definition, fitness.supplement_definition_nutrient, fitness.supplement_dose_event, and v_supplement_dose_current; backfills atomically.
    • Enforce migration policy to block concurrent index DDL; seeds updated to versioned definitions; provider disconnects and user deletes include fitness.supplement_dose_event.

Written for commit bdf4ca6. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added supplement dose tracking with planned, taken, skipped, and unknown statuses.
    • Added Recent Doses panels to web and mobile supplement screens, including dose history and actions to mark doses taken or skipped.
    • Updated nutrition totals to include nutrients only from explicitly taken supplement doses.
  • Documentation
    • Updated schema, roadmap, provider, migration, and incident documentation for the new supplement tracking model.
  • Bug Fixes
    • Improved cache refresh behavior after food and supplement dose updates.

Asherlc added 4 commits July 27, 2026 16:09
Keep scheduled supplements out of intake totals until a current append-only dose event explicitly records them as taken. Preserve stable definition schedules, source history, and installed V1 API shapes across web and mobile.
# Conflicts:
#	docs/production-incident-baseline.md
Local database validation remains unavailable after the volume cleanup incident. Host logs contain no fatal line or crash report, so the daemon termination cause remains unresolved.
Copilot AI review requested due to automatic review settings July 27, 2026 23:28
@Asherlc Asherlc linked an issue Jul 27, 2026 that may be closed by this pull request
@cursor

cursor Bot commented Jul 27, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @Asherlc, your pull request is larger than the review limit of 150000 diff characters

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR replaces automatic supplement food entries with versioned supplement definitions and append-only dose events. Only current taken events contribute nutrition, while server APIs and web/mobile panels expose status, history, and dose-recording actions.

Changes

Supplement dose event redesign

Layer / File(s) Summary
Schema and nutrition migration
src/db/schema/*, drizzle/0061_supplement_dose_events.sql, docs/schema.*
Adds versioned supplement definitions, definition nutrients, dose-event history, current-leaf views, and taken-only canonical nutrition aggregation.
Timezone-aware dose materialization
src/providers/auto-supplements.ts, src/providers/auto-supplements.test.ts, src/providers/auto-supplements.integration.test.ts
Materializes bounded user-scoped planned and unknown dose events without creating food entries.
Versioned repository and server APIs
packages/server/src/repositories/supplements-repository.ts, packages/server/src/routers/supplements.ts, packages/server/src/lib/nutrition-cache.ts
Preserves schedule identity across definition edits, records append-only dose successors, exposes occurrence/history APIs, and handles conflicts and cache invalidation.
Shared contracts
packages/format/src/supplement-dose-events.ts, src/db/execute-with-schema.ts
Adds dose-event schemas/status formatting and a shared schema-aware SQL execution helper.
Web and mobile interfaces
packages/web/src/components/SupplementDoseEventsPanel.tsx, packages/mobile/components/SupplementDoseEventsPanel.tsx, nutrition routes
Adds recent-dose panels with status counts, history, Taken/Skip actions, and loading/error/empty states.
Migration policy and seed alignment
scripts/migration-policy.ts, scripts/seed/*, src/processing/dataset-contracts.ts
Enforces transaction-compatible migration rules and updates seed and dataset contracts for definition-based supplement storage.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant supplementsRouter
  participant SupplementsRepository
  participant supplement_dose_event
  participant v_nutrition_canonical_nutrient
  Client->>supplementsRouter: recordDose(expectedCurrentEventId, status)
  supplementsRouter->>SupplementsRepository: recordDose()
  SupplementsRepository->>supplement_dose_event: lock current leaf and insert successor
  supplement_dose_event-->>SupplementsRepository: recorded event
  SupplementsRepository-->>supplementsRouter: dose result
  supplementsRouter-->>Client: invalidate nutrition caches and return result
  v_nutrition_canonical_nutrient->>supplement_dose_event: read current taken leaves
Loading

Assessment against linked issues

Objective Addressed Explanation
Separate planned, taken, skipped, and unknown statuses [#2064]
Ensure scheduled doses do not affect nutrition totals [#2064]
Expose explicit adherence status and dose history [#2064]

Possibly related PRs

  • Asherlc/dofek#1103: Both changes update Food router cache invalidation behavior.
  • Asherlc/dofek#1165: Both changes modify SupplementsRepository.save() and supplement view handling.
  • Asherlc/dofek#1175: Both changes modify migration-policy behavior for database migrations.

Suggested labels: area/server, area/web, area/mobile, area/providers, area/db, type/feature, breaking-change

🚥 Pre-merge checks | ✅ 1 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title is clear and imperative, but it lacks the required area prefix for this cross-cutting change. Prefix it with a relevant scope tag, e.g. "[server] Track supplement doses separately from plans", and keep it under 70 chars.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai

sourcery-ai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Implements per-user immutable supplement definitions with schedule identities and append-only dose events, rewrites the auto-supplements provider to materialize planned/unknown occurrences instead of food entries, integrates current taken doses into canonical nutrition, and exposes dose status/history APIs and UI on web/mobile while preserving existing V1 supplement list/save shapes.

Sequence diagram for recording a supplement dose event and updating nutrition

sequenceDiagram
  actor Client
  participant SupplementsRouter
  participant SupplementsRepository
  participant Database
  participant NutritionCache

  Client->>SupplementsRouter: recordDose(input)
  SupplementsRouter->>SupplementsRepository: recordDose(expectedCurrentEventId, status)
  SupplementsRepository->>Database: executeWithSchema(SELECT current event FOR UPDATE)
  Database-->>SupplementsRepository: currentDoseEvent
  SupplementsRepository->>Database: ensureProvider(DOFEK_PROVIDER_ID, DOFEK_PROVIDER_NAME, userId)
  SupplementsRepository->>Database: executeWithSchema(INSERT INTO fitness.supplement_dose_event)
  Database-->>SupplementsRepository: insertedId
  SupplementsRepository-->>SupplementsRouter: { id, scheduledDate, status }
  SupplementsRouter->>NutritionCache: invalidateNutritionCaches(userId)
  NutritionCache-->>SupplementsRouter: void
  SupplementsRouter-->>Client: recorded dose response
Loading

Entity relationship diagram for supplement definitions and dose events

erDiagram
  supplement {
    uuid id
    uuid userId
    uuid scheduleId
    uuid supersedesSupplementId
    date effectiveFrom
    date effectiveTo
  }

  supplementDoseEvent {
    uuid id
    uuid userId
    uuid scheduleId
    uuid supplementId
    date scheduledDate
    text status
    uuid supersedesEventId
  }

  supplement ||--o{ supplementDoseEvent : has_occurrence
  supplementDoseEvent }o--|| supplementDoseEvent : supersedes
Loading

File-Level Changes

Change Details Files
Refactor auto-supplements sync to be per-user, append-only dose-event materialization instead of food-entry upserts.
  • Require userId in SyncRun options and derive window dates using stored timezone or UTC fallback.
  • Query only the requested user’s effective supplement definitions within the requested date range.
  • Insert planned or unknown supplement_dose_event rows for each applicable occurrence with ON CONFLICT DO NOTHING.
  • Advance stale planned events to unknown via a set-based insert into v_supplement_dose_current without inferring taken.
  • Remove all fitness.food_entry writes from AutoSupplementsProvider and update unit/integration tests to assert dose-event behavior and strict user scoping.
src/providers/auto-supplements.ts
src/providers/auto-supplements.test.ts
src/providers/auto-supplements.integration.test.ts
Introduce immutable per-user supplement definition versions with stable schedule identities and a dose-event ledger, and wire them into canonical nutrition views.
  • Extend fitness.supplement table with scheduleId, supersedesSupplementId, effectiveFrom/effectiveTo, uniqueness and validity constraints.
  • Create fitness.supplement_dose_event table with status enum, supersession foreign keys, uniqueness, and provider/external identity constraints.
  • Add v_supplement_dose_current view for current event leaf per occurrence.
  • Rewrite v_supplement_with_nutrition to expose only active definitions with nutrients from supplement_nutrient.
  • Update v_nutrition_canonical_nutrient and v_nutrition_daily to add nutrients from current taken supplement_dose_event rows without causing source_conflict.
  • Add supplement_dose_event to provider-detail and user-settings deletion cascades and seed cleanup scripts.
src/db/schema/nutrition.ts
src/db/schema/enums.ts
drizzle/0061_supplement_dose_events.sql
drizzle/meta/_journal.json
packages/server/src/repositories/provider-detail-repository.ts
packages/server/src/repositories/provider-detail-repository.test.ts
packages/server/src/routers/provider-detail.test.ts
packages/server/src/repositories/settings-repository.ts
packages/server/src/repositories/settings-repository.test.ts
scripts/seed/core.ts
Version supplement definitions and expose dose occurrences and recording operations via the server repository and TRPC router while preserving V1 list/save shapes.
  • Enhance SupplementsRepository to list active definitions only, save by archiving changed versions and inserting immutable successors sharing scheduleId, and compute occurrence histories/counts from supplement_dose_event plus supplement.
  • Add recordDose to SupplementsRepository to append taken/skipped successors against the expected current event with conflict detection and provider identity ensurement.
  • Introduce SupplementDoseConflictError and unique violation mapping for stale or cross-user expectations.
  • Extend supplementsRouter with typed list, save, occurrences, and recordDose procedures, mapping conflict errors to TRPC CONFLICT and invalidating nutrition caches after dose recording.
  • Add an integration test suite for SupplementsRepository dose events and expand unit tests to cover versioning and scheduling semantics.
packages/server/src/repositories/supplements-repository.ts
packages/server/src/repositories/supplements-repository.test.ts
packages/server/src/repositories/supplement-dose-events.integration.test.ts
packages/server/src/routers/supplements.ts
packages/server/src/routers/supplements.test.ts
packages/server/src/lib/nutrition-cache.ts
Expose supplement dose-event status and history on web and mobile with matching loading/error/empty/populated states and cache invalidation behavior.
  • Create SupplementDoseEventsPanel components for web and mobile that read supplements.occurrences, render counts, status, history, and provide Taken/Skip actions wired to supplements.recordDose.
  • Handle loading/error/empty states explicitly and preserve cached occurrences on background query errors.
  • Invalidate food-by-date and nutrition analytics queries, plus supplements.occurrences, after successful dose recording; report recordDose errors via telemetry.
  • Integrate SupplementDoseEventsPanel into web nutrition/supplements route and mobile SupplementsScreen with updated copy emphasizing plans vs recorded doses.
  • Add Storybook stories and unit tests for SupplementDoseEventsPanel on both platforms to verify behavior, accessibility labels, and cache invalidation patterns.
packages/web/src/components/SupplementDoseEventsPanel.tsx
packages/web/src/components/SupplementDoseEventsPanel.test.tsx
packages/web/src/components/SupplementDoseEventsPanel.stories.tsx
packages/web/src/routes/nutrition/supplements.tsx
packages/mobile/components/SupplementDoseEventsPanel.tsx
packages/mobile/components/SupplementDoseEventsPanel.test.tsx
packages/mobile/components/SupplementDoseEventsPanel.stories.tsx
packages/mobile/app/supplements.tsx
packages/mobile/app/supplements.test.tsx
Adjust nutrition caches, food router cache invalidation, and docs to align with supplement dose-event-based nutrition overlay and durable V1 contracts.
  • Factor cache invalidation into a shared invalidateNutritionCaches helper and switch foodRouter mutations to use it instead of a local implementation.
  • Update food-repository selected-date meal breakdown to use canonical nutrient meal fields rather than joining food_entry.
  • Document new supplement schema, dose-event overlay, and V1 supplements.list/save compatibility in schema docs, README, provider README, roadmap, and production incident baseline.
  • Add format package module for supplement-dose-events with Zod schemas and status formatter, plus tests.
  • Update provider-detail DISCONNECT_CHILD_TABLES expectations and recovery-settings tests to account for supplement_dose_event in deletion flows.
packages/server/src/routers/food.ts
packages/server/src/repositories/food-repository.ts
packages/server/src/routers/food.test.ts
docs/schema.md
docs/schema.dbml
docs/schema.puml
README.md
src/providers/README.md
docs/roadmap.md
docs/production-incident-baseline.md
packages/format/src/supplement-dose-events.ts
packages/format/src/supplement-dose-events.test.ts
packages/format/package.json
packages/server/src/routers/provider-detail.test.ts
packages/server/src/repositories/provider-detail-repository.test.ts
packages/server/src/repositories/settings-repository.test.ts
packages/server/src/routers/recovery-settings-sleep-need-sport-settings.test.ts
docs/superpowers/plans/2026-07-27-supplement-dose-events.md

Assessment against linked issues

Issue Objective Addressed Explanation
#2064 Ensure nutrition totals never count merely scheduled supplement doses, and only explicitly taken doses contribute nutrients to canonical nutrition analysis.
#2064 Model supplement dose statuses (planned, taken, skipped, unknown) separately and expose explicit adherence status and edit history (including conflicts between imported and manual records) in the UI.
#2064 Refactor the auto-supplements sync so that it only materializes per-user, bounded supplement plans as dose events (without creating food entries or inferring doses were taken), preserving supplements list/save behavior while separating schedules from intake events.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Comment thread packages/mobile/components/SupplementDoseEventsPanel.test.tsx Fixed
Comment thread packages/web/src/components/SupplementDoseEventsPanel.test.tsx Fixed
@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Mobile Preview

Scan to open on device:

QR code for dofek://preview/pr-2225

Channel pr-2225
Deep Link dofek://preview/pr-2225
Commit 7fe512e

To test on device:

  1. Build and install the preview client: PREVIEW_CHANNEL=pr-2225 pnpm expo prebuild --clean -p ios
  2. Or tap deep link on an existing preview build: dofek://preview/pr-2225

Each PR gets its own channel. Build a preview client with PREVIEW_CHANNEL=pr-{N} to test.

@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Storybook previews for 7fe512ed are ready:

This comment updates automatically on each PR push.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Track supplement doses separately from plans via append-only dose events

🐞 Bug fix ✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Replace auto-supplements food entries with user-scoped append-only dose events.
• Version supplement definitions with stable schedule IDs and effective intervals.
• Expose dose status/history on web/mobile; only current taken doses affect nutrition.
Diagram

graph TD
  Web["Web doses UI"] --> TRPC["supplements tRPC"] --> Repo["SupplementsRepository"] --> DB[("Postgres supplement + dose_event")] --> Views["Nutrition views"]
  Mobile["Mobile doses UI"] --> TRPC
  Auto["AutoSupplementsProvider"] --> DB
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Model supplements as unconfirmed food_entry rows
  • ➕ Reuses existing food-entry pipeline and analytics surfaces
  • ➕ Avoids adding a new ledger table
  • ➖ Still conflates plans with intake and risks reintroducing fictional nutrition
  • ➖ Harder to represent durable status history and provenance cleanly
2. Single mutable occurrence row per schedule/date
  • ➕ Simpler storage and queries than a supersession chain
  • ➖ Loses append-only audit trail and provenance
  • ➖ Harder to make races safe without extra locking/versioning
3. Compute occurrences from schedules (no persisted planned/unknown)
  • ➕ Fewer writes; no materialization job
  • ➖ No stable event IDs for optimistic concurrency (expectedCurrentEventId)
  • ➖ Harder to preserve durable history and provider provenance

Recommendation: Keep the PR’s approach (versioned definitions + append-only dose-event ledger). It directly fixes the root cause (fictional intake and cross-user materialization), preserves provenance via constraints, supports optimistic concurrency with conflict handling, and composes cleanly into canonical nutrition as an additive overlay only for current taken leaves.

Files changed (45) +3404 / -846

Enhancement (8) +765 / -54
supplement-dose-events.tsIntroduce shared Zod schemas for dose occurrences +49/-0

Introduce shared Zod schemas for dose occurrences

• Defines Zod schemas/types for dose statuses, history events, and occurrences response. Provides a shared formatSupplementDoseStatus helper for UI parity.

packages/format/src/supplement-dose-events.ts

supplements.tsxAdd Recent Doses section to mobile supplements screen +17/-1

Add Recent Doses section to mobile supplements screen

• Updates copy to reflect plans vs intake and embeds SupplementDoseEventsPanel. Adds styling for a new subtitle explaining that only taken doses count toward nutrients.

packages/mobile/app/supplements.tsx

SupplementDoseEventsPanel.tsxImplement mobile dose panel with status, history, and actions +179/-0

Implement mobile dose panel with status, history, and actions

• Adds a RN panel that queries recent occurrences, shows status counts, renders occurrence history with provenance, and allows recording taken/skipped via mutation. Invalidates occurrences, food-by-date, and nutrition analytics on success and reports errors to telemetry.

packages/mobile/components/SupplementDoseEventsPanel.tsx

supplements-repository.tsImplement versioned definitions and append-only dose recording APIs +315/-41

Implement versioned definitions and append-only dose recording APIs

• Reworks save() to archive active definitions (effective_to) and insert immutable successors with stable schedule_id and supersedes pointers while preserving V1 shapes. Adds occurrences() to return current status + history and recordDose() to append a successor with optimistic concurrency and conflict handling, ensuring provider provenance.

packages/server/src/repositories/supplements-repository.ts

supplements.tsAdd additive dose-event endpoints and strict V1 outputs +61/-8

Add additive dose-event endpoints and strict V1 outputs

• Adds supplements.occurrences and supplements.recordDose endpoints with shared output schemas, conflict-to-TRPC CONFLICT mapping, and nutrition cache invalidation. Makes list/save outputs explicit to protect V1 projections.

packages/server/src/routers/supplements.ts

SupplementDoseEventsPanel.tsxImplement web dose panel with history and action buttons +128/-0

Implement web dose panel with history and action buttons

• Adds a panel that queries occurrences, shows status counts, renders each occurrence’s current status and append-only history, and allows marking taken/skipped. Invalidates occurrences, food-by-date, and nutrition analytics on success and reports errors to telemetry.

packages/web/src/components/SupplementDoseEventsPanel.tsx

SupplementStackPanel.tsxUpdate empty-state copy to reflect plan vs intake +1/-1

Update empty-state copy to reflect plan vs intake

• Changes empty state messaging to emphasize defining a plan and recording doses rather than auto-syncing nutrition intake.

packages/web/src/components/SupplementStackPanel.tsx

supplements.tsxAdd Recent Doses section to web supplements page +15/-3

Add Recent Doses section to web supplements page

• Updates the supplements route to include a new Recent Doses section with SupplementDoseEventsPanel and revised subtitle clarifying nutrient counting rules.

packages/web/src/routes/nutrition/supplements.tsx

Bug fix (2) +150 / -190
food-repository.tsFix meal totals query to include supplement overlay nutrients +2/-3

Fix meal totals query to include supplement overlay nutrients

• Changes meal grouping to use v_nutrition_canonical_nutrient.meal instead of joining food_entry (which is null for supplement overlay rows). Ensures taken supplement nutrients can appear in meal totals.

packages/server/src/repositories/food-repository.ts

auto-supplements.tsRewrite provider to create dose events instead of food entries +148/-187

Rewrite provider to create dose events instead of food entries

• Requires userId, reads stored timezone, selects only the requested user’s effective supplement definitions within the window, inserts planned/unknown dose events with provenance, and advances past planned events to unknown. Eliminates cross-user reads and stops creating fictional itemized food entries.

src/providers/auto-supplements.ts

Refactor (4) +26 / -20
nutrition-cache.tsCentralize nutrition cache invalidation +19/-0

Centralize nutrition cache invalidation

• Adds invalidateNutritionCaches to invalidate food.* and nutrition.* cache prefixes with consistent logging and Sentry capture on failure.

packages/server/src/lib/nutrition-cache.ts

provider-detail-repository.tsInclude supplement_dose_event in provider child tables +1/-0

Include supplement_dose_event in provider child tables

• Adds fitness.supplement_dose_event to the provider data table list for provider disconnect/deletion flows.

packages/server/src/repositories/provider-detail-repository.ts

settings-repository.tsDelete supplement dose events as part of deleteAllUserData +1/-0

Delete supplement dose events as part of deleteAllUserData

• Adds fitness.supplement_dose_event to USER_SCOPED_DELETE_TABLES so user wipes remove dose history before supplements.

packages/server/src/repositories/settings-repository.ts

food.tsUse shared nutrition cache invalidation in food mutations +5/-20

Use shared nutrition cache invalidation in food mutations

• Replaces the router-local cache invalidation helper with invalidateNutritionCaches for create/update/delete/quickAdd. Keeps cache behavior consistent with supplement dose recording.

packages/server/src/routers/food.ts

Tests (15) +1305 / -570
supplement-dose-events.test.tsTest shared supplement dose schemas and formatting +35/-0

Test shared supplement dose schemas and formatting

• Adds unit tests for status formatting and schema validation (rejects unsupported statuses).

packages/format/src/supplement-dose-events.test.ts

supplements.test.tsxUpdate supplements screen tests for new TRPC usage +19/-0

Update supplements screen tests for new TRPC usage

• Extends TRPC mocks to include occurrences and recordDose used by the new dose panel. Keeps screen tests stable with empty occurrence fixtures.

packages/mobile/app/supplements.test.tsx

SupplementDoseEventsPanel.test.tsxAdd mobile dose panel tests for states and actions +172/-0

Add mobile dose panel tests for states and actions

• Adds jsdom tests covering loading/error/empty/populated states and Taken/Skip interactions. Verifies recordDose calls and cache invalidation wiring.

packages/mobile/components/SupplementDoseEventsPanel.test.tsx

provider-detail-repository.test.tsUpdate provider detail tests for dose-event table inclusion +6/-5

Update provider detail tests for dose-event table inclusion

• Adjusts DISCONNECT_CHILD_TABLES expectations and call counts to include fitness.supplement_dose_event.

packages/server/src/repositories/provider-detail-repository.test.ts

settings-repository.test.tsUpdate user data deletion tests for dose-event cleanup +5/-5

Update user data deletion tests for dose-event cleanup

• Updates expected delete counts to include fitness.supplement_dose_event and notes ordering before supplement definitions.

packages/server/src/repositories/settings-repository.test.ts

supplement-dose-events.integration.test.tsAdd Postgres integration tests for dose-event behavior and nutrition overlay +250/-0

Add Postgres integration tests for dose-event behavior and nutrition overlay

• Validates immutable definition versioning, append-only successor insertion with conflict protection, cross-user isolation, and that taken supplements overlay into canonical nutrients without source conflicts.

packages/server/src/repositories/supplement-dose-events.integration.test.ts

supplements-repository.test.tsUpdate repository unit tests for schedule/version behavior +97/-1

Update repository unit tests for schedule/version behavior

• Extends fixtures for schedule_id/effective interval fields and asserts V1 list projection remains definition-only. Adds tests for rename behavior creating immutable successors while preserving schedule identity.

packages/server/src/repositories/supplements-repository.test.ts

food.test.tsUpdate cache invalidation log expectation +1/-1

Update cache invalidation log expectation

• Adjusts test assertions for the new shared nutrition cache invalidation log message prefix.

packages/server/src/routers/food.test.ts

provider-detail.test.tsUpdate provider-detail router tests for dose-event table list +3/-2

Update provider-detail router tests for dose-event table list

• Updates DISCONNECT_CHILD_TABLES length and contents to include fitness.supplement_dose_event.

packages/server/src/routers/provider-detail.test.ts

recovery-settings-sleep-need-sport-settings.test.tsUpdate settings router deleteAllUserData count expectation +1/-1

Update settings router deleteAllUserData count expectation

• Adjusts expected execution count to include the additional user-scoped dose-event delete.

packages/server/src/routers/recovery-settings-sleep-need-sport-settings.test.ts

supplements-sync.test.tsUpdate supplements-sync tests for new view columns and versioning +13/-4

Update supplements-sync tests for new view columns and versioning

• Extends mocked v_supplement_with_nutrition rows with schedule_id/supersedes/effective interval fields. Updates expectations for transaction execute calls now required for archival/versioned saves.

packages/server/src/routers/supplements-sync.test.ts

supplements.test.tsAdd router coverage for occurrences/recordDose and V1 stability +142/-2

Add router coverage for occurrences/recordDose and V1 stability

• Adds assertions that supplements.list/save preserve exact installed-client V1 shapes. Adds coverage for occurrences output and recordDose cache invalidation + conflict mapping.

packages/server/src/routers/supplements.test.ts

SupplementDoseEventsPanel.test.tsxAdd web dose panel tests for states and actions +182/-0

Add web dose panel tests for states and actions

• Adds RTL tests validating loading/error/empty/populated states, Taken/Skip actions calling recordDose, and telemetry/error wiring. Verifies invalidation hooks are invoked on success.

packages/web/src/components/SupplementDoseEventsPanel.test.tsx

auto-supplements.integration.test.tsRewrite integration tests for occurrence materialization and idempotency +257/-193

Rewrite integration tests for occurrence materialization and idempotency

• Updates tests to assert sync is user-scoped, bounded by effective dates and window, idempotent, does not create food_entry rows, and advances stale planned leaves to unknown without rewriting history.

src/providers/auto-supplements.integration.test.ts

auto-supplements.test.tsRefocus unit tests on new dose-event materialization behavior +122/-356

Refocus unit tests on new dose-event materialization behavior

• Removes legacy buildDailyEntries-based tests and replaces them with mocked execute flows asserting timezone parsing, definition scoping, and insert-on-conflict behavior for dose events.

src/providers/auto-supplements.test.ts

Documentation (8) +357 / -10
README.mdDocument supplement dose-event model and constraints +9/-0

Document supplement dose-event model and constraints

• Adds an overview of immutable supplement definition versions under stable schedule identities. Documents append-only planned/taken/skipped/unknown dose events and that only current taken leaves contribute to canonical nutrition.

README.md

production-incident-baseline.mdRecord Docker daemon loss incident during validation +61/-0

Record Docker daemon loss incident during validation

• Adds a detailed incident log describing an unauthorized broad Docker volume prune, resulting volume deletions, and ongoing inability to run local Postgres validation. Captures evidence, mitigation, and follow-up guidance.

docs/production-incident-baseline.md

roadmap.mdUpdate roadmap entry to reflect dose-event supplements +3/-1

Update roadmap entry to reflect dose-event supplements

• Replaces the previous auto-supplements food-entry approach with immutable schedules and append-only dose-event corrections. Notes only current taken leaves contribute nutrients.

docs/roadmap.md

schema.dbmlExtend schema DBML for supplement versioning and dose events +61/-6

Extend schema DBML for supplement versioning and dose events

• Adds supplement_dose_status enum, supplement schedule/effective interval fields and indexes, and the supplement_dose_event table with keys and references. Updates relationship section for new foreign keys.

docs/schema.dbml

schema.mdDocument durable supplement schema and nutrition overlay semantics +18/-2

Document durable supplement schema and nutrition overlay semantics

• Adds supplement and dose-event tables/views to the schema listing and clarifies that planned/skipped/unknown contribute nothing. Documents V1 supplements.list/save shape stability and additive occurrences/recordDose procedures.

docs/schema.md

schema.pumlUpdate PlantUML ER diagram for dose events +23/-0

Update PlantUML ER diagram for dose events

• Adds supplement schedule/effective interval columns and introduces supplement_dose_event entity. Connects dose events to user_profile and provider.

docs/schema.puml

2026-07-27-supplement-dose-events.mdAdd TDD plan for supplement dose events +179/-0

Add TDD plan for supplement dose events

• Adds a detailed plan covering goals, scope, test strategy, schema/migration tasks, and web/mobile parity requirements for the dose-event model.

docs/superpowers/plans/2026-07-27-supplement-dose-events.md

README.mdClarify auto-supplements materializes occurrences (not intake) +3/-1

Clarify auto-supplements materializes occurrences (not intake)

• Updates provider docs to describe Auto-Supplements as an internal schedule provider that materializes bounded, user-scoped occurrences and never writes food entries or infers taken.

src/providers/README.md

Other (8) +801 / -2
0061_supplement_dose_events.sqlAdd dose-event ledger, versioned supplements, and nutrition overlays +474/-0

Add dose-event ledger, versioned supplements, and nutrition overlays

• Adds schedule_id/supersedes/effective interval columns and constraints to fitness.supplement and creates supplement_dose_status + supplement_dose_event with composite FKs and uniqueness. Deletes historical auto-supplements food_entry rows, adds v_supplement_dose_current, rebuilds v_supplement_with_nutrition, and updates v_nutrition_canonical_nutrient/v_nutrition_daily to include only current taken dose-event nutrients as an overlay.

drizzle/0061_supplement_dose_events.sql

_journal.jsonRegister migration 0061_supplement_dose_events +7/-0

Register migration 0061_supplement_dose_events

• Adds a Drizzle journal entry for the new migration tag and breakpoint metadata.

drizzle/meta/_journal.json

package.jsonExport supplement-dose-events formatter/schemas +1/-0

Export supplement-dose-events formatter/schemas

• Adds a new export path for ./supplement-dose-events so server/web/mobile can share the contract.

packages/format/package.json

SupplementDoseEventsPanel.stories.tsxAdd Storybook scenarios for mobile dose panel +99/-0

Add Storybook scenarios for mobile dose panel

• Introduces default/empty/loading/error stories using a mocked TRPC link and QueryClientProvider. Enables visual verification of states and interactions.

packages/mobile/components/SupplementDoseEventsPanel.stories.tsx

SupplementDoseEventsPanel.stories.tsxAdd Storybook scenarios for web dose panel +108/-0

Add Storybook scenarios for web dose panel

• Adds default/empty/loading/error stories using a mocked TRPC link and QueryClientProvider to validate UI states and action flows.

packages/web/src/components/SupplementDoseEventsPanel.stories.tsx

core.tsClear supplement_dose_event during seed reset +1/-0

Clear supplement_dose_event during seed reset

• Ensures reseeding deletes supplement dose events for the seed user to avoid accumulating history between runs.

scripts/seed/core.ts

enums.tsAdd supplement_dose_status enum +7/-0

Add supplement_dose_status enum

• Defines the supplement_dose_status enum (planned/taken/skipped/unknown) for the new dose-event ledger table and views.

src/db/schema/enums.ts

nutrition.tsAdd versioned supplement fields and supplement_dose_event table +104/-2

Add versioned supplement fields and supplement_dose_event table

• Extends supplement with scheduleId, supersedesSupplementId, and effectiveFrom/effectiveTo plus partial unique indexes and interval validity check. Adds supplementDoseEvent table with composite FKs, uniqueness constraints, and indexes enforcing a linear append-only history per occurrence slot.

src/db/schema/nutrition.ts

@qodo-code-review

qodo-code-review Bot commented Jul 27, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 233 rules

Grey Divider


Action required

1. schedule_id default missing ✓ Resolved 🐞 Bug ≡ Correctness
Description
Migration 0061 makes fitness.supplement.schedule_id NOT NULL without setting a database DEFAULT,
but SupplementsRepository.save() inserts new supplements with scheduleId absent when there is no
prior version. Creating a new supplement definition will fail with a NOT NULL violation on
schedule_id.
Code

drizzle/0061_supplement_dose_events.sql[R4-15]

+ALTER TABLE fitness.supplement
+ADD COLUMN schedule_id uuid;
+--> statement-breakpoint
+
+UPDATE fitness.supplement
+SET schedule_id = id
+WHERE schedule_id IS NULL;
+--> statement-breakpoint
+
+ALTER TABLE fitness.supplement
+ALTER COLUMN schedule_id SET NOT NULL;
+--> statement-breakpoint
Relevance

⭐⭐⭐ High

Schema/migration correctness issues that can break writes are typically fixed during review.

PR-#1365
PR-#1572

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The migration creates a NOT NULL column without a default, while the repository insert path does not
provide a value for new supplements, so the DB must supply a default and currently cannot.

drizzle/0061_supplement_dose_events.sql[4-15]
packages/server/src/repositories/supplements-repository.ts[159-246]
src/db/schema/nutrition.ts[31-49]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The migration adds `fitness.supplement.schedule_id` and sets it `NOT NULL` but does not set a DB-level default. The application expects a default schedule identity for new supplements (schema declares `defaultRandom()`), and `SupplementsRepository.save()` does not provide `scheduleId` for brand-new entries.

## Issue Context
- With the current SQL, inserts that omit `schedule_id` will fail in Postgres.
- The repository insert path for new supplements passes `scheduleId: operation.existing?.scheduleId` which is `undefined` for new entries.

## Fix Focus Areas
- drizzle/0061_supplement_dose_events.sql[4-15]
- packages/server/src/repositories/supplements-repository.ts[232-246]
- src/db/schema/nutrition.ts[31-49]

## Suggested fix
1. Update the migration to set a DB default on the column, e.g.:
  - `ALTER TABLE fitness.supplement ALTER COLUMN schedule_id SET DEFAULT gen_random_uuid();`
  Place this after the column is added/backfilled and before/after the `SET NOT NULL` step.
2. (Optional hardening) In `SupplementsRepository.save()`, explicitly generate a UUID for brand-new entries when `operation.existing` is null, so the code path is robust even if the DB default is ever missing in a non-migrated environment.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Agent-only instructions in plan ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
The new TDD plan includes instructions that assume the presence of an AI/agent workflow (repository
“skills”) without providing an equivalent non-agent alternative. This violates the docs policy that
documentation must not require or assume an AI agent is present.
Code

docs/superpowers/plans/2026-07-27-supplement-dose-events.md[R3-5]

+> **For agentic workers:** Use the repository's issue, test, integration-test,
+> shipping, and review-comment skills while executing this plan. Complete each
+> behavior test before its implementation.
Relevance

⭐⭐⭐ High

Prior docs-plan PRs removed “For agentic workers” requirements to keep docs human-actionable.

PR-#1241
PR-#1468
PR-#2207

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 773497 forbids docs that require/assume an AI agent without a documented non-agent
alternative. The added block explicitly instructs “agentic workers” to use agent-specific “skills”.

Rule 773497: Docs must not require or assume an AI agent is present
docs/superpowers/plans/2026-07-27-supplement-dose-events.md[3-5]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New documentation includes agent-only instructions (references to “agentic workers” and repository “skills”) without an equivalent human/CLI workflow.

## Issue Context
The docs policy requires that any mention of an AI agent be optional and accompanied by a concrete non-agent alternative in the same section.

## Fix Focus Areas
- docs/superpowers/plans/2026-07-27-supplement-dose-events.md[3-5]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. recordDose rethrows unknown error ✓ Resolved 📘 Rule violation ≡ Correctness
Description
The new supplements.recordDose procedure maps SupplementDoseConflictError to a TRPCError, but
rethrows other unknown errors directly. This violates the requirement to wrap client-facing
procedure failures in TRPCError with semantic codes.
Code

packages/server/src/routers/supplements.ts[R55-68]

+      const repository = new SupplementsRepository(ctx.db, ctx.userId, ctx.timezone);
+      try {
+        const recorded = await repository.recordDose(input.expectedCurrentEventId, input.status);
+        await invalidateNutritionCaches(ctx.userId);
+        return recorded;
+      } catch (error: unknown) {
+        if (error instanceof SupplementDoseConflictError) {
+          throw new TRPCError({
+            code: "CONFLICT",
+            message: error.message,
+            cause: error,
+          });
+        }
+        throw error;
Relevance

⭐⭐⭐ High

Team has accepted replacing throw error with a TRPCError wrapper for unknown failures.

PR-#2045
PR-#1960

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 722038 requires tRPC procedure failures to be thrown as TRPCError with semantic
codes. In the new recordDose handler, non-conflict errors are rethrown (throw error) rather than
being wrapped.

Rule 722038: Use TRPCError with semantic error codes for all tRPC procedure failures
packages/server/src/routers/supplements.ts[55-68]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The tRPC procedure rethrows unknown errors instead of wrapping them in `TRPCError`, which can leak non-semantic errors to clients and break consistent error handling.

## Issue Context
The compliance rule requires that errors intended to reach clients be expressed as `TRPCError` with appropriate semantic `code` values.

## Fix Focus Areas
- packages/server/src/routers/supplements.ts[55-68]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. sync() throws missing userId ✗ Dismissed 📘 Rule violation ≡ Correctness
Description
AutoSupplementsProvider.sync() throws a generic Error when run.options.userId is missing
instead of returning a SyncResult describing the failure. This breaks the requirement that
provider sync outcomes be represented via SyncResult rather than exceptions.
Code

src/providers/auto-supplements.ts[R61-63]

+    const userId = run.options.userId;
+    if (!userId) {
+      throw new Error("auto-supplements sync requires userId");
Relevance

⭐⭐⭐ High

Provider sync flows are expected to return SyncResult-style errors rather than throwing.

PR-#1291
PR-#1284

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 722135 requires provider sync methods to return SyncResult (including for handled
error paths) rather than throwing. The new code throws `new Error("auto-supplements sync requires
userId") inside sync()`.

Rule 722135: Use SyncResult for all provider sync method outcomes
src/providers/auto-supplements.ts[61-63]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Provider sync throws `Error` on an error path, instead of returning a `SyncResult` with structured failure information.

## Issue Context
Provider sync methods should consistently report outcomes via `SyncResult` (including failures), so callers can handle errors uniformly.

## Fix Focus Areas
- src/providers/auto-supplements.ts[61-63]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (2)
5. run.db.execute without executeWithSchema ✓ Resolved 📘 Rule violation ≡ Correctness
Description
AutoSupplementsProvider.sync() uses raw run.db.execute(...) and then manually parses results,
rather than using the standard executeWithSchema helper for schema-validated query execution. This
increases the risk of schema drift and inconsistent result-shape handling across the codebase.
Code

src/providers/auto-supplements.ts[R66-88]

+    const timezone = parseStoredTimezone(
+      await run.db.execute(
+        sql`SELECT value
+            FROM fitness.user_settings
+            WHERE user_id = ${userId}
+              AND key = 'timezone'
+            LIMIT 1`,
+      ),
+    );
+    const startDate = formatDateYmdInTimeZone(run.window.since, timezone);
+    const endDate = formatDateYmdInTimeZone(run.window.until, timezone);
+    const today = formatDateYmdInTimeZone(new Date(), timezone);
+
+    const definitions = z.array(supplementDefinitionRowSchema).parse(
+      await run.db.execute(
+        sql`SELECT id, schedule_id, effective_from, effective_to
+            FROM fitness.supplement
+            WHERE user_id = ${userId}
+              AND effective_from <= ${endDate}::date
+              AND (effective_to IS NULL OR effective_to > ${startDate}::date)
+            ORDER BY schedule_id, effective_from, created_at`,
+      ),
+    );
Relevance

⭐⭐⭐ High

Strong precedent: raw SQL SELECTs should use executeWithSchema for runtime shape validation.

PR-#2219
PR-#1860
PR-#1158

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 722037 requires using executeWithSchema (or an approved equivalent) for raw SQL
calls that return rows. The provider executes a SELECT value ... and a `SELECT id, schedule_id,
... via run.db.execute(...)` in newly added code.

Rule 722037: Use executeWithSchema for raw SQL queries instead of untyped execution
src/providers/auto-supplements.ts[66-88]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Raw SQL queries are executed via `run.db.execute(...)` without using the repo’s `executeWithSchema` helper.

## Issue Context
The compliance rule requires schema-aware execution to avoid silent type/shape mismatches when SQL changes.

## Fix Focus Areas
- src/providers/auto-supplements.ts[66-88]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Dose events deduped on write ✗ Dismissed 📘 Rule violation ⌂ Architecture
Description
The new dose-event ingestion path uses ON CONFLICT DO NOTHING and a unique index on `(user_id,
provider_id, external_id)`, which drops duplicate incoming records instead of preserving all raw
ingested events. This violates the requirement to avoid deduplication/collapsing in write paths and
keep the raw store append-only.
Code

src/providers/auto-supplements.ts[R108-131]

+            sql`INSERT INTO fitness.supplement_dose_event (
+                user_id,
+                schedule_id,
+                supplement_id,
+                provider_id,
+                external_id,
+                scheduled_date,
+                status,
+                recorded_at,
+                source_name
+              )
+              VALUES (
+                ${userId},
+                ${definition.schedule_id}::uuid,
+                ${definition.id}::uuid,
+                ${PROVIDER_ID},
+                ${`schedule:${definition.schedule_id}:${date}:${status}`},
+                ${date}::date,
+                ${status}::fitness.supplement_dose_status,
+                ${recordedAt},
+                ${PROVIDER_NAME}
+              )
+              ON CONFLICT DO NOTHING
+              RETURNING id`,
Relevance

⭐⭐ Medium

Repo often uses unique keys + ON CONFLICT for idempotency; unclear if this event store must be
append-only.

PR-#1365
PR-#1375
PR-#1110

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 773547 forbids deduplication/collapsing in ingestion write paths. The sync provider
inserts into fitness.supplement_dose_event with ON CONFLICT DO NOTHING, and the migration adds a
unique index on (user_id, provider_id, external_id), together enforcing write-time deduplication.

Rule 773547: Preserve all raw ingested records with source attribution; no deduplication in write paths
src/providers/auto-supplements.ts[108-171]
drizzle/0061_supplement_dose_events.sql[133-140]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The dose-event ingestion path is deduplicating records at write time via `ON CONFLICT DO NOTHING` and a uniqueness constraint, which prevents storing all ingested records.

## Issue Context
Compliance requires raw ingested records to be preserved with source attribution and prohibits deduplication/collapsing in ingestion write paths; deduplication should be done in read models (views/materialized views/downstream tables) instead.

## Fix Focus Areas
- src/providers/auto-supplements.ts[108-171]
- drizzle/0061_supplement_dose_events.sql[133-140]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread docs/superpowers/plans/2026-07-27-supplement-dose-events.md Outdated
Comment thread src/providers/auto-supplements.ts
Comment thread src/providers/auto-supplements.ts
Comment thread packages/server/src/routers/supplements.ts Outdated
Comment thread src/providers/auto-supplements.ts Outdated
Comment thread drizzle/0061_supplement_dose_events.sql
Asherlc added 2 commits July 27, 2026 17:15
Replace supplement rows atomically with stable schedules and immutable definitions. Keep dose events linked to both identities and enforce transaction-bound normalization in migration policy.
@codereviewbot-ai

codereviewbot-ai Bot commented Jul 28, 2026

Copy link
Copy Markdown

LGTM!

Summary of Review

  • Database Migration (0061_supplement_dose_events.sql): Schema normalization into supplement schedules (fitness.supplement), versioned definitions (fitness.supplement_definition), canonical nutrients (fitness.supplement_definition_nutrient), and append-only dose events (fitness.supplement_dose_event) is cleanly structured with proper indexes and constraints.
  • Repository Implementation (SupplementsRepository): Concurrency safety with FOR UPDATE transaction locks, optimistic concurrency on dose supersession, Zod schema runtime validation, and proper effective date versioning.
  • Repository & System Integration: Child table disconnects (provider-detail-repository.ts), user data deletion order (settings-repository.ts), and cache invalidation (nutrition-cache.ts) correctly incorporate supplement_dose_event.
  • Mobile UI & Formatting: Robust handling of loading, error, and cached states in SupplementDoseEventsPanel, with proper TRPC query invalidation on dose recording.

🤖 Reviewed by codereviewbot.ai - Catch bugs before your team does.

@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

Reload router modules in schema tests so Stryker exercises static Zod initialization, and assert provider date/status/count boundaries through the public sync contract.
@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

…sue-2064

# Conflicts:
#	docs/production-incident-baseline.md
@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

Assert reconciliation, occurrence, row-boundary, and dose-conflict behavior so the repository mutation shard verifies its observable contracts.
@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
drizzle/.sqlfluff (1)

1-4: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Pin sqlfluff to a version that accepts large_file_skip_byte_limit = 0.

large_file_skip_byte_limit = 0 disables the size skip, but sqlfluff has a known regression where this can raise TypeError: '>' not supported between instances of 'int' and 'str' in affected versions. Pin sqlfluff to a fixed release to keep CI linting consistent in drizzle/.sqlfluff.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@drizzle/.sqlfluff` around lines 1 - 4, Pin the sqlfluff dependency used by CI
to a fixed release that supports large_file_skip_byte_limit = 0 without the
TypeError regression. Keep the existing drizzle/.sqlfluff setting unchanged and
ensure all linting environments resolve the same compatible version.
packages/server/src/repositories/settings-repository.test.ts (1)

232-250: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

The test name promises ordering ("dose events before supplements") but only asserts a call count.

supplement_dose_event has FKs into supplement/supplement_definition, so deletion order is the thing that can break. A count of 5 passes even if the order is inverted. Assert the relative index of the two statements, e.g. by stringifying transactionExecute.mock.calls and comparing positions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/server/src/repositories/settings-repository.test.ts` around lines
232 - 250, Update the test around SettingsRepository.deleteAllUserData to verify
deletion ordering, not just the five-call count: inspect
transactionExecute.mock.calls, identify the supplement_dose_event and supplement
deletion statements, and assert the dose-event statement occurs at a lower index
than the supplement statement. Preserve the existing call-count assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/production-incident-baseline.md`:
- Around line 19419-19435: Update the incident record’s “Root cause” section to
cite authoritative PostgreSQL documentation for the restriction on concurrent
CREATE INDEX and DROP INDEX operations inside transactions. Add links to the
relevant PostgreSQL CREATE INDEX and DROP INDEX references while preserving the
existing explanation and mitigation details.

In `@drizzle/0061_supplement_dose_events.sql`:
- Around line 240-242: Before the DELETE in the migration, add a preflight count
for rows matching provider_id = 'auto-supplements' and use it to assess the
migration’s expected workload; preserve the existing DDL transaction and
all-or-nothing rollback behavior. If execution is slow, record the observed
timing in docs/production-incident-baseline.md, without splitting the DELETE
into a separate transaction.

In `@packages/mobile/components/SupplementDoseEventsPanel.test.tsx`:
- Around line 81-98: Add a top-level invalidate function backed by
mocks.invalidate to the mocked useUtils() return value in
SupplementDoseEventsPanel.test.tsx, while preserving the existing food,
nutritionAnalytics, and supplements utilities. This ensures the assertion around
the no-op dose flow can detect unexpected invalidation calls.

In `@packages/server/src/repositories/settings-repository.ts`:
- Line 35: Reorder the table deletion entries so fitness.supplement_dose_event
appears before fitness.supplement_definition and fitness.supplement. Preserve
all existing entries and deletion behavior; only adjust the ordering in the
relevant settings repository list.

In `@packages/server/src/repositories/supplements-repository.test.ts`:
- Around line 627-716: Replace the catch-all test around freshSupplementSchema
and FreshSupplementsRepository with focused tests using the existing static
imports, removing vi.resetModules() and the dynamic import. Keep one test for
schema boundary rejection and another for invalid view-row rejection; remove the
duplicated list, occurrences, and recordDose happy-path assertions, while
preserving the existing invalid inputs and repository rejection behavior.

In `@packages/server/src/repositories/supplements-repository.ts`:
- Around line 207-219: Resolve the effective-date boundary semantics across the
update flow around the transaction that closes entries and the successor
creation logic: prevent the archived definition and successor from both being
active on effectiveDate. Either set the prior version’s effective_to to the
preceding day and update the related schema constraint/consumers accordingly, or
establish effective_to as exclusive and update every effective-window predicate
in v_supplement_with_nutrition and the dose materializer to use that convention
consistently.
- Around line 118-122: Update definitionsEqual to compare the parsed Supplement
fields explicitly rather than comparing JSON.stringify results. Parse both
values with supplementSchema, then compare each relevant normalized field by
value so equivalent definitions remain equal regardless of object key ordering.

In `@packages/server/src/routers/admin.ts`:
- Around line 200-201: Add `supplement_dose_event` to the `target_tables` list
used by the admin overview counts, alongside the existing supplement-related
table entries. Preserve the current counting and diagnostic behavior for all
other tables.

In `@packages/web/src/components/SupplementDoseEventsPanel.test.tsx`:
- Around line 81-98: Wire mocks.invalidate into the top-level object returned by
the mocked trpc.useUtils() in SupplementDoseEventsPanel.test.tsx, alongside the
existing food, nutritionAnalytics, and supplements entries. Keep the existing
assertion on mocks.invalidate so it can detect unintended broad
utils.invalidate() calls, and apply the same mock wiring to the corresponding
mobile test.
- Line 1: Update the useUtils() mock return objects in both
SupplementDoseEventsPanel test files to expose the declared mocks.invalidate spy
as the top-level invalidate property. Keep the existing not.toHaveBeenCalled
assertions unchanged so they validate the component behavior rather than an
unwired mock field.

In `@src/db/schema/nutrition.ts`:
- Around line 68-86: Add a self-supersedence CHECK constraint to the
supplementDefinition schema, named supplement_definition_not_self_superseding,
rejecting rows where supersedesDefinitionId equals id while allowing NULL
references. Add the matching constraint to the fitness.supplement_definition
table definition in drizzle/0061_supplement_dose_events.sql, alongside the
existing constraints.

In `@src/db/supplement-dose-migration.integration.test.ts`:
- Around line 187-214: Add assertions in the migration integration test around
definitionRowsSchema to verify each migrated definition has effective_to set to
null, updating the selected fields/schema and expected rows as needed. Then
query fitness.v_supplement_with_nutrition, parse the result with a suitable
schema, and assert the expected user-visible rows and nutrition values in sort
order.

In `@src/processing/dataset-contracts.ts`:
- Around line 281-286: Update the nutrition dataset’s sources declaration in
dataset-contracts to include supplement_dose_event alongside the existing
supplement tables. Preserve the current source entries and ordering, and ensure
the declared sources cover the table used to gate whether supplement nutrient
amounts count toward nutrition totals.

In `@src/providers/auto-supplements.integration.test.ts`:
- Around line 291-346: Update the migration test around “migration cleanup
removes only fictional auto-supplement foods and cascades nutrients” so it
exercises drizzle/0061_supplement_dose_events.sql rather than issuing its own
equivalent DELETE. Seed the fictional and real food rows before applying the
migration, then assert only provider_id='auto-supplements' rows and their
nutrients are removed while the real provider rows remain; preserve the cascade
assertions.
- Around line 123-130: Update the dose-event query in the test around
supplementDoseEvent to filter by both PRIMARY_USER_ID and the relevant
definition primary.id, then add an explicit ascending order by scheduledDate
before executing it. Keep the existing order-sensitive assertion unchanged.
- Around line 252-260: Replace the generic db.execute call assigned to takenRows
with executeWithSchema(), defining and applying a Zod schema for amount,
food_entry_id, and supplement_dose_event_id so the query result is validated at
runtime before the subsequent assertions.

In `@src/providers/auto-supplements.test.ts`:
- Around line 87-90: The serialized SQL assertions in
src/providers/auto-supplements.test.ts lines 87-90 and 162-164 need updating:
remove the not.toContain("fitness.food_entry") assertion at lines 87-90, and at
lines 162-164 capture each insert’s bound status parameter and positively assert
it is either "planned" or "unknown" instead of searching serialized SQL for
'"taken"'.

In `@src/providers/auto-supplements.ts`:
- Around line 111-145: Replace the nested loops in the supplement
synchronization flow with one set-based INSERT ... SELECT using generate_series
for the date window and the existing definition applicability predicate,
eliminating per-(definition, date) awaits and repeated datesInRange calls.
Preserve the current status, external_id, conflict handling, and inserted-row
counting semantics; add a concise SQL comment documenting that the partial
root-key uniqueness absorbs planned/unknown duplicates.
- Around line 34-43: Validate the three dates passed to datesInRange in sync()
before generating the range, rejecting any " -- " sentinel with a specific named
error. Keep datesInRange focused on valid YYYY-MM-DD values and ensure the
validation occurs once before the definitions query or event generation.

---

Outside diff comments:
In `@drizzle/.sqlfluff`:
- Around line 1-4: Pin the sqlfluff dependency used by CI to a fixed release
that supports large_file_skip_byte_limit = 0 without the TypeError regression.
Keep the existing drizzle/.sqlfluff setting unchanged and ensure all linting
environments resolve the same compatible version.

In `@packages/server/src/repositories/settings-repository.test.ts`:
- Around line 232-250: Update the test around
SettingsRepository.deleteAllUserData to verify deletion ordering, not just the
five-call count: inspect transactionExecute.mock.calls, identify the
supplement_dose_event and supplement deletion statements, and assert the
dose-event statement occurs at a lower index than the supplement statement.
Preserve the existing call-count assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b8901579-4a26-4ba8-935d-d064740347b3

📥 Commits

Reviewing files that changed from the base of the PR and between 0a057e0 and 53597b3.

📒 Files selected for processing (63)
  • AGENTS.md
  • README.md
  • docs/production-incident-baseline.md
  • docs/roadmap.md
  • docs/schema.dbml
  • docs/schema.md
  • docs/schema.puml
  • docs/superpowers/plans/2026-04-25-review-app-seed-database.md
  • docs/superpowers/plans/2026-06-04-slow-query-optimization-sequence.md
  • docs/superpowers/plans/2026-07-27-supplement-dose-events.md
  • drizzle/.sqlfluff
  • drizzle/0061_supplement_dose_events.sql
  • drizzle/meta/_journal.json
  • package.json
  • packages/format/package.json
  • packages/format/src/supplement-dose-events.test.ts
  • packages/format/src/supplement-dose-events.ts
  • packages/mobile/app/supplements.test.tsx
  • packages/mobile/app/supplements.tsx
  • packages/mobile/components/SupplementDoseEventsPanel.stories.tsx
  • packages/mobile/components/SupplementDoseEventsPanel.test.tsx
  • packages/mobile/components/SupplementDoseEventsPanel.tsx
  • packages/server/src/lib/nutrition-cache.ts
  • packages/server/src/lib/typed-sql.test.ts
  • packages/server/src/lib/typed-sql.ts
  • packages/server/src/repositories/food-repository.ts
  • packages/server/src/repositories/provider-detail-repository.test.ts
  • packages/server/src/repositories/provider-detail-repository.ts
  • packages/server/src/repositories/settings-repository.test.ts
  • packages/server/src/repositories/settings-repository.ts
  • packages/server/src/repositories/supplement-dose-events.integration.test.ts
  • packages/server/src/repositories/supplements-repository.test.ts
  • packages/server/src/repositories/supplements-repository.ts
  • packages/server/src/routers/admin.ts
  • packages/server/src/routers/food.test.ts
  • packages/server/src/routers/food.ts
  • packages/server/src/routers/provider-detail.test.ts
  • packages/server/src/routers/recovery-settings-sleep-need-sport-settings.test.ts
  • packages/server/src/routers/router-data.integration.test.ts
  • packages/server/src/routers/settings.integration.test.ts
  • packages/server/src/routers/supplements-sync.test.ts
  • packages/server/src/routers/supplements.test.ts
  • packages/server/src/routers/supplements.ts
  • packages/web/src/components/SupplementDoseEventsPanel.stories.tsx
  • packages/web/src/components/SupplementDoseEventsPanel.test.tsx
  • packages/web/src/components/SupplementDoseEventsPanel.tsx
  • packages/web/src/components/SupplementStackPanel.tsx
  • packages/web/src/routes/nutrition/supplements.tsx
  • scripts/migration-policy.test.ts
  • scripts/migration-policy.ts
  • scripts/seed/core.ts
  • scripts/seed/nutrition.ts
  • src/db/README.md
  • src/db/execute-with-schema.test.ts
  • src/db/execute-with-schema.ts
  • src/db/schema/enums.ts
  • src/db/schema/nutrition.ts
  • src/db/supplement-dose-migration.integration.test.ts
  • src/processing/dataset-contracts.ts
  • src/providers/README.md
  • src/providers/auto-supplements.integration.test.ts
  • src/providers/auto-supplements.test.ts
  • src/providers/auto-supplements.ts

Comment thread docs/production-incident-baseline.md
Comment thread drizzle/0061_supplement_dose_events.sql
Comment thread packages/mobile/components/SupplementDoseEventsPanel.test.tsx
Comment thread packages/server/src/repositories/settings-repository.ts
Comment thread packages/server/src/repositories/supplements-repository.test.ts Outdated
Comment thread src/providers/auto-supplements.integration.test.ts Outdated
Comment thread src/providers/auto-supplements.integration.test.ts Outdated
Comment thread src/providers/auto-supplements.test.ts
Comment thread src/providers/auto-supplements.ts Outdated
Comment thread src/providers/auto-supplements.ts Outdated
Use canonical nutrient identities, assert wrapped PostgreSQL constraints, and reset cloned test data between retry attempts.
@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P0-06] Scheduled supplements look consumed in nutrition totals

3 participants