Skip to content

Plan: Intake tracking for CreatorTrack (round one)

URL: https://mkdocs.justinsforge.com/memory/plans/intake-tracking-creatortrack-2026-07-03/

Date: 2026-07-03 Approved spec: A universal "what I put in my body" ledger built as a first-class CreatorTrack (forge-suite) app backed by DEDICATED typed tables in the shared core Postgres (NOT the generic collection engine, and NOT the frozen lifeos-spine surface at workspace.justinsforge.com). Star schema: app.intake_items (catalog: name, kind[] facet food|supplement|rx|otc, default dose+unit, route, is_rx, schedule scheduled|prn, RxNorm, optional macros) + app.intake_ingredients (multi-active products) + app.intake_entries (fact ledger, one row per dose: ts, dose, route, reason, source) + app.intake_regimens (scheduled adherence). Logged by texting any AI/Telegram bot ("creatine 5g", "took my finasteride") through a new intake capture verb that auto-seeds the Item on first mention. Dedicated columns give clean joins to wellness.wellness_facts for correlation and clean adherence/dose math, the reason we chose typed tables over collections. Round one = migration + capture verb + read API + a minimal Intake app surface (today's entries + item catalog) + Python helper + rollup. NOT the full adherence board / fitness panel / correlation charts.

Corrected home (2026-07-03): CreatorTrack = /home/justinwieb/forge-suite (Next.js, app.creatortrack.ai). The lifeos-spine surface (workspace.justinsforge.com, forge_workspace_app.py, forge/data/workspace/migrations) is a FROZEN ORPHAN per the 2026-07-01 canon in reference_forge_data_lifeos_spine; do not extend it. Migration 0041 was parked out of that dir.

Naming: feature = Intake; catalog row = Item; a logged dose = Intake entry.

Mental model (unchanged): macros on the Item -> rolls into daily nutrition (converge with the meals Food Log later). Fitness-tagged -> correlates vs HRV/training. Scheduled -> adherence for free (no separate habit). PRN -> logged only when taken, with a reason. Wellness metrics are measurements, not intake; Intake correlates against them by timestamp.

Storage pattern note: meals/habits in forge-suite use the generic collection engine (database_row nodes + collection_values). Intake deliberately does NOT; it uses dedicated tables queried directly by lib/intake.ts via sql wrapped in asUser RLS (lib/rls.ts). This is a new pattern for forge-suite, justified by the analytics goal.

Out of scope (round two+): - Full Intake surface: adherence board, fitness-supplements panel, correlation-vs-HRV charts, edit/delete UI polish. - Converging the meals Food Log into Intake (macros already live on the Item; no double-entry). - RxNorm auto-lookup (column exists; manual for now). - Retiring the current _MEALS snack fallback in forge_workspace_capture.py (leave until proven). - Backfill of historical logs.

Task list

Task 1: Dedicated-table migration in forge-suite

  • Files: /home/justinwieb/forge-suite/db/migrations/<timestamp>_intake.sql (generate the name with python3 ~/forge/scripts/forge_workspace_new_migration.py "intake dedicated tables" run from inside ~/forge-suite; paste the parked schema from scratchpad/0041_intake.sql.parked, adjusting nothing about the table defs, they already reference core.workspaces, app.nodes, core.apply_workspace_rls, lifeos_app, all present in forge-suite's core DB).
  • What: Create app.intake_items, app.intake_ingredients, app.intake_entries, app.intake_regimens with indexes, grants to lifeos_app, and core.apply_workspace_rls() on all four.
  • Verification: apply with cd ~/forge-suite && PGDATABASE=lifeos WORKSPACE_MIGRATIONS_DIR=db/migrations python3 ~/forge/scripts/forge_workspace_migrate.py; then psql "$LIFEOS_DSN" -c "SELECT relname,relrowsecurity FROM pg_class WHERE relname LIKE 'intake_%';" shows rowsecurity=t on all four.
  • Commit: feat(intake): dedicated app.intake_* tables + RLS

Task 2: Server mutation + read lib (lib/intake.ts)

  • Files: /home/justinwieb/forge-suite/lib/intake.ts
  • What: Clone the shape of lib/meals.ts but query the dedicated tables. Exports: ensureIntakeApp(id, workspaceId) (self-provision the intake node, see Task 3); createIntakeEntry(id, workspaceId, {name, dose, unit, kind, route, reason, isRx, schedule, source}) (upsert Item by (workspace_id, lower(name)) auto-seeding defaults, insert intake_entries row) wrapped in asUser; getIntakeData(id, workspaceId, date?) returning {ids, items, entries} for the day. Fail loud on bad workspace.
  • Verification: cd ~/forge-suite && npx tsc --noEmit -p tsconfig.json 2>&1 | grep -i intake returns nothing (compiles).
  • Commit: feat(intake): lib/intake.ts mutation + read over dedicated tables

Task 3: Register the intake system app

  • Files: /home/justinwieb/forge-suite/lib/system-apps.ts (add ensureIntakeApp + SYSTEM_KEYS.intakeApp + SystemAppKind 'intake'), /home/justinwieb/forge-suite/lib/apps.ts (add 'intake' to APP_NODE_TYPES + a SYSTEM_APPS[] card: id intake, name "Intake", route, icon 💊, tagline), /home/justinwieb/forge-suite/components/registry.tsx (map intake: IntakeApp).
  • What: Make provision install an intake node (type='intake', title "Intake", icon 💊) and register the node-type so isAppNode accepts it and the UI can render it.
  • Verification: npx tsc --noEmit 2>&1 | grep -iE "intake|apps.ts|registry" clean; after Task 6 provision, psql -c "SELECT id,type,title FROM app.nodes WHERE type='intake';" shows the node.
  • Commit: feat(intake): register intake system app + node type

Task 4: Capture verb dispatch

  • Files: /home/justinwieb/forge-suite/app/api/capture/route.ts
  • What: Add an if (verb === "intake") block (mirror the meal block ~:159-187): coerce numbers via num(), resolve identity+workspace, call createIntakeEntry(me, wsId, {...}), logActivity in after(). Return {ok, id}.
  • Verification: curl -fsS -X POST localhost:3070/api/capture -H "authorization: Bearer $CAPTURE_TOKEN" -H 'content-type: application/json' -d '{"verb":"intake","workspace":"justin","name":"Creatine","dose":5,"unit":"g","kind":"supplement"}' | jq .ok -> true; psql -c "SELECT i.name,e.dose,e.dose_unit FROM app.intake_entries e JOIN app.intake_items i ON i.id=e.item_id ORDER BY e.id DESC LIMIT 1;" shows Creatine/5/g.
  • Commit: feat(intake): intake capture verb

Task 5: Read API route

  • Files: /home/justinwieb/forge-suite/app/api/apps/intake/route.ts
  • What: GET mirrors app/api/apps/meals/route.ts: currentUser(), read ?workspaceId&date, call getIntakeData, return {ok, ids, items, entries}. (POST op-dispatch for manual create/delete can wait for round two.)
  • Verification: curl -fsS "localhost:3070/api/apps/intake?workspaceId=67&date=$(date +%F)" -H "authorization: Bearer $CREATORTRACK_API_TOKEN" | jq '.entries' returns the Creatine entry.
  • Commit: feat(intake): GET /api/apps/intake

Task 6: Minimal Intake app UI

  • Files: /home/justinwieb/forge-suite/components/apps/intake/IntakeApp.tsx (+ a small client IntakeClient.tsx)
  • What: Server component fetches getIntakeData, renders a minimal surface: a "Taken today" list (name, dose, reason, rx/scheduled badge) and the Item catalog below. No edit UI this round. Register was done in Task 3.
  • Verification: provision + open the intake app for workspace 67 at the dev origin; the Creatine/Acetaminophen entries render. /screenshot the dev URL.
  • Commit: feat(intake): minimal Intake app surface

Task 7: Python capture helper

  • Files: /home/justinwieb/forge/scripts/forge_workspace_capture.py
  • What: Add log_intake(name, dose=None, unit=None, kind=None, route=None, reason=None, is_rx=None, schedule=None, brand="life", date=None, notes=None) (shape the intake verb POST, mirror log_food) and intake_day(brand, date) (mirror food_day, GET /api/apps/intake). Add intake to the module docstring verb list.
  • Verification: python -c "import sys;sys.path.insert(0,'/home/justinwieb/forge/scripts');import forge_workspace_capture as c;print(c.log_intake('Acetaminophen',dose=500,unit='mg',kind='otc',reason='headache'));print(c.intake_day())" prints ok:true and the entry appears.
  • Commit: feat(intake): log_intake + intake_day python helpers

Task 8: Daily rollup + wellness correlation

  • Files: /home/justinwieb/forge/scripts/forge_intake_rollup.py (new)
  • What: Read-only module: intake_day_rollup(date) (day's entries + macro subtotal, macro-bearing intake summed alongside the meals Food Log, keyed by source so protein shakes aren't double counted) and intake_vs_wellness(metric, days) (join intake_entries.logged_at::date to a pivoted wellness.wellness_facts metric like hrv/sleep). Parameterized SQL, fail loud.
  • Verification: python /home/justinwieb/forge/scripts/forge_intake_rollup.py --date $(date +%F) prints entries + subtotal; ... --vs hrv --days 14 prints paired rows.
  • Commit: feat(intake): daily rollup + wellness correlation query

Task 9: Register and document

  • Files: /home/justinwieb/forge/memory/general/reference_intake_tracking.md, MEMORY.md index, cross-link in reference_food_log.md.
  • What: Topic file (schema, capture verb, helpers, rollup, naming, storage-pattern rationale, round-two roadmap, meals-convergence note); MEMORY.md index line; run eval harness.
  • Verification: bash /home/justinwieb/forge/scripts/forge_eval_run.sh passes.
  • Commit: docs(intake): register intake tracking topic + index