feat(subjective): add body-state inputs and session RPE - #2414
Conversation
|
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. |
|
🤖 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. |
📝 WalkthroughWalkthroughAdds 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. ChangesSubjective inputs and session RPE
Estimated code review effort: 5 (Critical) | ~120 minutes Assessment against linked issues
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
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. Comment |
Reviewer's GuideAdds 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 flowsequenceDiagram
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
Entity relationship diagram for subjective body-state schemaerDiagram
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
}
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Mobile PreviewScan to open on device:
To test on device:
|
|
Storybook previews for This comment updates automatically on each PR push. |
PR Summary by QodoAdd subjective body check-ins, injury events, and session RPE across server/web/mobile
AI Description
Diagram
High-Level Assessment
Files changed (42)
|
Code Review by Qodo
Context used✅ Compliance rules (platform):
209 rules✅ Skills:
fix-provider, write-tests, cloudflare 1.
|
|
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. |
There was a problem hiding this comment.
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
📒 Files selected for processing (45)
cspell.jsondocs/production-incident-baseline.mddocs/schema.dbmldocs/schema.mddocs/schema.pumldocs/superpowers/plans/2026-08-02-subjective-inputs.mddocs/superpowers/specs/2026-08-02-subjective-inputs-design.mddrizzle/0068_subjective_inputs.sqldrizzle/meta/_journal.jsonpackages/mobile/app/(tabs)/recovery.test.tsxpackages/mobile/app/(tabs)/recovery.tsxpackages/mobile/app/activity/[id].test.tsxpackages/mobile/app/activity/[id].tsxpackages/mobile/components/ActivityPerceivedExertion.tsxpackages/mobile/components/SubjectiveTrackingPanel.tsxpackages/server/src/mcp/route-lifecycle.test.tspackages/server/src/mcp/route.test.tspackages/server/src/mcp/route.tspackages/server/src/mcp/tools.tspackages/server/src/models/activity.test.tspackages/server/src/models/activity.tspackages/server/src/repositories/activity-repository.test.tspackages/server/src/repositories/activity-repository.tspackages/server/src/repositories/subjective-repository.test.tspackages/server/src/repositories/subjective-repository.tspackages/server/src/router.test.tspackages/server/src/router.tspackages/server/src/routers/activity.test.tspackages/server/src/routers/activity.tspackages/server/src/routers/subjective.test.tspackages/server/src/routers/subjective.tspackages/web/src/components/ActivityPerceivedExertion.stories.tsxpackages/web/src/components/ActivityPerceivedExertion.test.tsxpackages/web/src/components/ActivityPerceivedExertion.tsxpackages/web/src/components/SubjectiveTrackingPanel.stories.tsxpackages/web/src/components/SubjectiveTrackingPanel.test.tsxpackages/web/src/components/SubjectiveTrackingPanel.tsxpackages/web/src/pages/ActivityDetailPage.stories.tsxpackages/web/src/pages/ActivityDetailPage.test.tsxpackages/web/src/pages/ActivityDetailPage.tsxpackages/web/src/pages/TrackingPage.tsxsrc/db/schema/activity.tssrc/db/schema/events.tssrc/db/subjective-inputs.integration.test.tssrc/lib/cache.ts
|
🤖 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
|
🤖 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. |
|
🤖 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. |
There was a problem hiding this comment.
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
📒 Files selected for processing (25)
docs/superpowers/plans/2026-08-02-subjective-inputs.mddrizzle/0068_subjective_inputs.sqlpackages/mobile/components/ActivityPerceivedExertion.stories.tsxpackages/mobile/components/ActivityPerceivedExertion.test.tsxpackages/mobile/components/ActivityPerceivedExertion.tsxpackages/mobile/components/SubjectiveTrackingPanel.stories.tsxpackages/mobile/components/SubjectiveTrackingPanel.test.tsxpackages/mobile/components/SubjectiveTrackingPanel.tsxpackages/server/src/lib/date-schema.tspackages/server/src/mcp/route.test.tspackages/server/src/mcp/tools.tspackages/server/src/repositories/activity-repository.test.tspackages/server/src/repositories/activity-repository.tspackages/server/src/repositories/subjective-repository.test.tspackages/server/src/repositories/subjective-repository.tspackages/server/src/routers/activity.test.tspackages/server/src/routers/subjective.test.tspackages/server/src/routers/subjective.tspackages/web/src/components/ActivityPerceivedExertion.stories.tsxpackages/web/src/components/ActivityPerceivedExertion.test.tsxpackages/web/src/components/ActivityPerceivedExertion.tsxpackages/web/src/components/SubjectiveTrackingPanel.stories.tsxpackages/web/src/components/SubjectiveTrackingPanel.test.tsxpackages/web/src/components/SubjectiveTrackingPanel.tsxsrc/db/schema/events.ts
7e22407 to
f6de089
Compare
|
🤖 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. |
|
🤖 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. |
|
🤖 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. |
|
🤖 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. |
There was a problem hiding this comment.
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 winRevoke and replace
EXPO_TOKENbefore merging the remediation.The document states that
EXPO_TOKENremains present indev,prod, andstaging, 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
📒 Files selected for processing (46)
cspell.jsondocs/production-incident-baseline.mddocs/schema.dbmldocs/schema.mddocs/schema.pumldocs/superpowers/plans/2026-08-02-subjective-inputs.mddocs/superpowers/specs/2026-08-02-subjective-inputs-design.mddrizzle/0069_subjective_inputs.sqldrizzle/meta/_journal.jsonpackages/mobile/app/(tabs)/recovery.test.tsxpackages/mobile/app/(tabs)/recovery.tsxpackages/mobile/app/activity/[id].test.tsxpackages/mobile/app/activity/[id].tsxpackages/mobile/components/ActivityPerceivedExertion.stories.tsxpackages/mobile/components/ActivityPerceivedExertion.test.tsxpackages/mobile/components/ActivityPerceivedExertion.tsxpackages/mobile/components/SubjectiveTrackingPanel.stories.tsxpackages/mobile/components/SubjectiveTrackingPanel.test.tsxpackages/mobile/components/SubjectiveTrackingPanel.tsxpackages/server/src/lib/date-schema.tspackages/server/src/mcp/route-lifecycle.test.tspackages/server/src/mcp/route.test.tspackages/server/src/mcp/route.tspackages/server/src/mcp/tools.tspackages/server/src/models/activity.test.tspackages/server/src/models/activity.tspackages/server/src/repositories/activity-repository.test.tspackages/server/src/repositories/activity-repository.tspackages/server/src/repositories/subjective-repository.test.tspackages/server/src/repositories/subjective-repository.tspackages/server/src/router.test.tspackages/server/src/router.tspackages/server/src/routers/activity.test.tspackages/server/src/routers/activity.tspackages/server/src/routers/subjective.test.tspackages/server/src/routers/subjective.tspackages/web/src/components/ActivityPerceivedExertion.stories.tsxpackages/web/src/components/ActivityPerceivedExertion.test.tsxpackages/web/src/components/ActivityPerceivedExertion.tsxpackages/web/src/components/SubjectiveTrackingPanel.stories.tsxpackages/web/src/components/SubjectiveTrackingPanel.test.tsxpackages/web/src/components/SubjectiveTrackingPanel.tsxpackages/web/src/pages/ActivityDetailPage.stories.tsxpackages/web/src/pages/ActivityDetailPage.test.tsxpackages/web/src/pages/ActivityDetailPage.tsxpackages/web/src/pages/TrackingPage.tsx
|
🤖 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. |
|
🤖 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. |
|
🤖 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. |
|
🤖 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. |
|
🤖 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. |
|
🤖 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. |
|
🤖 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. |
|
LGTM! The changes are well-structured, correct, and well-tested:
🤖 Reviewed by codereviewbot.ai - Catch bugs before your team does. |
There was a problem hiding this comment.
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 winRedundant second index on
subjective_check_in (user_id, date).
subjective_check_in_user_date_keyis a unique index on(user_id, date).subjective_check_in_user_date_idxis 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_idxfrom 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 winNo change needed.
docs/schema.dbmlis stale; the migration and Drizzle schema defineinjury_event.severityas 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 winDocument the
body_region.parent_idself-reference.
parent_idis a real FK indrizzle/0069_subjective_inputs.sqlandsrc/db/schema/events.ts, so AGENTS.md rule 2 requires diagram/schema changes to match production constraints. Markparent_id : text <<FK>>and add thebody_region ||--o{ body_regionrelationship.🤖 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 winPut
setPerceivedExertionbehind the account-erasure write fence.This mutation writes user health data directly through
ActivityRepository. It bypasseswithAccountErasureUserWriteFence, unlike the other activity writes in this router. A write can then succeed after account erasure has started.Run
repo.setPerceivedExertion()insidewithAccountErasureUserWriteFence(). 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 winFix the duplicated
idxindrizzle/meta/_journal.json.
drizzle/meta/_journal.jsonhas duplicate entries atidx: 79for0067_personal_experiment_learning_loopand0068_canonical_activity_types. Drizzle metadata usesidxas the sequential migration index, so this also shifts subsequent entries out of line. Reindex the affected journal entries so eachidxis 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
📒 Files selected for processing (26)
cspell.jsondocs/production-incident-baseline.mddocs/schema.dbmldocs/schema.pumldocs/superpowers/plans/2026-08-02-subjective-inputs.mddrizzle/0069_subjective_inputs.sqldrizzle/_views/01_v_activity.sqldrizzle/meta/_journal.jsonpackages/mobile/app/activity/[id].test.tsxpackages/mobile/components/ActivityPerceivedExertion.test.tsxpackages/mobile/components/SubjectiveTrackingPanel.test.tsxpackages/mobile/components/SubjectiveTrackingPanel.tsxpackages/server/src/mcp/route.test.tspackages/server/src/mcp/tools.tspackages/server/src/repositories/subjective-repository.test.tspackages/server/src/repositories/subjective-repository.tspackages/server/src/router.test.tspackages/server/src/router.tspackages/server/src/routers/activity-dedup.integration.test.tspackages/server/src/routers/activity.test.tspackages/server/src/routers/activity.tspackages/server/src/routers/subjective.test.tspackages/server/src/routers/subjective.tspackages/web/src/components/SubjectiveTrackingPanel.test.tsxpackages/web/src/components/SubjectiveTrackingPanel.tsxsrc/account-erasure/postgres-erasure.ts
|
🤖 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. |
|
🤖 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. |
Summary
get_subjective_timelineMCP read toolValidation
pnpm exec vitest run --project unit packages/server/src/repositories/subjective-repository.test.ts packages/server/src/routers/subjective.test.ts --retry=0pnpm exec vitest run --project unit packages/server/src/repositories/activity-repository.test.ts packages/server/src/routers/activity.test.ts --retry=0pnpm exec vitest run packages/web/src/components/ActivityPerceivedExertion.test.tsx packages/web/src/components/SubjectiveTrackingPanel.test.tsx --retry=0The 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 indocs/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:
Enhancements:
Documentation:
Tests:
Chores:
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_inputsto validate theperceived_exertionrange, create new tables/constraints, and seedfitness.body_region.New Features
fitness.activity.perceived_exertion(0–10); newfitness.body_region(seeded),fitness.subjective_check_in,fitness.subjective_symptom,fitness.injury_event.subjectiverouter (regions,checkIn/saveCheckIn,injuriesCRUD,timeline) andactivity.setPerceivedExertion; all user‑scoped with cache invalidation forsubjectiveandactivity.get_subjective_timelineunderhealth:read.SubjectiveTrackingPanelon Tracking/Recovery andActivityPerceivedExertionon Activity Detail; mutation errors reported to Sentry; raw‑input‑only storage with explicit “not logged” vs “all clear.”Bug Fixes
perceived_exertioncolumn; validate range; usebigintwhere needed; enforce non‑emptybody_regionIDs/labels.perceived_exertionthroughv_activityand exposeperceivedExertionon Activity Detail.transactionto 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.fitness.body_regionas a shared system table so user erasure preserves region references.Written for commit 5689b46. Summary will update on new commits.
Summary by CodeRabbit