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 withpython3 ~/forge/scripts/forge_workspace_new_migration.py "intake dedicated tables"run from inside~/forge-suite; paste the parked schema fromscratchpad/0041_intake.sql.parked, adjusting nothing about the table defs, they already referencecore.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_regimenswith indexes, grants tolifeos_app, andcore.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; thenpsql "$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.tsbut 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, insertintake_entriesrow) wrapped inasUser;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 intakereturns 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(addensureIntakeApp+SYSTEM_KEYS.intakeApp+SystemAppKind'intake'),/home/justinwieb/forge-suite/lib/apps.ts(add'intake'toAPP_NODE_TYPES+ aSYSTEM_APPS[]card: id intake, name "Intake", route, icon 💊, tagline),/home/justinwieb/forge-suite/components/registry.tsx(mapintake: IntakeApp). - What: Make
provisioninstall an intake node (type='intake', title "Intake", icon 💊) and register the node-type soisAppNodeaccepts 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 themealblock ~:159-187): coerce numbers vianum(), resolve identity+workspace, callcreateIntakeEntry(me, wsId, {...}),logActivityinafter(). 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:
GETmirrorsapp/api/apps/meals/route.ts:currentUser(), read?workspaceId&date, callgetIntakeData, 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 clientIntakeClient.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.
/screenshotthe 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 theintakeverb POST, mirrorlog_food) andintake_day(brand, date)(mirrorfood_day, GET/api/apps/intake). Addintaketo 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) andintake_vs_wellness(metric, days)(joinintake_entries.logged_at::dateto a pivotedwellness.wellness_factsmetric 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 14prints 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.mdindex, cross-link inreference_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.shpasses. - Commit:
docs(intake): register intake tracking topic + index