Skip to content

feat(subjective): add body-state inputs and session RPE - #2414

Merged
Asherlc merged 23 commits into
mainfrom
Asherlc/issue-2247-subjective-inputs
Aug 3, 2026
Merged

Asherlc merged 23 commits into
mainfrom
Asherlc/issue-2247-subjective-inputs

Conversation

@Asherlc

@Asherlc Asherlc commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • add canonical session RPE, daily subjective check-ins, sparse body-region symptoms, injury/niggle events, and bilateral finger/pulley references
  • expose user-scoped tRPC contracts and the get_subjective_timeline MCP read tool
  • add equivalent web and iOS Recovery/activity-detail workflows with Sentry-reported mutation errors
  • preserve raw-input-only storage and explicit not-logged versus all-clear state

Validation

  • pnpm exec vitest run --project unit packages/server/src/repositories/subjective-repository.test.ts packages/server/src/routers/subjective.test.ts --retry=0
  • pnpm exec vitest run --project unit packages/server/src/repositories/activity-repository.test.ts packages/server/src/routers/activity.test.ts --retry=0
  • pnpm exec vitest run packages/web/src/components/ActivityPerceivedExertion.test.tsx packages/web/src/components/SubjectiveTrackingPanel.test.tsx --retry=0
  • mobile activity/recovery suites: 47 tests passed
  • server, web, and mobile typechecks passed
  • Biome, migration policy, mobile telemetry policy, and web Storybook coverage passed

The Docker-backed integration test is present but could not start because the shared Docker host returned all predefined address pools have been fully subnetted; this is documented in docs/production-incident-baseline.md.

Fixes #2247

Summary by Sourcery

Introduce user-scoped subjective body-state tracking (check-ins, symptoms, injuries) and session RPE updates, with shared server contracts and tooling consumed by web and mobile clients.

New Features:

  • Add canonical subjective body-state tables for regions, daily check-ins, symptoms, and injury/niggle events with seeded bilateral hand and pulley references.
  • Expose a new subjective repository and tRPC router providing regions, check-in read/write, injury CRUD, and date-window timelines, plus an MCP tool to read raw subjective timelines.
  • Add session perceived exertion support to activities, including API mutations and web/mobile UI components on activity detail pages.
  • Add subjective tracking panels to web Tracking and mobile Recovery screens for logging body symptoms and injury events.

Enhancements:

  • Enforce database-level constraints for perceived exertion, symptom scores, and injury validity, with integration tests documenting expected failures.
  • Wire cache-domain invalidation for activity and subjective data so mutations refresh related views across clients.

Documentation:

  • Document the new subjective input schema and design, including storage semantics, server contracts, and client workflows.
  • Record the Docker network exhaustion incident that blocked the new subjective integration test and its implications for local validation.

Tests:

  • Add unit, repository, router, MCP route, component, Storybook, and Postgres-backed integration tests for subjective inputs and session RPE behavior.
  • Extend existing activity and recovery tests on web and mobile to cover perceived exertion and subjective tracking flows.

Chores:

  • Add a forward-only database migration seeding body-region references and creating subjective input tables.
  • Add internal implementation plan and design docs to guide agentic workers for subjective input features.

Summary by cubic

Adds first‑party subjective inputs (daily check‑ins, body‑region symptoms, injury/niggle events) and session RPE with shared server APIs and matching web/iOS UIs. Aligns with issue #2247; run migration 0069_subjective_inputs to validate the perceived_exertion range, create new tables/constraints, and seed fitness.body_region.

  • New Features

    • Database: range check on fitness.activity.perceived_exertion (0–10); new fitness.body_region (seeded), fitness.subjective_check_in, fitness.subjective_symptom, fitness.injury_event.
    • Server APIs: subjective router (regions, checkIn/saveCheckIn, injuries CRUD, timeline) and activity.setPerceivedExertion; all user‑scoped with cache invalidation for subjective and activity.
    • MCP: get_subjective_timeline under health:read.
    • Web + iOS: SubjectiveTrackingPanel on Tracking/Recovery and ActivityPerceivedExertion on Activity Detail; mutation errors reported to Sentry; raw‑input‑only storage with explicit “not logged” vs “all clear.”
  • Bug Fixes

    • Migration: avoid duplicate perceived_exertion column; validate range; use bigint where needed; enforce non‑empty body_region IDs/labels.
    • Activity: project perceived_exertion through v_activity and expose perceivedExertion on Activity Detail.
    • Injury flows: strict validation (ISO dates, kind, severity 0–10, onset/resolution order), delete route, and cache‑backed list/timeline refresh.
    • DB/infra/tests: add DB transaction to MCP router/tests; expand coverage for subjective mutations, timeline, activity RPE (including set/clear), and deduped activity details; switch to canonical activity fixtures; add a typed shared router test caller helper; document the Docker networking blocker for the new integration test.
    • Account erasure: classify fitness.body_region as a shared system table so user erasure preserves region references.

Written for commit 5689b46. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added body-state tracking for soreness, stiffness, tenderness, niggles, symptoms, and injuries on web and mobile.
    • Added daily check-ins with body-region selection, symptom scores, injury details, and all-clear logging.
    • Added perceived exertion tracking on activity details using a 0–10 scale, including save and clear actions.
    • Added read-only timeline access for subjective health data.
  • Documentation
    • Documented the subjective-input data model and implementation plan.
  • Bug Fixes
    • Improved validation for scores, dates, injury records, and activity exertion values.

Copilot AI review requested due to automatic review settings August 2, 2026 19:16
@cursor

cursor Bot commented Aug 2, 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, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

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 Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds session perceived exertion, body-region check-ins, symptom records, injury events, server APIs, MCP timeline access, and equivalent web and mobile interfaces. The change includes database migrations, validation, cache invalidation, integration tests, unit tests, and Storybook scenarios.

Changes

Subjective inputs and session RPE

Layer / File(s) Summary
Schema and persistence
src/db/schema/*, drizzle/*, docs/schema*
Adds subjective-input tables, seeded body regions, constraints, indexes, and activity perceived-exertion persistence.
Server repositories and APIs
packages/server/src/repositories/*, packages/server/src/routers/*, packages/server/src/router.ts, src/lib/cache.ts
Adds user-scoped check-in, symptom, injury, timeline, and session-RPE operations with validation and cache invalidation.
MCP timeline access
packages/server/src/mcp/*, packages/server/src/lib/date-schema.ts
Adds the protected get_subjective_timeline tool and shared ISO-date validation.
Mobile workflows
packages/mobile/app/*, packages/mobile/components/*
Adds subjective tracking on Recovery and perceived exertion on activity details, with tests and Storybook scenarios.
Web workflows
packages/web/src/components/*, packages/web/src/pages/*
Adds Body State tracking and activity perceived exertion, with tests and Storybook scenarios.

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

Assessment against linked issues

Objective Addressed Explanation
Session RPE, body-region symptoms, and injury events [#2247]
User-scoped validated APIs with explicit logged/not-logged state [#2247]
Mobile and web workflows with Storybook coverage [#2247]
MCP read surface and database integration tests [#2247]

Possibly related PRs

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

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the subjective inputs and session RPE changes, uses imperative wording, is 55 characters, and has no trailing punctuation.

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 Aug 2, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Adds canonical subjective body-state storage and APIs (check-ins, symptoms, injuries), exposes them via tRPC and an MCP read tool, wires equivalent web and mobile UIs, and introduces session RPE editing for activities with proper validation and cache invalidation.

Sequence diagram for subjective saveCheckIn flow

sequenceDiagram
  actor User
  participant WebClient as SubjectiveTrackingPanel
  participant TrpcServer as subjectiveRouter
  participant Repo as SubjectiveRepository
  participant DB as Database
  participant Cache as invalidateUserQueryDomains

  User->>WebClient: submit symptoms
  WebClient->>TrpcServer: saveCheckIn(input)
  TrpcServer->>TrpcServer: validate input (dateSchema, symptomInputSchema)
  TrpcServer->>Repo: saveCheckIn(date, symptoms)
  Repo->>DB: transaction(callback)
  Repo->>DB: INSERT subjective_check_in (ON CONFLICT ...)
  Repo->>DB: DELETE subjective_symptom by check_in_id
  Repo->>DB: INSERT subjective_symptom rows
  DB-->>Repo: commit
  Repo->>DB: SELECT subjective_check_in + subjective_symptom
  DB-->>Repo: checkIn result
  Repo-->>TrpcServer: SubjectiveCheckIn
  TrpcServer->>Cache: invalidateUserQueryDomains(userId, [subjective])
  Cache-->>TrpcServer: ok
  TrpcServer-->>WebClient: SubjectiveCheckIn
  WebClient-->>User: updated body check-in state
Loading

Entity relationship diagram for subjective body-state schema

erDiagram
  user_profile ||--o{ subjective_check_in : owns
  user_profile ||--o{ injury_event : owns
  body_region ||--o{ body_region : parent
  subjective_check_in ||--o{ subjective_symptom : has
  body_region ||--o{ subjective_symptom : locates
  body_region ||--o{ injury_event : locates

  user_profile {
    uuid id
  }

  body_region {
    text id
    text parent_id
    text label
    text kind
    int sort_order
  }

  subjective_check_in {
    uuid id
    uuid user_id
    date date
  }

  subjective_symptom {
    uuid id
    uuid check_in_id
    text body_region_id
    text kind
    int score
  }

  injury_event {
    uuid id
    uuid user_id
    text body_region_id
    text kind
    date onset_date
    date resolved_date
    int severity
    text description
  }
Loading

File-Level Changes

Change Details Files
Introduce subjective body-state schema and repository layer for check-ins, symptoms, and injuries with strict constraints and tests.
  • Add fitness.body_region, fitness.subjective_check_in, fitness.subjective_symptom, and fitness.injury_event tables with FK and CHECK constraints plus seeded bilateral finger/pulley regions.
  • Implement SubjectiveRepository with Zod-validated SQL for regions, daily check-in read/save (transactional upsert + symptom replace), injury CRUD, and date-range timeline assembly.
  • Expose a subjective tRPC router with routes for regions, check-in, saveCheckIn, injuries CRUD, and timeline, including Zod validation, NOT_FOUND/BAD_REQUEST errors, and user-scoped cache invalidation.
  • Document the new subjective schema, design, implementation plan, and capture the blocked Docker-backed integration test in the incident baseline.
  • Add Postgres integration tests verifying region seeding, all-clear check-ins, RPE/symptom/injury constraints, and transactional behavior.
src/db/schema/events.ts
drizzle/0068_subjective_inputs.sql
src/db/subjective-inputs.integration.test.ts
docs/schema.md
docs/schema.dbml
docs/schema.puml
packages/server/src/repositories/subjective-repository.ts
packages/server/src/repositories/subjective-repository.test.ts
packages/server/src/routers/subjective.ts
packages/server/src/routers/subjective.test.ts
docs/superpowers/specs/2026-08-02-subjective-inputs-design.md
docs/superpowers/plans/2026-08-02-subjective-inputs.md
docs/production-incident-baseline.md
Add session RPE support to activities across DB, server model/repository/router, cache, and web/mobile clients.
  • Add perceived_exertion column constraint (0–10 or NULL) and plumb it through ActivityRow, Activity model, repository queries, and ActivityDetail DTOs.
  • Implement ActivityRepository.setPerceivedExertion to update all raw member rows for a visible canonical activity for the user, returning found flag and stored value.
  • Add activityRouter.setPerceivedExertion tRPC mutation with 0–10 validation, NOT_FOUND handling, and invalidateUserQueryDomains for the activity cache domain (new activity/calendar prefixes).
  • Create web and mobile ActivityPerceivedExertion components that edit RPE (slider/buttons), invoke the mutation, invalidate activity.byId, and report mutation errors to Sentry.
  • Extend server, web, and mobile tests/stories to cover perceivedExertion plumbing and mutation behavior.
src/db/schema/activity.ts
packages/server/src/repositories/activity-repository.ts
packages/server/src/repositories/activity-repository.test.ts
packages/server/src/models/activity.ts
packages/server/src/routers/activity.ts
packages/server/src/routers/activity.test.ts
src/lib/cache.ts
packages/web/src/pages/ActivityDetailPage.tsx
packages/web/src/pages/ActivityDetailPage.test.tsx
packages/web/src/pages/ActivityDetailPage.stories.tsx
packages/web/src/components/ActivityPerceivedExertion.tsx
packages/web/src/components/ActivityPerceivedExertion.test.tsx
packages/web/src/components/ActivityPerceivedExertion.stories.tsx
packages/mobile/app/activity/[id].tsx
packages/mobile/app/activity/[id].test.tsx
packages/mobile/components/ActivityPerceivedExertion.tsx
packages/mobile/components/ActivityPerceivedExertion.test.tsx
packages/mobile/components/ActivityPerceivedExertion.stories.tsx
Expose subjective timeline via MCP and wire user-scoped subjective and recovery UIs on web and mobile.
  • Extend MCP context DB type to include transaction and register get_subjective_timeline tool that enforces health:read scope, validates date windows, and returns SubjectiveRepository.timeline output; update MCP tests to assert listing and schema.
  • Register the subjectiveRouter on the main app router so subjective.* is available to clients.
  • Add SubjectiveTrackingPanel components for web and mobile that consume subjective.checkIn/regions/injuries/saveCheckIn/createInjury, maintain local symptom draft state, and invalidate subjective.checkIn/timeline/injuries on writes while capturing unexpected errors with telemetry.
  • Mount the subjective panels on the web Tracking page (Body State section) and mobile Recovery tab, ensuring they distinguish not-logged vs all-clear and display injury timelines.
  • Adjust recovery tests/mocks to cover new subjective queries and cache invalidation paths.
packages/server/src/mcp/tools.ts
packages/server/src/mcp/route.ts
packages/server/src/mcp/route.test.ts
packages/server/src/mcp/route-lifecycle.test.ts
packages/server/src/router.ts
packages/web/src/pages/TrackingPage.tsx
packages/web/src/components/SubjectiveTrackingPanel.tsx
packages/web/src/components/SubjectiveTrackingPanel.test.tsx
packages/web/src/components/SubjectiveTrackingPanel.stories.tsx
packages/mobile/app/(tabs)/recovery.tsx
packages/mobile/app/(tabs)/recovery.test.tsx
packages/mobile/components/SubjectiveTrackingPanel.tsx
packages/mobile/components/SubjectiveTrackingPanel.test.tsx
packages/mobile/components/SubjectiveTrackingPanel.stories.tsx
packages/mobile/lib/useTodayQueryDate.ts

Assessment against linked issues

Issue Objective Addressed Explanation
#2247 Extend the schema to support session-level RPE, body-map soreness (with appropriate granularity and types), injury/niggle events, and shared body-region reference data, while preserving raw-data-only storage and representing missing entries distinctly from zeros.
#2247 Implement server-side contracts (tRPC + Zod) and MCP tools for subjective data: mutations/queries that return explicit logged/not-logged state for daily check-ins, injury timeline access, and a read-only MCP timeline tool, with appropriate tests.
#2247 Add dual-platform UI (web + mobile) for fast daily subjective entry and session RPE editing, including body-state panels and injury timeline views, with Storybook coverage and tests.

Possibly linked issues


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

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Mobile Preview

Scan to open on device:

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

Channel pr-2414
Deep Link dofek://preview/pr-2414
Commit f4054eb

To test on device:

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

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

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Storybook previews for f4054ebc are ready:

This comment updates automatically on each PR push.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add subjective body check-ins, injury events, and session RPE across server/web/mobile

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

Grey Divider

AI Description

• Add Postgres schema + migration for subjective check-ins, sparse symptoms, and injury/niggle
 events.
• Expose user-scoped tRPC + MCP read tool for subjective timelines and activity session RPE.
• Add equivalent web + iOS UI flows for body-state logging and session-effort editing with error
 telemetry.
Diagram

graph TD
  W["Web UI"] --> API["tRPC API"] --> SR["Subjective repo"] --> DB[("Postgres")]
  M["Mobile UI"] --> API
  API --> AR["Activity repo"] --> DB
  MCP["MCP tool"] --> SR
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Store symptoms as JSON on check-in row
  • ➕ Fewer tables/joins; simpler CRUD for check-in writes
  • ➕ Potentially simpler timeline reads (single table scan)
  • ➖ Weaker relational integrity (region FK, uniqueness per kind/region) unless enforced in app code
  • ➖ Harder to query/filter by symptom kind/region over time
  • ➖ More migration risk if JSON shape evolves
2. Model injuries as Life Events
  • ➕ Reuses existing event/timeline UX and storage patterns
  • ➕ Avoids introducing a new injury_event table
  • ➖ Life events semantics differ (injury resolution/onset constraints, severity range)
  • ➖ Risk of conflating subjective medical-ish events with generic markers
  • ➖ Harder to maintain a dedicated injury CRUD API without leaking unrelated life-event behaviors

Recommendation: The PR’s normalized schema (reference regions + check-in header + sparse symptom rows + separate injury events) is the stronger long-term choice because it enforces ownership/constraints at the DB layer and keeps future querying (timelines, filtering by region/kind) straightforward. The JSON and Life Events alternatives reduce tables upfront but shift integrity and evolution risk into application code and blur semantics.

Files changed (42) +2326 / -86

Enhancement (15) +1010 / -2
recovery.tsxMount SubjectiveTrackingPanel on Recovery tab +3/-0

Mount SubjectiveTrackingPanel on Recovery tab

• Imports and renders the new SubjectiveTrackingPanel within the Recovery screen flow so users can log daily body check-ins and view injuries.

packages/mobile/app/(tabs)/recovery.tsx

[id].tsxRender session perceived exertion control on Activity Detail screen +2/-0

Render session perceived exertion control on Activity Detail screen

• Adds the ActivityPerceivedExertion component to the activity detail screen and passes the current perceivedExertion value from the loaded activity.

packages/mobile/app/activity/[id].tsx

ActivityPerceivedExertion.tsxAdd mobile Session effort editor with mutation + telemetry +97/-0

Add mobile Session effort editor with mutation + telemetry

• Introduces a native control to increment/decrement, save, or clear session RPE (0–10). Uses activity.setPerceivedExertion mutation, invalidates activity.byId on success, and reports unexpected errors via captureException.

packages/mobile/components/ActivityPerceivedExertion.tsx

SubjectiveTrackingPanel.tsxAdd mobile body check-in + injury timeline panel +164/-0

Add mobile body check-in + injury timeline panel

• Implements a Recovery-tab panel to log an explicit daily check-in (including all-clear) and add sparse symptom entries by region/kind/score. Reads regions/check-in/injuries via tRPC, persists via saveCheckIn, invalidates checkIn + timeline, and reports mutation errors to telemetry.

packages/mobile/components/SubjectiveTrackingPanel.tsx

tools.tsRegister get_subjective_timeline MCP tool (health:read) +21/-1

Register get_subjective_timeline MCP tool (health:read)

• Adds an MCP tool that validates a date window, requires the health:read scope, and returns the raw subjective timeline (check-ins, symptoms, injuries) via SubjectiveRepository.

packages/server/src/mcp/tools.ts

activity.tsExpose perceivedExertion on activity detail model +7/-0

Expose perceivedExertion on activity detail model

• Adds perceived_exertion to ActivityRow and perceivedExertion to ActivityDetail, including a getter and mapping through toDetail() output.

packages/server/src/models/activity.ts

activity-repository.tsAdd setPerceivedExertion and select perceived_exertion in detail queries +32/-0

Add setPerceivedExertion and select perceived_exertion in detail queries

• Extends the activity detail row schema and SELECTs to include perceived_exertion. Adds setPerceivedExertion() to write to all member activity rows of a visible canonical activity group scoped to the authenticated user.

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

subjective-repository.tsAdd repository for regions, check-ins, symptoms, injuries, and timelines +269/-0

Add repository for regions, check-ins, symptoms, injuries, and timelines

• Implements SQL-backed accessors for body regions, per-date check-in reads, transactional upsert-and-replace symptom writes, injury CRUD scoped by user_id, and date-window timeline assembly (including overlapping injury events). Uses Zod schemas at DB boundaries and transactions for check-in saves.

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

router.tsRegister subjectiveRouter on the app router +2/-0

Register subjectiveRouter on the app router

• Adds subjectiveRouter to the server's top-level tRPC router so clients can call subjective.* procedures.

packages/server/src/router.ts

activity.tsAdd activity.setPerceivedExertion mutation with domain invalidation +13/-1

Add activity.setPerceivedExertion mutation with domain invalidation

• Introduces a protected mutation that validates nullable 0–10 input, writes via ActivityRepository.setPerceivedExertion, returns NOT_FOUND when no visible canonical activity is updated, and invalidates the user's activity query domain.

packages/server/src/routers/activity.ts

subjective.tsAdd subjective tRPC router for regions, check-ins, injuries, and timeline +111/-0

Add subjective tRPC router for regions, check-ins, injuries, and timeline

• Implements user-scoped subjective procedures with Zod input validation, cache TTLs for reads, and invalidateUserQueryDomains("subjective") on writes. Enforces date-window ordering errors and returns structured NOT_FOUND errors for missing injury events.

packages/server/src/routers/subjective.ts

ActivityPerceivedExertion.tsxAdd web Session effort editor with mutation + telemetry +64/-0

Add web Session effort editor with mutation + telemetry

• Implements a range slider to set/clear perceived exertion (0–10) on activity detail. Invalidates activity.byId on success and reports unexpected mutation errors to captureException.

packages/web/src/components/ActivityPerceivedExertion.tsx

SubjectiveTrackingPanel.tsxAdd web body check-in + injury timeline panel +219/-0

Add web body check-in + injury timeline panel

• Implements a Tracking-page panel for daily check-ins with sparse symptom drafts and explicit all-clear logging, plus a simple injury/niggle creation flow and timeline list. Uses targeted cache invalidation and Sentry-style telemetry for mutation errors.

packages/web/src/components/SubjectiveTrackingPanel.tsx

ActivityDetailPage.tsxRender ActivityPerceivedExertion on web Activity Detail page +2/-0

Render ActivityPerceivedExertion on web Activity Detail page

• Mounts the ActivityPerceivedExertion component on ActivityDetailPage and passes through activityId and current perceivedExertion value.

packages/web/src/pages/ActivityDetailPage.tsx

TrackingPage.tsxAdd Body State section to Tracking page +4/-0

Add Body State section to Tracking page

• Adds a new page section and mounts SubjectiveTrackingPanel to expose subjective logging on web.

packages/web/src/pages/TrackingPage.tsx

Tests (13) +601 / -2
recovery.test.tsxMock subjective tRPC hooks + invalidation for Recovery screen tests +25/-0

Mock subjective tRPC hooks + invalidation for Recovery screen tests

• Extends Recovery screen tests to include subjective.checkIn/regions/injuries queries and saveCheckIn mutation mocks. Adds utils invalidation expectations for subjective cache keys.

packages/mobile/app/(tabs)/recovery.test.tsx

[id].test.tsxAdd perceived exertion mutation mock for Activity Detail tests +6/-0

Add perceived exertion mutation mock for Activity Detail tests

• Extends Activity detail tests with a mocked activity.setPerceivedExertion mutation and adds perceivedExertion to the activity fixture shape.

packages/mobile/app/activity/[id].test.tsx

route-lifecycle.test.tsUpdate MCP router test DB stub to include transactions +4/-1

Update MCP router test DB stub to include transactions

• Adjusts MCP route lifecycle tests to pass a db stub that includes transaction(), matching new MCP tool requirements.

packages/server/src/mcp/route-lifecycle.test.ts

route.test.tsAdd MCP tool listing coverage for get_subjective_timeline +17/-1

Add MCP tool listing coverage for get_subjective_timeline

• Updates test app setup to provide transaction() and asserts the tool list includes get_subjective_timeline with the expected input schema.

packages/server/src/mcp/route.test.ts

activity-repository.test.tsAdd unit coverage for setting session perceived exertion +28/-0

Add unit coverage for setting session perceived exertion

• Updates fixtures to include perceived_exertion and adds tests verifying setPerceivedExertion updates raw member rows for visible canonical activities and reports not-found when no rows update.

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

subjective-repository.test.tsAdd SubjectiveRepository unit tests for semantics + scoping +116/-0

Add SubjectiveRepository unit tests for semantics + scoping

• Introduces tests covering not-logged vs all-clear distinction, transactional replace semantics for symptoms, timeline assembly including overlapping injuries, and user scoping for injury writes.

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

activity.test.tsAdd router coverage for session RPE contract + cache invalidation +36/-0

Add router coverage for session RPE contract + cache invalidation

• Mocks invalidateUserQueryDomains and adds tests verifying activity.setPerceivedExertion writes via repository, invalidates the activity cache domain, and rejects out-of-range values at input validation.

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

subjective.test.tsAdd subjectiveRouter tests for validation, semantics, and NOT_FOUND +121/-0

Add subjectiveRouter tests for validation, semantics, and NOT_FOUND

• Adds router tests verifying explicit not-logged state, all-clear persistence with subjective-only invalidation, pre-DB rejection of invalid scores and reversed date windows, and NOT_FOUND behavior when updating another user's injury.

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

ActivityPerceivedExertion.test.tsxAdd component test for saving and clearing session effort +45/-0

Add component test for saving and clearing session effort

• Introduces a jsdom test verifying slider changes trigger activity.setPerceivedExertion with the expected values for Save and Clear actions.

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

SubjectiveTrackingPanel.test.tsxAdd component tests for all-clear, sparse symptoms, and injury creation +94/-0

Add component tests for all-clear, sparse symptoms, and injury creation

• Adds jsdom tests ensuring the panel can log an explicit all-clear check-in, save sparse symptoms without implied zeros, and create a niggle injury with the selected region and description.

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

ActivityDetailPage.stories.tsxUpdate ActivityDetailPage story fixture for perceivedExertion +1/-0

Update ActivityDetailPage story fixture for perceivedExertion

• Adds perceivedExertion to the base activity fixture to match the expanded ActivityDetail contract.

packages/web/src/pages/ActivityDetailPage.stories.tsx

ActivityDetailPage.test.tsxUpdate ActivityDetailPage tests for perceivedExertion and mutation wiring +8/-0

Update ActivityDetailPage tests for perceivedExertion and mutation wiring

• Extends the ActivityDetailPage test fixture to include perceivedExertion and stubs the activity.setPerceivedExertion mutation hook.

packages/web/src/pages/ActivityDetailPage.test.tsx

subjective-inputs.integration.test.tsAdd Postgres integration test for subjective constraints and seeds +100/-0

Add Postgres integration test for subjective constraints and seeds

• Introduces an integration test that validates seeded region IDs (bilateral hands and pulley nodes), allows all-clear check-ins with no symptom rows, and asserts DB-level rejections for out-of-range RPE/symptom scores and reversed injury resolution dates.

src/db/subjective-inputs.integration.test.ts

Documentation (8) +484 / -81
production-incident-baseline.mdDocument Docker network exhaustion blocking subjective integration test +9/-0

Document Docker network exhaustion blocking subjective integration test

• Adds an incident entry describing why the new Docker-backed integration test could not start locally due to exhausted Docker address pools. Captures symptoms, root cause, and follow-up guidance to re-run the suite once capacity is restored.

docs/production-incident-baseline.md

schema.dbmlUpdate DBML schema for subjective inputs and offset types +151/-76

Update DBML schema for subjective inputs and offset types

• Extends the DBML schema with body_region, subjective_check_in, subjective_symptom, and injury_event tables plus foreign keys. Also updates activity/sleep_session UTC offset columns to bigint and applies minor formatting cleanups.

docs/schema.dbml

schema.mdAdd Subjective Inputs section to schema documentation +15/-0

Add Subjective Inputs section to schema documentation

• Documents the purpose and storage semantics of the new subjective input tables. Emphasizes raw-input-only storage and DB constraints for ownership and score/date boundaries.

docs/schema.md

schema.pumlAdd subjective entities to PUML ER diagram +51/-5

Add subjective entities to PUML ER diagram

• Adds body_region, subjective_check_in, subjective_symptom, and injury_event entities and their relationships. Updates UTC offset types to bigint for activity and sleep_session in the diagram.

docs/schema.puml

2026-08-02-subjective-inputs.mdAdd implementation plan for subjective inputs + session RPE +105/-0

Add implementation plan for subjective inputs + session RPE

• Introduces a task-by-task implementation plan spanning schema/migration, repository/router contracts, MCP tool, and web/mobile UI work. Records validation steps and notes the integration-test blocker due to Docker networking.

docs/superpowers/plans/2026-08-02-subjective-inputs.md

2026-08-02-subjective-inputs-design.mdAdd design spec for subjective inputs storage and contracts +29/-0

Add design spec for subjective inputs storage and contracts

• Defines scope, storage semantics (including explicit not-logged vs all-clear), and server/client contract expectations. Clarifies that the system stores raw observations only and exposes a read-only MCP timeline tool guarded by health:read.

docs/superpowers/specs/2026-08-02-subjective-inputs-design.md

ActivityPerceivedExertion.stories.tsxAdd Storybook stories for ActivityPerceivedExertion +60/-0

Add Storybook stories for ActivityPerceivedExertion

• Adds Storybook coverage using a mock tRPC link to demonstrate unset vs logged session effort states.

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

SubjectiveTrackingPanel.stories.tsxAdd Storybook story for SubjectiveTrackingPanel +64/-0

Add Storybook story for SubjectiveTrackingPanel

• Adds a Storybook story with mocked tRPC responses for regions, check-in, and injuries to showcase the panel UI.

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

Other (6) +231 / -1
0068_subjective_inputs.sqlAdd migration for subjective inputs tables and activity RPE constraint +122/-0

Add migration for subjective inputs tables and activity RPE constraint

• Adds a check constraint enforcing activity.perceived_exertion in [0,10] when present. Creates body_region (seeded hierarchical references), subjective_check_in (unique per user/date), subjective_symptom (sparse with score/kind constraints), and injury_event (severity/date/description constraints) with supporting indexes.

drizzle/0068_subjective_inputs.sql

_journal.jsonRegister subjective inputs migration +7/-0

Register subjective inputs migration

• Adds migration journal metadata entry for 0068_subjective_inputs.

drizzle/meta/_journal.json

route.tsExtend MCP router DB contract to support transactions +1/-1

Extend MCP router DB contract to support transactions

• Broadens CreateMcpRouterOptions.db to include transaction so tools can use repositories that require transactional operations.

packages/server/src/mcp/route.ts

activity.tsAdd Drizzle check constraint for activity perceived exertion range +4/-0

Add Drizzle check constraint for activity perceived exertion range

• Adds a Drizzle-level check constraint matching the migration to ensure perceivedExertion is null or within 0–10.

src/db/schema/activity.ts

events.tsAdd Drizzle tables for body regions, check-ins, symptoms, and injuries +95/-0

Add Drizzle tables for body regions, check-ins, symptoms, and injuries

• Defines Drizzle schemas for bodyRegion, subjectiveCheckIn, subjectiveSymptom, and injuryEvent including indexes, unique constraints, foreign keys, and check constraints enforcing kind/score/severity/date rules.

src/db/schema/events.ts

cache.tsAdd cache-domain prefixes for activity and subjective queries +2/-0

Add cache-domain prefixes for activity and subjective queries

• Extends USER_QUERY_PREFIXES with activity and subjective domains so routers can invalidate caches via invalidateUserQueryDomains on relevant mutations.

src/lib/cache.ts

@qodo-code-review

qodo-code-review Bot commented Aug 2, 2026

Copy link
Copy Markdown

Code Review by Qodo

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

Context used
✅ Compliance rules (platform): 209 rules

Grey Divider


Remediation recommended

1. DB row schemas use z.string() ✓ Resolved 📘 Rule violation ≡ Correctness
Description
checkInRowSchema and injuryRowSchema model DB date/timestamp columns using raw z.string()
instead of the shared dateStringSchema/timestampStringSchema, risking inconsistent temporal
parsing across drivers and clients. This violates the requirement to use the shared DB temporal Zod
helpers for DB result schemas.
Code

packages/server/src/repositories/subjective-repository.ts[R18-22]

+const checkInRowSchema = z.object({
+  id: z.string(),
+  date: z.string(),
+  created_at: z.string(),
+  updated_at: z.string(),
Relevance

●●● Strong

Team repeatedly accepted replacing z.string() dates/timestamps with shared
dateStringSchema/timestampStringSchema helpers.

PR-#2388
PR-#867
PR-#1123

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The compliance rule requires shared dateStringSchema/timestampStringSchema for DB temporal
columns. In subjective-repository.ts, the DB row Zod schemas use raw z.string() for date,
created_at, updated_at, onset_date, and resolved_date even though shared helpers exist in
typed-sql.ts.

Rule 721933: Use shared date and timestamp schemas for DB result Zod types
packages/server/src/repositories/subjective-repository.ts[18-43]
packages/server/src/lib/typed-sql.ts[22-41]

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

## Issue description
DB result row schemas are using `z.string()` for SQL `DATE`/`TIMESTAMPTZ` fields.

## Issue Context
Per compliance, DB result schemas must use shared helpers from `packages/server/src/lib/typed-sql.ts`: `dateStringSchema` for SQL DATE and `timestampStringSchema` for timestamps.

## Fix Focus Areas
- packages/server/src/repositories/subjective-repository.ts[18-43]
- packages/server/src/lib/typed-sql.ts[22-41]

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


2. RPE zero not savable ✓ Resolved 🐞 Bug ≡ Correctness
Description
In ActivityPerceivedExertion (web), when the stored value is null the slider renders at 0 but the
saved payload remains null unless the user moves the slider, so clicking Save clears instead of
persisting an RPE of 0. This makes a valid value (0) impossible to save via the default UI state.
Code

packages/web/src/components/ActivityPerceivedExertion.tsx[R45-48]

+          type="button"
+          className="rounded bg-accent/15 px-2 py-1 text-xs text-accent"
+          onClick={() => mutation.mutate({ id: activityId, value: draft })}
+          disabled={mutation.isPending}
Relevance

●● Moderate

Similar UI “unset renders as value” bugs often fixed, but no exact RPE-zero submission precedent
found.

PR-#2285

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The range input is controlled with value={draft ?? 0} so an unset value renders as 0, but the Save
button submits value: draft (null) unchanged; the server mutation explicitly allows 0 (min(0)).

packages/web/src/components/ActivityPerceivedExertion.tsx[33-48]
packages/server/src/routers/activity.ts[155-164]

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 web RPE slider displays a fallback of `0` when `draft` is `null`, but the Save handler submits `draft` directly. This means the UI can show 0 while the mutation sends `null`.

### Issue Context
Server accepts `0..10` or `null` for RPE, so clients should be able to explicitly save `0` without requiring a slider change.

### Fix Focus Areas
- packages/web/src/components/ActivityPerceivedExertion.tsx[33-59]
- packages/server/src/routers/activity.ts[155-165]

### Implementation notes
Adjust the Save click handler to submit `draft ?? 0` (or refactor to track an explicit “unset vs set” state) so that the default slider position can be saved as 0. Keep Clear as the explicit `null` path, and update/extend tests to cover saving 0 from the unset state.

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


3. 0068 migration skips 0067 📘 Rule violation ⚙ Maintainability
Description
The PR adds a new Drizzle migration with numeric prefix 0068 even though the prior migration tag
is 0066, leaving a gap. This violates the requirement that new manual migrations use the next
unused sequential number and can break deterministic migration ordering assumptions.
Code

drizzle/0068_subjective_inputs.sql[R1-3]

+ALTER TABLE fitness.activity
+ADD CONSTRAINT activity_perceived_exertion_range
+CHECK (perceived_exertion IS NULL OR perceived_exertion BETWEEN 0 AND 10);
Relevance

●● Moderate

No prior reviews found enforcing strictly sequential migration numbers; only duplicate-prefix
collision checks seen.

PR-#587
PR-#1854

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 773523 requires new manual migrations to use the next unused sequential NNNN_*.sql number.
The PR introduces drizzle/0068_subjective_inputs.sql while the migration journal shows the
previous migration tag is 0066_sleep_staging_available and the next tag jumps to
0068_subjective_inputs, indicating 0067 was skipped.

Rule 773523: Enforce sequential naming for manual migration SQL files
drizzle/0068_subjective_inputs.sql[1-3]
drizzle/meta/_journal.json[544-556]

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

## Issue description
A new manual migration was added as `drizzle/0068_subjective_inputs.sql`, but the migration sequence currently goes from `0066_*` to `0068_*` with no `0067_*` migration.

## Issue Context
The compliance rule requires new manual migrations to use the next unused sequential integer prefix to avoid gaps and ordering ambiguity.

## Fix Focus Areas
- drizzle/0068_subjective_inputs.sql[1-3]
- drizzle/meta/_journal.json[544-556]

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


View more (3)
4. MCP timezone ignored ✓ Resolved 🐞 Bug ≡ Correctness
Description
The new get_subjective_timeline MCP tool declares an optional timezone input but ignores it and
always uses context.timezone. This makes the external tool contract misleading and inconsistent with
other MCP tools that honor timezone overrides.
Code

packages/server/src/mcp/tools.ts[R620-623]

+    async ({ start_date, end_date }) => {
+      requireMcpScope(context.scopes, "health:read");
+      assertDateRange(start_date, end_date);
+      const repository = new SubjectiveRepository(context.db, context.userId, context.timezone);
Relevance

●● Moderate

No direct precedent found for MCP timezone overrides; repo has heavy timezone work but unclear tool
contract enforcement.

PR-#1683
PR-#2290

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The subjective tool’s schema includes timezone but the handler destructures only start/end dates and
always uses context.timezone; in the same file, other tools demonstrate the intended timezone
override pattern.

packages/server/src/mcp/tools.ts[274-291]
packages/server/src/mcp/tools.ts[609-625]

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

### Issue description
`get_subjective_timeline` includes `timezone` in `inputSchema` but the handler doesn’t accept/use it.

### Issue Context
Other MCP tools (e.g. `get_daily_health_summary`) accept `timezone` and use `timezone ?? context.timezone`, so callers will reasonably expect the same behavior here.

### Fix Focus Areas
- packages/server/src/mcp/tools.ts[274-293]
- packages/server/src/mcp/tools.ts[609-625]

### Implementation notes
Either:
1) Accept `timezone` in the handler and pass `timezone ?? context.timezone` into `SubjectiveRepository`, or
2) Remove `timezone` from the tool’s `inputSchema` if it has no semantic meaning for date-based subjective timelines.
Add/adjust MCP route tests if needed to reflect the chosen contract.

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


5. Mobile injuries read-only ✓ Resolved 🐞 Bug ≡ Correctness
Description
The mobile Recovery SubjectiveTrackingPanel only queries and displays injuries but provides no
create/update/delete controls or mutations, so users cannot log or manage injury events from the
surfaced workflow. This conflicts with the PR’s stated goal/design of equivalent injury workflows
across clients.
Code

packages/mobile/components/SubjectiveTrackingPanel.tsx[R111-114]

+      <Text style={styles.timelineTitle}>Injuries and niggles</Text>
+      {injuries.data?.length ? (
+        injuries.data.map((injury) => (
+          <Text key={injury.id} style={styles.injury}>
Relevance

●● Moderate

No historical evidence on requiring mobile injury CRUD parity; similar UI-scope gaps not found.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The mobile panel sets up subjective queries and saveCheckIn mutation, but the injuries section only
renders a list with no mutation wiring; the design doc explicitly calls for mobile to create/manage
injuries.

packages/mobile/components/SubjectiveTrackingPanel.tsx[9-27]
packages/mobile/components/SubjectiveTrackingPanel.tsx[111-121]
docs/superpowers/specs/2026-08-02-subjective-inputs-design.md[23-25]

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 mobile SubjectiveTrackingPanel renders injuries but doesn’t expose any mutation paths for creating or editing injury/niggle events.

### Issue Context
The server `subjective` router provides injury CRUD, and the design doc states mobile should support creating/managing injuries.

### Fix Focus Areas
- packages/mobile/components/SubjectiveTrackingPanel.tsx[9-27]
- packages/mobile/components/SubjectiveTrackingPanel.tsx[111-121]
- docs/superpowers/specs/2026-08-02-subjective-inputs-design.md[23-25]

### Implementation notes
Add at least an MVP injury creation flow (kind, region, severity 0–10, description, onset/resolution as needed) wired to `trpc.subjective.createInjury`, plus invalidation of `subjective.injuries` (and any other dependent queries). Extend mobile tests to cover creating an injury/niggle from the Recovery screen.

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


6. Web injury kind/severity blocked ✓ Resolved 🐞 Bug ≡ Correctness
Description
The web SubjectiveTrackingPanel hardcodes injury creation to kind='niggle' and reuses the symptom
score input (min 1) as injury severity, so users cannot create kind='injury' events or log severity
0 even though the server contract allows both. This prevents valid injury_event states from being
entered through the UI.
Code

packages/web/src/components/SubjectiveTrackingPanel.tsx[R192-195]

+                kind: "niggle",
+                bodyRegionId: selectedRegion,
+                onsetDate: date,
+                resolvedDate: null,
Relevance

●● Moderate

No historical evidence on enforcing full injury kind/severity input coverage in web UI; unclear
product intent.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The web panel’s score input is constrained to 1–10 and its createInjury mutation payload hardcodes
kind='niggle'; meanwhile the server schema accepts severity min(0) and supports both kinds.

packages/web/src/components/SubjectiveTrackingPanel.tsx[121-131]
packages/web/src/components/SubjectiveTrackingPanel.tsx[186-199]
packages/server/src/routers/subjective.ts[20-27]

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 web UI only exposes “Add niggle” and sends `kind: 'niggle'`, and it uses the symptom score (1–10) input as injury severity, which should be 0–10.

### Issue Context
Server-side `createInjury` supports both `injury` and `niggle` and validates severity `0..10`.

### Fix Focus Areas
- packages/web/src/components/SubjectiveTrackingPanel.tsx[121-131]
- packages/web/src/components/SubjectiveTrackingPanel.tsx[176-203]
- packages/server/src/routers/subjective.ts[20-27]

### Implementation notes
Introduce explicit injury controls:
- A selector/toggle for kind (`injury` vs `niggle`).
- A dedicated severity input that allows 0–10 (don’t reuse the symptom score state).
Update the component tests to cover creating an `injury` and creating severity `0`.

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



Informational

7. Missing body_region parent FK ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
The SQL migration creates a self-referencing FK for fitness.body_region.parent_id, but the Drizzle
table model defines parentId without a .references() constraint. This creates schema metadata drift
that can lead to incorrect schema diffs / future migration generation behavior.
Code

src/db/schema/events.ts[R245-248]

+    id: text("id").primaryKey(),
+    parentId: text("parent_id"),
+    label: text("label").notNull(),
+    kind: text("kind").notNull(),
Relevance

●●● Strong

Schema drift fixes (migration vs Drizzle model constraints) have been accepted previously (missing
PK/FK alignment).

PR-#1365
PR-#1552

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The migration explicitly defines parent_id as REFERENCES fitness.body_region (id), while the
Drizzle schema’s parentId column has no references() call (unlike other FKs in the same file).

drizzle/0068_subjective_inputs.sql[5-8]
src/db/schema/events.ts[242-250]

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

### Issue description
`fitness.body_region.parent_id` is a self-referencing foreign key in the migration, but the Drizzle schema models it as an unconstrained text column.

### Issue Context
Postgres will still enforce the FK created by the migration, but the ORM schema no longer faithfully represents the DB schema, which can cause schema drift in tooling (introspection / migration generation / documentation).

### Fix Focus Areas
- src/db/schema/events.ts[242-260]
- drizzle/0068_subjective_inputs.sql[5-14]

### Implementation notes
Update `parentId` to include a self-reference, e.g. `parentId: text("parent_id").references(() => bodyRegion.id, { onDelete: "restrict" })` (adjust to match the migration’s ON DELETE RESTRICT). If Drizzle requires a specific pattern for self-references, follow that pattern and add/extend a schema test to assert the FK exists.

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


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread packages/server/src/repositories/subjective-repository.ts Outdated
Comment thread drizzle/0068_subjective_inputs.sql Outdated
Comment thread packages/web/src/components/ActivityPerceivedExertion.tsx Outdated
Comment thread src/db/schema/events.ts
Comment thread packages/server/src/mcp/tools.ts
Comment thread packages/web/src/components/SubjectiveTrackingPanel.tsx Outdated
Comment thread packages/mobile/components/SubjectiveTrackingPanel.tsx
@codereviewbot-ai

codereviewbot-ai Bot commented Aug 3, 2026

Copy link
Copy Markdown

LGTM! All new subjective tracking endpoints, MCP tools, database schemas, repository methods, and mobile/web components look well-designed and thoroughly tested. The issues previously flagged by automated review (migration numbering sequence, Drizzle FK references, date schemas, and client UI controls) remain the primary actionable items.


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

@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: 29

🤖 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/superpowers/plans/2026-08-02-subjective-inputs.md`:
- Line 3: Remove the agent-only “REQUIRED SUB-SKILL” directive from the plan in
2026-08-02-subjective-inputs.md, and place the equivalent instruction in
AGENTS.md if it must be retained. Keep the plan’s human-facing documentation
standalone.
- Line 105: Update the checklist item near the commit instruction to remove the
hard-coded Asherlc/issue-2247-subjective-inputs branch requirement. Make the
commit step branch-neutral, or explicitly require user approval before switching
or creating a branch, while preserving the conventional commit subject
requirement.

In `@drizzle/0068_subjective_inputs.sql`:
- Around line 1-3: Update the migration statement for
activity_perceived_exertion_range to add the CHECK constraint with NOT VALID,
then issue a separate ALTER TABLE fitness.activity VALIDATE CONSTRAINT
activity_perceived_exertion_range statement. Do not modify the existing schema
definition in src/db/schema/activity.ts.

In `@packages/mobile/components/ActivityPerceivedExertion.tsx`:
- Around line 7-64: Add ActivityPerceivedExertion.test.tsx plus corresponding
.storybook and .rnstorybook stories covering default, pending, saved, cleared,
and error states. Also add SubjectiveTrackingPanel.test.tsx and stories in both
Storybook locations covering loading, error, empty, logged, and injury states;
apply the requested artifacts to both named components.

In `@packages/mobile/components/SubjectiveTrackingPanel.tsx`:
- Line 15: Update the regionId state in SubjectiveTrackingPanel to use string |
null, initialize it with null, and preserve null whenever no body region is
selected instead of using an empty string.
- Around line 111-120: Add an injury/niggle entry editor to
SubjectiveTrackingPanel alongside the existing injuries.data rendering,
collecting onset date, body region, description, severity, and optional
resolution date. Submit these raw fields through the existing server contract,
refresh the injury events after a successful write, and preserve the current
empty-state and event-list behavior.
- Around line 12-14: Update SubjectiveTrackingPanel’s checkIn, regions, and
injuries query handling to render explicit loading and error states instead of
treating failed queries as empty data. Ensure check-in write actions remain
disabled until checkIn successfully provides the current record, preventing
submissions based on unavailable data; keep empty-state messaging only for
successful empty responses.
- Around line 99-103: Update the clear-symptoms handler in
SubjectiveTrackingPanel so it does not call setSavedSymptoms([]) before
save.mutate succeeds; instead update the local draft from the successful
mutation result, or restore the previous symptoms in the mutation’s error path
while preserving the existing failure state.

In `@packages/server/src/mcp/route.test.ts`:
- Line 428: Add tools/call coverage for get_subjective_timeline in the route
tests: add a successful invocation asserting the repository payload, and an
invocation with start_date later than end_date asserting the range error
produced by assertDateRange. Follow the existing tools/call test structure and
fixtures used by the neighboring tool tests.

In `@packages/server/src/mcp/tools.ts`:
- Around line 614-624: Remove the unused timezone parameter from the
get_subjective_timeline inputSchema in packages/server/src/mcp/tools.ts (lines
614-624), then remove the corresponding timezone property from the expected
schema in packages/server/src/mcp/route.test.ts (lines 474-482) so the test
matches the corrected contract.

In `@packages/server/src/repositories/activity-repository.test.ts`:
- Around line 974-979: Update the test around the query assertions to verify
that the perceived-exertion value is bound as a parameter. In the existing
query.params assertions for setPerceivedExertion, add a check that the
parameters contain the expected value 7, while preserving the current table and
ID assertions.

In `@packages/server/src/repositories/activity-repository.ts`:
- Around line 566-576: Update the group lookup in the activity query within the
repository method containing the fitness.v_activity_members subquery: replace
the scalar activity_id equality subquery with an IN-based condition so multiple
matching fitness.v_activity rows are supported, while preserving the existing
member_activity_id, user_id, and timestamp predicates.

In `@packages/server/src/repositories/subjective-repository.test.ts`:
- Around line 86-116: Add focused tests beside the existing createInjury test:
call updateInjury with only resolvedDate set to null and assert the generated
SQL from execute contains “resolved_date = NULL” while query.params includes the
injury id and USER_ID; add a deleteInjury case using an empty repository result
and assert it resolves to false.

In `@packages/server/src/repositories/subjective-repository.ts`:
- Around line 25-31: Split symptom row validation by query: keep
symptomRowSchema without check_in_id for readCheckIn, and add a distinct
timeline row schema requiring check_in_id for timeline results. Update the
timeline query/grouping path to use the required field directly and remove the
symptom.check_in_id ?? "" fallback, ensuring absent values remain undefined or
null rather than an empty-string grouping key.
- Around line 183-190: Update deleteInjury to validate the DELETE ... RETURNING
result with a Zod schema, using this.query.executeWithSchema in line with the
repository’s existing query pattern. Base the boolean return on the validated
rows while preserving the current user and injury filtering behavior.
- Around line 96-107: Replace the per-symptom INSERT loop in the check-in save
transaction with a single multi-row INSERT built using sql.join, while
preserving the existing DELETE and symptom field mappings. Keep the transaction
behavior and return this.checkIn(date) unchanged.
- Around line 18-43: Update the row schemas in subjective-repository.ts to
import and use dateStringSchema for date and onset_date, and
timestampStringSchema for created_at and updated_at; apply timestampStringSchema
with nullable handling to resolved_date while preserving its nullable contract.

In `@packages/server/src/routers/activity.test.ts`:
- Around line 867-896: Extend the setPerceivedExertion test suite with a
nullable-clear case: call caller.setPerceivedExertion using the existing
activity ID and value null, mock or reuse
ActivityRepository.prototype.setPerceivedExertion to return a null perceived
exertion, and assert the response is { perceivedExertion: null } plus repository
delegation with null.
- Around line 13-18: Update the dofek/lib/cache mock to preserve the queryCache
export, providing an invalidateByPrefix mock alongside
mockInvalidateUserQueryDomains (or retaining the original queryCache export), so
invalidateActivityListCaches works for recompute, delete, bulkDelete, and
restoreProviderAbsent tests.

In `@packages/server/src/routers/subjective.ts`:
- Line 11: Replace the regex-based dateSchema in the subjective router with
z.iso.date() so invalid calendar dates are rejected as BAD_REQUEST before
reaching Postgres. Apply the same migration to the duplicate date schema in the
MCP tools module, preserving one canonical date-only validator across both
locations.

In `@packages/web/src/components/ActivityPerceivedExertion.tsx`:
- Around line 33-51: Require an explicit slider selection before allowing Save
in ActivityPerceivedExertion: disable the Save button when draft is null, while
preserving the existing mutation behavior for numeric values and the separate
Clear action for null.

In `@packages/web/src/components/SubjectiveTrackingPanel.stories.tsx`:
- Around line 52-64: Expand
packages/web/src/components/SubjectiveTrackingPanel.stories.tsx#L52-L64 by
adding stories for the not-logged, symptoms-logged, and injuries-present states
alongside the existing all-clear Default story. Also update
packages/web/src/components/ActivityPerceivedExertion.stories.tsx#L42-L60 to add
coverage for mutation-pending and mutation-error states while preserving the
existing Unset and Logged stories.

In `@packages/web/src/components/SubjectiveTrackingPanel.tsx`:
- Around line 51-60: Update the useEffect that synchronizes symptoms from
checkIn.data so it initializes local state only once per check-in load, rather
than overwriting edits after background refetches. Use an initialization ref or
equivalent guard tied to the check-in identity, preserving unsaved symptoms
added through addSymptom while still populating symptoms from the initial server
response.
- Around line 28-30: The SubjectiveTrackingPanel silently treats query failures
as empty states. Update the render logic using checkIn, regions, and injuries to
check each query’s error before empty-data fallbacks, and render
QueryStatePanel’s explicit error state at the respective sections; preserve
normal loading and genuine empty-state behavior.
- Around line 186-202: Add a dedicated injury-severity state and input in
SubjectiveTrackingPanel, separate from the symptom score state and its “Score
(1–10)” control. Bind the new injury severity input to the niggle form and
update createInjury.mutate to send that value instead of selectedScore.
- Around line 121-132: Clamp the numeric value in the score input’s onChange
handler before passing it to setSelectedScore, ensuring selectedScore always
remains between 1 and 10. Preserve the existing input constraints and
save.mutate flow while preventing out-of-range values from reaching the
mutation.
- Around line 190-199: Update SubjectiveTrackingPanel’s injury creation flow so
onsetDate uses editable state rather than the fixed today() value. Add a date
input bound to that state, initialize it to today, and pass the user-selected
date to createInjury.mutate while preserving the existing submission behavior.
- Around line 15-23: Replace the custom date construction in today() with the
shared formatDateYmd() helper, importing it from the established date utility
module; use useTodayQueryDate() instead only if this component requires
automatic date rollover behavior.

In `@src/db/schema/events.ts`:
- Around line 242-260: Update the parentId column in bodyRegion to declare a
self-reference to bodyRegion.id using Drizzle’s references configuration,
preserving the migration’s ON DELETE RESTRICT behavior. Do not alter the
surrounding indexes, checks, or other columns.
🪄 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: 4d60cc0c-c4f6-4664-9ff3-954d12848c6f

📥 Commits

Reviewing files that changed from the base of the PR and between f49cc12 and 164ddda.

📒 Files selected for processing (45)
  • cspell.json
  • docs/production-incident-baseline.md
  • docs/schema.dbml
  • docs/schema.md
  • docs/schema.puml
  • docs/superpowers/plans/2026-08-02-subjective-inputs.md
  • docs/superpowers/specs/2026-08-02-subjective-inputs-design.md
  • drizzle/0068_subjective_inputs.sql
  • drizzle/meta/_journal.json
  • packages/mobile/app/(tabs)/recovery.test.tsx
  • packages/mobile/app/(tabs)/recovery.tsx
  • packages/mobile/app/activity/[id].test.tsx
  • packages/mobile/app/activity/[id].tsx
  • packages/mobile/components/ActivityPerceivedExertion.tsx
  • packages/mobile/components/SubjectiveTrackingPanel.tsx
  • packages/server/src/mcp/route-lifecycle.test.ts
  • packages/server/src/mcp/route.test.ts
  • packages/server/src/mcp/route.ts
  • packages/server/src/mcp/tools.ts
  • packages/server/src/models/activity.test.ts
  • packages/server/src/models/activity.ts
  • packages/server/src/repositories/activity-repository.test.ts
  • packages/server/src/repositories/activity-repository.ts
  • packages/server/src/repositories/subjective-repository.test.ts
  • packages/server/src/repositories/subjective-repository.ts
  • packages/server/src/router.test.ts
  • packages/server/src/router.ts
  • packages/server/src/routers/activity.test.ts
  • packages/server/src/routers/activity.ts
  • packages/server/src/routers/subjective.test.ts
  • packages/server/src/routers/subjective.ts
  • packages/web/src/components/ActivityPerceivedExertion.stories.tsx
  • packages/web/src/components/ActivityPerceivedExertion.test.tsx
  • packages/web/src/components/ActivityPerceivedExertion.tsx
  • packages/web/src/components/SubjectiveTrackingPanel.stories.tsx
  • packages/web/src/components/SubjectiveTrackingPanel.test.tsx
  • packages/web/src/components/SubjectiveTrackingPanel.tsx
  • packages/web/src/pages/ActivityDetailPage.stories.tsx
  • packages/web/src/pages/ActivityDetailPage.test.tsx
  • packages/web/src/pages/ActivityDetailPage.tsx
  • packages/web/src/pages/TrackingPage.tsx
  • src/db/schema/activity.ts
  • src/db/schema/events.ts
  • src/db/subjective-inputs.integration.test.ts
  • src/lib/cache.ts

Comment thread docs/superpowers/plans/2026-08-02-subjective-inputs.md Outdated
Comment thread docs/superpowers/plans/2026-08-02-subjective-inputs.md Outdated
Comment thread drizzle/0068_subjective_inputs.sql Outdated
Comment thread packages/mobile/components/ActivityPerceivedExertion.tsx
Comment thread packages/mobile/components/SubjectiveTrackingPanel.tsx
Comment thread packages/web/src/components/SubjectiveTrackingPanel.tsx
Comment thread packages/web/src/components/SubjectiveTrackingPanel.tsx Outdated
Comment thread packages/web/src/components/SubjectiveTrackingPanel.tsx Outdated
Comment thread packages/web/src/components/SubjectiveTrackingPanel.tsx Outdated
Comment thread src/db/schema/events.ts
@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.

2 similar comments
@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: 7

🤖 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 `@drizzle/0068_subjective_inputs.sql`:
- Line 14: Change the sort_order column definition in migration 0068 from bigint
to integer to match the canonical sortOrder definition in the events schema.
Keep the existing NOT NULL constraint and DEFAULT 0 unchanged.
- Line 115: Make injury severity nullable throughout the subjective-inputs flow:
remove NOT NULL from the migration, update the Drizzle schema, Zod contracts,
repository row schema, and related tests to accept null, and preserve the 0–10
validation constraint for non-null values.

In `@packages/mobile/components/SubjectiveTrackingPanel.test.tsx`:
- Around line 51-55: Update the beforeEach setup in SubjectiveTrackingPanel
tests to restore the default data and error values for mocks.regionsResult,
mocks.checkInResult, and mocks.injuriesResult, in addition to resetting spies,
so each test starts with isolated query fixtures.

In `@packages/mobile/components/SubjectiveTrackingPanel.tsx`:
- Around line 62-64: Update the mobile SubjectiveTrackingPanel initialization
effect to use a useRef guard like the web panel’s initializedCheckInRef,
applying the first checkIn data only once so later refetches cannot overwrite
symptoms added through addSymptom. Import useRef with the existing React hooks
and add a regression test confirming unsaved symptoms persist after a check-in
refetch.

In `@packages/web/src/components/SubjectiveTrackingPanel.stories.tsx`:
- Around line 98-112: Extend SubjectiveStoryScenario and createMockLink in
SubjectiveTrackingPanel.stories.tsx to support loading and error states, keeping
the loading observable open and emitting a TRPCClientError for the error
scenario. Add exported Loading and ErrorState stories that render
SubjectiveStory with the corresponding scenarios, while preserving the existing
stories.

In `@packages/web/src/components/SubjectiveTrackingPanel.tsx`:
- Around line 46-52: Update the onSuccess handler of createInjury in
SubjectiveTrackingPanel to invalidate subjective.timeline in addition to
subjective.injuries, matching the existing post-write cache invalidation
behavior.
- Line 24: Replace the mount-frozen formatDateYmd useMemo in
SubjectiveTrackingPanel with the existing useTodayQueryDate hook, and use its
returned date consistently for all queries, invalidation calls, and mutation
arguments in the panel so the check-in date rolls over after local midnight.
🪄 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: 66f0298d-665f-4bc6-b47a-a0cbc1869a67

📥 Commits

Reviewing files that changed from the base of the PR and between 164ddda and 7e22407.

📒 Files selected for processing (25)
  • docs/superpowers/plans/2026-08-02-subjective-inputs.md
  • drizzle/0068_subjective_inputs.sql
  • packages/mobile/components/ActivityPerceivedExertion.stories.tsx
  • packages/mobile/components/ActivityPerceivedExertion.test.tsx
  • packages/mobile/components/ActivityPerceivedExertion.tsx
  • packages/mobile/components/SubjectiveTrackingPanel.stories.tsx
  • packages/mobile/components/SubjectiveTrackingPanel.test.tsx
  • packages/mobile/components/SubjectiveTrackingPanel.tsx
  • packages/server/src/lib/date-schema.ts
  • packages/server/src/mcp/route.test.ts
  • packages/server/src/mcp/tools.ts
  • packages/server/src/repositories/activity-repository.test.ts
  • packages/server/src/repositories/activity-repository.ts
  • packages/server/src/repositories/subjective-repository.test.ts
  • packages/server/src/repositories/subjective-repository.ts
  • packages/server/src/routers/activity.test.ts
  • packages/server/src/routers/subjective.test.ts
  • packages/server/src/routers/subjective.ts
  • packages/web/src/components/ActivityPerceivedExertion.stories.tsx
  • packages/web/src/components/ActivityPerceivedExertion.test.tsx
  • packages/web/src/components/ActivityPerceivedExertion.tsx
  • packages/web/src/components/SubjectiveTrackingPanel.stories.tsx
  • packages/web/src/components/SubjectiveTrackingPanel.test.tsx
  • packages/web/src/components/SubjectiveTrackingPanel.tsx
  • src/db/schema/events.ts

Comment thread drizzle/0068_subjective_inputs.sql
Comment thread drizzle/0068_subjective_inputs.sql Outdated
Comment thread packages/mobile/components/SubjectiveTrackingPanel.test.tsx
Comment thread packages/mobile/components/SubjectiveTrackingPanel.tsx
Comment thread packages/web/src/components/SubjectiveTrackingPanel.stories.tsx
Comment thread packages/web/src/components/SubjectiveTrackingPanel.tsx Outdated
Comment thread packages/web/src/components/SubjectiveTrackingPanel.tsx
@Asherlc
Asherlc force-pushed the Asherlc/issue-2247-subjective-inputs branch from 7e22407 to f6de089 Compare August 3, 2026 07:40
@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.

@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: 10

Caution

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

⚠️ Outside diff range comments (1)
docs/production-incident-baseline.md (1)

205-210: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Revoke and replace EXPO_TOKEN before merging the remediation.

The document states that EXPO_TOKEN remains present in dev, prod, and staging, and that revocation is still required. Masking future logs does not invalidate an already exposed credential. Complete Expo-side revocation and replacement, then record the completion evidence.

🤖 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 `@docs/production-incident-baseline.md` around lines 205 - 210, Complete
Expo-side revocation and replacement of EXPO_TOKEN before merging the
remediation, then update the Remaining Risk section to document completion
evidence and remove the statement that the credential remains present or
revocation is still required.
🤖 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/superpowers/plans/2026-08-02-subjective-inputs.md`:
- Line 17: Update the field-type statement in the plan to specify each numeric
contract independently: symptom scores as integers from 1–10, injury severity as
integers from 0–10, and activity RPE as a nullable numeric value from 0–10.
Remove the combined “nullable-or-integer/real” wording so the schema and Zod
contracts have unambiguous types.

In `@drizzle/0069_subjective_inputs.sql`:
- Around line 111-129: Add an index on injury_event.body_region_id, matching the
existing subjective_symptom_region_idx pattern, while preserving the current
injury_event_user_onset_idx and table constraints.

In `@drizzle/meta/_journal.json`:
- Line 568: Update the drizzle journal entry for 0069_subjective_inputs so its
when timestamp is later than 0068_canonical_activity_types (1785725168000).
Regenerate the entry or adjust the existing when value while preserving the
journal structure.

In `@packages/mobile/app/activity/`[id].test.tsx:
- Line 236: Extend the test around mockPerceivedExertionMutate and
ActivityDetailScreen to assert that the rendered activity includes the
perceived-exertion UI, using the “Session perceived exertion” accessibility
label or “Session effort” text and verifying the value from
baseCyclingActivity.perceivedExertion.

In `@packages/mobile/components/ActivityPerceivedExertion.test.tsx`:
- Around line 17-26: Update
packages/mobile/components/ActivityPerceivedExertion.test.tsx:17-26 by capturing
the options passed to setPerceivedExertion.useMutation and making mutate invoke
options.onSuccess or options.onError based on a controllable test flag,
preserving the existing mocks so tests can verify draft updates, invalidation,
and error reporting. Apply the same pattern in
packages/mobile/components/SubjectiveTrackingPanel.test.tsx:39-46 to both
createInjury.useMutation and saveCheckIn.useMutation, enabling verification of
form reset, injuries/timeline/checkIn invalidation, and captureException; mirror
recomputeMutation in packages/mobile/app/activity/[id].test.tsx.

In `@packages/mobile/components/SubjectiveTrackingPanel.tsx`:
- Around line 102-114: Separate the injury form’s region state from the symptom
form’s regionId: add dedicated injury-region state and a region selector within
the injury form, then update createInjury.mutate to use that value. Keep the
existing symptom selector and addSymptom flow tied exclusively to regionId, and
require the injury selector’s chosen region when submitting.
- Around line 102-114: Replace the tap-to-cycle behavior in the region-selection
Pressable around the onPress handler with a scrollable list or modal picker
backed by regions.data, allowing users to select any region directly, including
individual fingers and pulley locations. Preserve the current selected label and
update regionId when an option is chosen.

In `@packages/server/src/routers/subjective.test.ts`:
- Around line 31-43: Remove the vi.mock block for executeWithSchema from the
subjective router tests, leaving the real typed-SQL implementation active so
injuryRowSchema validation is exercised by tests such as “accepts nullable
injury severity.”

In `@packages/server/src/routers/subjective.ts`:
- Around line 20-27: Add explicit onset/resolution date validation to
createInjury and updateInjury before database writes: reject any resolvedDate
earlier than onsetDate with a tRPC BAD_REQUEST error and actionable
client-visible message. In updateInjury, compare the provided date against the
stored injury row when either date is omitted, preserving existing values for
the comparison and allowing nullable resolvedDate.

In `@packages/web/src/components/SubjectiveTrackingPanel.tsx`:
- Around line 39-45: Update the “Log all clear” action in
SubjectiveTrackingPanel to clear the local symptoms state when the all-clear
mutation is triggered, ensuring staged drafts are removed and the badge displays
“All clear” after success. Add a regression test covering a staged draft
symptom, clicking “Log all clear,” and asserting the symptoms list is empty and
the badge shows “All clear.”

---

Outside diff comments:
In `@docs/production-incident-baseline.md`:
- Around line 205-210: Complete Expo-side revocation and replacement of
EXPO_TOKEN before merging the remediation, then update the Remaining Risk
section to document completion evidence and remove the statement that the
credential remains present or revocation is still required.
🪄 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: 1b14e402-cb35-4530-b5fa-757991100418

📥 Commits

Reviewing files that changed from the base of the PR and between 7e22407 and 6359421.

📒 Files selected for processing (46)
  • cspell.json
  • docs/production-incident-baseline.md
  • docs/schema.dbml
  • docs/schema.md
  • docs/schema.puml
  • docs/superpowers/plans/2026-08-02-subjective-inputs.md
  • docs/superpowers/specs/2026-08-02-subjective-inputs-design.md
  • drizzle/0069_subjective_inputs.sql
  • drizzle/meta/_journal.json
  • packages/mobile/app/(tabs)/recovery.test.tsx
  • packages/mobile/app/(tabs)/recovery.tsx
  • packages/mobile/app/activity/[id].test.tsx
  • packages/mobile/app/activity/[id].tsx
  • packages/mobile/components/ActivityPerceivedExertion.stories.tsx
  • packages/mobile/components/ActivityPerceivedExertion.test.tsx
  • packages/mobile/components/ActivityPerceivedExertion.tsx
  • packages/mobile/components/SubjectiveTrackingPanel.stories.tsx
  • packages/mobile/components/SubjectiveTrackingPanel.test.tsx
  • packages/mobile/components/SubjectiveTrackingPanel.tsx
  • packages/server/src/lib/date-schema.ts
  • packages/server/src/mcp/route-lifecycle.test.ts
  • packages/server/src/mcp/route.test.ts
  • packages/server/src/mcp/route.ts
  • packages/server/src/mcp/tools.ts
  • packages/server/src/models/activity.test.ts
  • packages/server/src/models/activity.ts
  • packages/server/src/repositories/activity-repository.test.ts
  • packages/server/src/repositories/activity-repository.ts
  • packages/server/src/repositories/subjective-repository.test.ts
  • packages/server/src/repositories/subjective-repository.ts
  • packages/server/src/router.test.ts
  • packages/server/src/router.ts
  • packages/server/src/routers/activity.test.ts
  • packages/server/src/routers/activity.ts
  • packages/server/src/routers/subjective.test.ts
  • packages/server/src/routers/subjective.ts
  • packages/web/src/components/ActivityPerceivedExertion.stories.tsx
  • packages/web/src/components/ActivityPerceivedExertion.test.tsx
  • packages/web/src/components/ActivityPerceivedExertion.tsx
  • packages/web/src/components/SubjectiveTrackingPanel.stories.tsx
  • packages/web/src/components/SubjectiveTrackingPanel.test.tsx
  • packages/web/src/components/SubjectiveTrackingPanel.tsx
  • packages/web/src/pages/ActivityDetailPage.stories.tsx
  • packages/web/src/pages/ActivityDetailPage.test.tsx
  • packages/web/src/pages/ActivityDetailPage.tsx
  • packages/web/src/pages/TrackingPage.tsx

Comment thread docs/superpowers/plans/2026-08-02-subjective-inputs.md Outdated
Comment thread drizzle/0069_subjective_inputs.sql
Comment thread drizzle/meta/_journal.json Outdated
Comment thread packages/mobile/app/activity/[id].test.tsx
Comment thread packages/mobile/components/ActivityPerceivedExertion.test.tsx
Comment thread packages/mobile/components/SubjectiveTrackingPanel.tsx
Comment thread packages/server/src/routers/subjective.test.ts Outdated
Comment thread packages/server/src/routers/subjective.ts
Comment thread packages/web/src/components/SubjectiveTrackingPanel.tsx
@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.

@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.

@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.

@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

codereviewbot-ai Bot commented Aug 3, 2026

Copy link
Copy Markdown

LGTM!

The changes are well-structured, correct, and well-tested:

  • Database & Schemas: Migration 0069_subjective_inputs.sql and view 01_v_activity.sql properly define table structures, constraints, indices, and view outputs for subjective check-ins, symptoms, injuries, body regions, and session perceived exertion.
  • Repositories & API: SubjectiveRepository and ActivityRepository use parameterized SQL queries, transaction safety, batch inserts via sql.join, and validated Zod schemas (dateStringSchema / timestampStringSchema).
  • MCP Tools: get_subjective_timeline adheres strictly to contract, validates inputs, and has route test coverage.
  • Mobile UI & Components: SubjectiveTrackingPanel and ActivityPerceivedExertion correctly handle draft states, loading/error fallbacks, mutation invalidations, and accessibility labels, supported by component tests and Storybook stories.

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

@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: 8

Caution

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

⚠️ Outside diff range comments (5)
docs/schema.dbml (2)

1661-1664: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Redundant second index on subjective_check_in (user_id, date).

subjective_check_in_user_date_key is a unique index on (user_id, date). subjective_check_in_user_date_idx is a non-unique index on the same columns in the same order. The unique index already serves every lookup and range scan the second index would serve. The duplicate only adds write cost and storage.

Drop subjective_check_in_user_date_idx from the migration and the Drizzle schema.

🤖 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 `@docs/schema.dbml` around lines 1661 - 1664, Remove the redundant
subjective_check_in_user_date_idx definition from the subjective_check_in schema
and its migration, while retaining the unique subjective_check_in_user_date_key
index on (user_id, date).

1483-1502: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

No change needed. docs/schema.dbml is stale; the migration and Drizzle schema define injury_event.severity as nullable with a 0–10 range.

🤖 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 `@docs/schema.dbml` around lines 1483 - 1502, Update the injury_event
definition to match the migration and Drizzle schema: make severity nullable
instead of not null and document or enforce its valid 0–10 range using the
established schema convention. Leave the other columns and indexes unchanged.
docs/schema.puml (1)

816-824: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Document the body_region.parent_id self-reference.

parent_id is a real FK in drizzle/0069_subjective_inputs.sql and src/db/schema/events.ts, so AGENTS.md rule 2 requires diagram/schema changes to match production constraints. Mark parent_id : text <<FK>> and add the body_region ||--o{ body_region relationship.

🤖 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 `@docs/schema.puml` around lines 816 - 824, Update the body_region entity
definition to mark parent_id as a foreign key, then add the self-referential
body_region relationship using the diagram’s existing relationship notation.
packages/server/src/routers/activity.ts (1)

156-160: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Put setPerceivedExertion behind the account-erasure write fence.

This mutation writes user health data directly through ActivityRepository. It bypasses withAccountErasureUserWriteFence, unlike the other activity writes in this router. A write can then succeed after account erasure has started.

Run repo.setPerceivedExertion() inside withAccountErasureUserWriteFence(). Add a router test that verifies this mutation uses the fence.

Proposed fix
   setPerceivedExertion: protectedProcedure
     .input(z.object({ id: z.guid(), value: z.number().min(0).max(10).nullable() }))
     .mutation(async ({ ctx, input }) => {
-      const repo = new ActivityRepository(ctx.db, ctx.userId, ctx.timezone, ctx.accessWindow);
-      const result = await repo.setPerceivedExertion(input.id, input.value);
+      const result = await withAccountErasureUserWriteFence(
+        ctx.db,
+        ctx.userId,
+        async (transaction) => {
+          const repo = new ActivityRepository(
+            transaction,
+            ctx.userId,
+            ctx.timezone,
+            ctx.accessWindow,
+          );
+          return repo.setPerceivedExertion(input.id, input.value);
+        },
+      );
       if (!result.found) {
         throw new TRPCError({ code: "NOT_FOUND", message: "Activity not found" });
       }
🤖 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/routers/activity.ts` around lines 156 - 160, Wrap the
ActivityRepository.setPerceivedExertion call in setPerceivedExertion with
withAccountErasureUserWriteFence, matching the pattern used by other activity
write mutations. Add a router test confirming the fence is invoked for this
mutation and prevents writes after account erasure begins.
drizzle/meta/_journal.json (1)

552-563: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Fix the duplicated idx in drizzle/meta/_journal.json.

drizzle/meta/_journal.json has duplicate entries at idx: 79 for 0067_personal_experiment_learning_loop and 0068_canonical_activity_types. Drizzle metadata uses idx as the sequential migration index, so this also shifts subsequent entries out of line. Reindex the affected journal entries so each idx is unique and sequential.

🤖 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/meta/_journal.json` around lines 552 - 563, Update the migration
journal entries following 0067_personal_experiment_learning_loop in
drizzle/meta/_journal.json so the duplicate idx 79 is removed and every
subsequent entry, including 0068_canonical_activity_types, has a unique
sequential idx.
🤖 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 `@packages/mobile/components/SubjectiveTrackingPanel.test.tsx`:
- Around line 200-217: Split the shared mocks.invalidate spy into separate spies
for checkIn.invalidate, timeline.invalidate, and injuries.invalidate, then
update the test around SubjectiveTrackingPanel to assert each expected
invalidation explicitly after adding an injury. Keep the existing mutation
success and error callback assertions unchanged.

In `@packages/mobile/components/SubjectiveTrackingPanel.tsx`:
- Around line 205-218: Update the injury submission logic in the Pressable’s
disabled condition and onPress guard to require a valid injuryOnsetDate
alongside injuryRegionId and injuryDescription. Prevent createInjury.mutate from
running when the onset date is empty or malformed, while preserving the existing
pending-state behavior.
- Around line 158-165: Gate the injury-region control in SubjectiveTrackingPanel
behind the same regions loading and error states used by the symptom-region
control. Render QueryStatePanel instead of the Pressable while regions.isLoading
or regions.error is present, and only allow the injury picker to open when
regions has successfully loaded; remove the regions.data ?? [] fallback in the
modal path so failed queries do not show an empty picker.

In `@packages/server/src/repositories/subjective-repository.ts`:
- Around line 130-141: Define a shared sql projection fragment for the nine
injury columns near the repository’s other query helpers, then interpolate it in
getInjury and the injuries, createInjury, updateInjury, and timeline queries.
Remove each duplicated SELECT/RETURNING column list while preserving the
existing aliases and injuryRowSchema-compatible shape.

In `@packages/server/src/routers/subjective.test.ts`:
- Around line 35-42: Move the duplicated makeCaller factory from
subjective.test.ts and activity.test.ts into the colocated test-helpers.ts,
exporting a shared factory that accepts the router or create-caller dependency.
Update both test files to use this helper while preserving the existing execute
mock, fallback response, transaction passthrough, and user/timezone context.

In `@packages/web/src/components/SubjectiveTrackingPanel.test.tsx`:
- Around line 20-41: Split the shared mocks.invalidate spy in the trpc mock so
subjective.checkIn.invalidate, subjective.timeline.invalidate, and
subjective.injuries.invalidate each use distinct spies. Update the associated
tests to assert the specific invalidation target, especially timeline.invalidate
after createInjury, so a missing individual call is detected.

In `@packages/web/src/components/SubjectiveTrackingPanel.tsx`:
- Around line 216-232: Update the injury-region control in
SubjectiveTrackingPanel so it follows the regions query loading and error states
before rendering the select, using QueryStatePanel consistently with the
symptom-region control. Do not render the select from the regionOptions fallback
when regions is loading or has an error; preserve the existing selection
behavior for successfully loaded regions and handle the empty state explicitly.
- Around line 289-306: Update the disabled condition on the injury submission
button in SubjectiveTrackingPanel so it also disables when injuryOnsetDate is
empty, preventing createInjury.mutate from receiving an unset onset date.
Preserve the existing description, region, and pending-state checks.

---

Outside diff comments:
In `@docs/schema.dbml`:
- Around line 1661-1664: Remove the redundant subjective_check_in_user_date_idx
definition from the subjective_check_in schema and its migration, while
retaining the unique subjective_check_in_user_date_key index on (user_id, date).
- Around line 1483-1502: Update the injury_event definition to match the
migration and Drizzle schema: make severity nullable instead of not null and
document or enforce its valid 0–10 range using the established schema
convention. Leave the other columns and indexes unchanged.

In `@docs/schema.puml`:
- Around line 816-824: Update the body_region entity definition to mark
parent_id as a foreign key, then add the self-referential body_region
relationship using the diagram’s existing relationship notation.

In `@drizzle/meta/_journal.json`:
- Around line 552-563: Update the migration journal entries following
0067_personal_experiment_learning_loop in drizzle/meta/_journal.json so the
duplicate idx 79 is removed and every subsequent entry, including
0068_canonical_activity_types, has a unique sequential idx.

In `@packages/server/src/routers/activity.ts`:
- Around line 156-160: Wrap the ActivityRepository.setPerceivedExertion call in
setPerceivedExertion with withAccountErasureUserWriteFence, matching the pattern
used by other activity write mutations. Add a router test confirming the fence
is invoked for this mutation and prevents writes after account erasure begins.
🪄 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: cc31f943-ff0d-4704-8ae7-e88de86dceda

📥 Commits

Reviewing files that changed from the base of the PR and between 6359421 and 523a9aa.

📒 Files selected for processing (26)
  • cspell.json
  • docs/production-incident-baseline.md
  • docs/schema.dbml
  • docs/schema.puml
  • docs/superpowers/plans/2026-08-02-subjective-inputs.md
  • drizzle/0069_subjective_inputs.sql
  • drizzle/_views/01_v_activity.sql
  • drizzle/meta/_journal.json
  • packages/mobile/app/activity/[id].test.tsx
  • packages/mobile/components/ActivityPerceivedExertion.test.tsx
  • packages/mobile/components/SubjectiveTrackingPanel.test.tsx
  • packages/mobile/components/SubjectiveTrackingPanel.tsx
  • packages/server/src/mcp/route.test.ts
  • packages/server/src/mcp/tools.ts
  • packages/server/src/repositories/subjective-repository.test.ts
  • packages/server/src/repositories/subjective-repository.ts
  • packages/server/src/router.test.ts
  • packages/server/src/router.ts
  • packages/server/src/routers/activity-dedup.integration.test.ts
  • packages/server/src/routers/activity.test.ts
  • packages/server/src/routers/activity.ts
  • packages/server/src/routers/subjective.test.ts
  • packages/server/src/routers/subjective.ts
  • packages/web/src/components/SubjectiveTrackingPanel.test.tsx
  • packages/web/src/components/SubjectiveTrackingPanel.tsx
  • src/account-erasure/postgres-erasure.ts

Comment thread packages/mobile/components/SubjectiveTrackingPanel.test.tsx
Comment thread packages/mobile/components/SubjectiveTrackingPanel.tsx Outdated
Comment thread packages/mobile/components/SubjectiveTrackingPanel.tsx Outdated
Comment thread packages/server/src/repositories/subjective-repository.ts
Comment thread packages/server/src/routers/subjective.test.ts
Comment thread packages/web/src/components/SubjectiveTrackingPanel.test.tsx
Comment thread packages/web/src/components/SubjectiveTrackingPanel.tsx Outdated
Comment thread packages/web/src/components/SubjectiveTrackingPanel.tsx Outdated
@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository Owner rate limit exceeded. Free accounts are limited to 3 reviews per 4 hours across all repositories. Upgrade to a paid plan for unlimited reviews.

@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository Owner rate limit exceeded. Free accounts are limited to 3 reviews per 4 hours across all repositories. Upgrade to a paid plan for unlimited reviews.

@Asherlc
Asherlc merged commit 565fc9a into main Aug 3, 2026
106 checks passed
@Asherlc
Asherlc deleted the Asherlc/issue-2247-subjective-inputs branch August 3, 2026 15:03
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.

Subjective inputs: session RPE, body-map soreness, and an injury/niggle event timeline

2 participants