Skip to content

[climbing] Add grade system switcher - #2484

Merged
Asherlc merged 52 commits into
mainfrom
add-climbing-grade-switcher
Aug 11, 2026
Merged

Asherlc merged 52 commits into
mainfrom
add-climbing-grade-switcher

Conversation

@Asherlc

@Asherlc Asherlc commented Aug 11, 2026

Copy link
Copy Markdown
Owner

What changed

  • adds persisted boulder and route grade-system preferences, including Font, French, UIAA, Ewbank, Saxon, Norwegian, and Brazilian Crux
  • uses OpenBeta Sandbag for canonical validation, conversion, and ordering
  • converts dashboard/activity grade displays server-side while preserving raw stored grades
  • updates web and mobile settings plus manual climbing logging
  • adds the Postgres enum migration and focused regression coverage

Why

Climbers can now view and log boulder and route grades in their preferred relevant grading systems, analogous to the existing km/miles setting.

Validation

  • pnpm lint
  • pnpm typecheck
  • focused unit tests for training, server, web, and mobile climbing flows

Summary by Sourcery

Add Sandbag-backed multi-system climbing grade preferences, Hangboarding analytics and detail surfaces, and body-fat history visuals while tightening processing status alerts, Apple Health interval persistence, and telemetry/noise handling across server, web, and mobile.

New Features:

  • Introduce user-specific climbing grade system preferences for boulders and routes and apply them across server, web, and mobile climbing views
  • Add Hangboarding analytics and detail APIs with corresponding web and mobile UI for hangboard sessions
  • Expose body-fat percentage history and charts on web Body and mobile Recovery tabs

Bug Fixes:

  • Normalize empty cycling modality values to treat them as unknown instead of invalid
  • Ensure processing status alerts select the newest relevant failure event and handle missing failure timestamps
  • Prevent transient network transport errors from being reported to Sentry in mobile query-client telemetry

Enhancements:

  • Replace bespoke climbing grade parsing with a Sandbag-backed multi-system grade validation, conversion, and ordering layer
  • Refine activity visibility filtering to operate directly on canonical activities instead of expanding the recursive visibility view
  • Group provider-processing failures into single, clearer alerts with provider- and dataset-specific copy

Build:

  • Add @openbeta/sandbag dependency to the training workspace and update pnpm lock and audit ignores accordingly

CI:

  • Tighten Apple Health workout upsert tests to require transactional databases and validate Hang Ten interval replacement behavior

Documentation:

  • Document recent incident baselines and implementation plans for Hangboarding, climbing grade systems, Dependabot/CI interactions, processing alerts, and body-fat charts

Tests:

  • Add focused unit and integration tests for Hangboarding repositories and routers, climbing grade-system validation, processing status ordering and alerts, Apple Health Hang Ten parsing and persistence, and mobile/web dashboard contracts

Summary by cubic

Adds a grade-system switcher with persisted boulder and route preferences. Validates, converts, and orders grades server-side using @openbeta/sandbag, so all climbing views show grades in the user’s chosen systems.

  • New Features

    • Persist climbingGradeSystems with defaults and schema validation; resolve via resolveClimbingGradePreference.
    • Enforce climb-type/system compatibility and reject invalid grades; normalize gradeSortValue with Sandbag scores.
    • Use @dofek/training/climbing-grades for validation, conversion, grade lists, and labels; load preference server-side for conversions.
    • Web: add ClimbingGradeSystemToggle (with Storybook) and apply preference in manual logging. Mobile: add ClimbingGradeSystemSettings and apply preference in logging; displays use server-converted grades. Invalidate settings and climbing caches after changes.
  • Dependencies

    • Add @openbeta/sandbag@0.0.55 and zod@4.4.3 to @dofek/training.
    • Postgres enum migration: extend fitness.climbing_grade_system with font, french, uiaa, ewbank, saxon, norwegian, brazilian_crux.

Written for commit 3f44f08. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added support for multiple bouldering and route grading systems, including Font, French, UIAA, Ewbank, Saxon, Norwegian, and Brazilian.
    • Added independent climbing-grade preferences for bouldering and routes in web and mobile settings.
    • Grade selectors now show valid options for the selected system and climb type.
    • Climbing data is converted, sorted, and displayed according to saved preferences.
  • Bug Fixes
    • Improved grade validation and handling of invalid or unsupported grades.
  • Documentation
    • Added climbing-grade design and implementation documentation.

Asherlc added 30 commits August 7, 2026 12:07
* chore: configure Paseo workspace workflows

* fix: allow alertable in spell check

* fix: scope mutation checks to pull request changes

* test: align mobile tRPC mocks

* test: align web tRPC mocks

* fix: sync mobile Expo dependencies

* docs: record CI incident findings
* docs: specify body fat percentage chart

* docs: plan body fat percentage chart

* feat: expose mobile body fat history

* feat: add web body fat chart

* feat: show body fat chart on web body page

* feat: show body fat trend on mobile recovery

* test: cover mobile recovery body fat response

* test: type recovery body fat fixture

* fix: close body fat chart review findings

* fix: preserve body fat chart layout

* fix: restore CI checks

* test: strengthen processing mutations
@codereviewbot-ai

codereviewbot-ai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Pull Request Review Summary

Overall, this is a clean and well-structured implementation integrating @openbeta/sandbag for international climbing grade systems across server and mobile/web clients.

Summary of Findings & Issues:

  1. ClimbingRepository.getSessionSummaries - Missing location name when first entry is null:
    In getSessionSummaries, existing.locationName was set only upon initializing the summary object. If the first climbing entry in a session had location_name: null, existing.locationName remained null even if subsequent entries in the same session had a non-null location.
  2. ClimbingRepository.getActivityEntries - Comparator returning NaN:
    Setting fallback gradeSortValue to -Infinity leads to (-Infinity) - (-Infinity) = NaN during .sort(), causing non-deterministic sorting behavior in V8 engine. Replacing -Infinity with a finite negative number resolves this.
  3. Duplicated resolveGradePreference logic:
    resolveGradePreference is duplicated across packages/mobile/app/climbing-log.tsx and packages/mobile/app/settings.tsx. Consider consolidating this helper function into @dofek/training/climbing-grades.

Inline comments with suggested fixes have been posted on the diff.


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

Comment thread packages/server/src/repositories/climbing-repository.ts
Comment thread packages/server/src/repositories/climbing-repository.ts Outdated
@codereviewbot-ai

codereviewbot-ai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Summary

Overall, this is a well-structured PR integrating @openbeta/sandbag for multi-system climbing grade conversions and user preferences across bouldering and route grade systems.

Key Highlights & Observations:

  1. Sandbag Integration (@dofek/training/climbing-grades.ts): Replaced custom SQL regex grade sorting with Sandbag scale scores. Flagged a potential TypeError in gradeSortValue when getScore() returns undefined.
  2. Repository Ordering (ClimbingRepository): JS-based display grade mapping and sorting replaced SQL CASE statements. Added a recommendation for a secondary tie-breaker when sorting activity entries to maintain deterministic output.
  3. Mobile & Settings UX: Grade preference schema and validation are handled nicely on server and mobile. Noted a minor nit on duplicated resolveGradePreference logic between mobile screens.

🤖 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: 19

Caution

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

⚠️ Outside diff range comments (2)
packages/server/src/repositories/climbing-repository.test.ts (1)

132-136: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for a non-default gradePreference.

makeRepository always constructs ClimbingRepository with the default preference, so every assertion in this file exercises the identity path where the source system equals the display system. The conversion branch of #displayGrade, the merge of two source grades onto one display grade in getVolumeByGrade, and the unparseable-grade fallback stay untested. AGENTS.md requires tests for changed behavior.

💚 Proposed helper change
-  function makeRepository(rows: Record<string, unknown>[] = []) {
+  function makeRepository(
+    rows: Record<string, unknown>[] = [],
+    gradePreference?: ClimbingGradePreference,
+  ) {
     const execute = vi.fn().mockResolvedValue(rows);
-    const repo = new ClimbingRepository(executeDb(execute), "user-1", "America/Los_Angeles");
+    const repo = new ClimbingRepository(
+      executeDb(execute),
+      "user-1",
+      "America/Los_Angeles",
+      undefined,
+      gradePreference,
+    );
     return { repo, execute };
   }

Add at least one case with { boulder: "font", route: "french" } that asserts the converted display grade and system.

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

In `@packages/server/src/repositories/climbing-repository.test.ts` around lines
132 - 136, Update makeRepository to accept an optional gradePreference override
while preserving the current default, then add coverage using { boulder: "font",
route: "french" } that verifies converted display grades and their systems.
Include assertions covering getVolumeByGrade’s merging of source grades and the
unparseable-grade fallback.

Source: Path instructions

packages/server/src/contracts/mobile-dashboard-contracts.ts (1)

352-389: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Validate gradeSystem against climbType.

The schemas accept font for a route and french for a boulder. This contradicts the boulder-only and route-only systems in packages/server/src/climbing-grade-preferences.ts Lines 12-15.

Use a discriminated union or a refinement that rejects invalid climbType and gradeSystem pairs. Add output-contract tests for both invalid pair types.

As per coding guidelines, “Use Zod to parse data crossing runtime boundaries.”

🤖 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/contracts/mobile-dashboard-contracts.ts` around lines 352
- 389, Update the gradeProgression and volumeByGrade schemas to reject invalid
climbType/gradeSystem combinations, using a discriminated union or Zod
refinement aligned with the boulder-only and route-only systems defined in
climbing-grade-preferences.ts. Preserve all existing field validation, and add
output-contract tests covering both a route with font and a boulder with french.

Source: Coding guidelines

🤖 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-11-climbing-grade-systems.md`:
- Around line 40-41: Update the expected displayGrade in the
convertClimbingGrade example to "6a+/6b+" so it matches the regression test in
climbing-grades.test.ts and avoids contradictory expectations.
- Around line 72-77: Update the plan’s commit and push steps, including Step 5
and the referenced repeated sections, to require explicit user or workflow
approval before performing those operations; otherwise leave commit and push
execution to the operator. Do not instruct the agent to switch branches, rebase,
commit, or push unconditionally.

In `@drizzle/0074_climbing_grade_systems.sql`:
- Around line 1-7: Update the migration that defines
fitness.climbing_grade_system so new values are inserted in the same order as
declared in src/db/schema/enums.ts, placing font after v_scale rather than
appending it after yds; use PostgreSQL enum insertion positioning for the
remaining values as needed to preserve the full declared order.

In `@packages/mobile/components/ClimbingAttemptLog.tsx`:
- Around line 98-102: Update ClimbingAttemptLog’s grade field to use a
selectable control populated from gradeOptionsForSystem(gradeSystem), matching
the web implementation instead of accepting arbitrary text. Clear grade whenever
the climb type changes, and add coverage that switches climb type then submits a
valid route grade, ensuring the mobile behavior remains equivalent to web.

In `@packages/server/src/repositories/climbing-repository.ts`:
- Around line 332-345: Update the summary aggregation around the summaries Map
so locationName is populated from a later non-null row when the existing summary
value is null. Preserve the first non-null location value and the existing
behavior for activities whose location remains null.
- Around line 83-94: Use the shared climbing grade definitions instead of
duplicating the nine-value list: in
packages/server/src/repositories/climbing-repository.ts:83-94, import and pass
CLIMBING_GRADE_SYSTEMS to z.enum for gradeSystemSchema; in
packages/server/src/routers/climbing.test.ts:117-126, import ClimbingGradeSystem
and type the optional gradeSystem field as gradeSystem?: ClimbingGradeSystem,
removing the inline union.
- Around line 413-427: Update the fallback display object in the mapping before
the sort to use a finite gradeSortValue sentinel instead of -Infinity, or adjust
the comparator to explicitly compare values without subtracting two non-finite
values. Keep failed-display rows consistently ordered and preserve descending
gradeSortValue ordering for valid grades.

In `@packages/server/src/routers/climbing.test.ts`:
- Around line 639-644: Update the test around
validFont.caller.logClimbingSession to mock or spy on validFont.execute and
assert it was called, rather than asserting an INTERNAL_SERVER_ERROR rejection.
Preserve the Font “6a” boulder input and use the execute-call assertion to
verify it passed input validation and reached the repository.
- Around line 117-126: Replace the inline gradeSystem union in the relevant test
type with the exported ClimbingGradeSystem type from
`@dofek/training/climbing-grades`, adding the necessary import and preserving the
optional property.
- Around line 129-135: Update makeClimbingSessionInput so its fallback grade is
selected from gradeSystem, using valid defaults for each supported bouldering
and route grading system instead of only climbType. Add test cases for
non-default systems such as font and french that omit grade, ensuring the
generated input uses a valid matching-system grade.

In `@packages/server/src/routers/climbing.ts`:
- Around line 208-266: Extract the repeated loadClimbingGradePreference and
ClimbingRepository construction into a shared helper near the router procedures.
Update the affected procedures, including gradeProgression, volumeByGrade, and
sessionSummary, to call that helper while preserving their existing query
methods and mapping behavior.
- Around line 205-219: Update the grade-preference change handling in
settings.set to invalidate the climbing query caches activityEntries,
gradeProgression, volumeByGrade, and sessionSummary whenever the preference
changes, while preserving the existing settings. invalidation.

In `@packages/server/src/routers/settings.ts`:
- Around line 28-31: Update the settings mutation handling for
CLIMBING_GRADE_PREFERENCE_SETTINGS_KEY to invalidate both
`${ctx.userId}:climbing.` and `${ctx.userId}:mobileDashboard.training` after the
preference changes, using the existing cache invalidation mechanism and
preserving other settings behavior.

In `@packages/training/src/climbing-grades.test.ts`:
- Around line 12-42: Extend the climbing-grade tests around
gradeSystemsForClimbType and related helpers with table-driven cases covering
French, UIAA, Ewbank, Saxon, Norwegian, and Brazilian Crux. For each route
system, assert a representative valid grade is included in
gradeOptionsForSystem, accepted by isValidClimbingGrade, ordered correctly, and
converted to the expected display result; retain the existing Font coverage and
use the established Sandbag mappings.

In `@packages/training/src/climbing-grades.ts`:
- Around line 56-66: Update the UIAA entry in SYSTEM_LABELS, which feeds
gradeSystemLabel(), to use a readable expanded label such as “International
Climbing and Mountaineering Federation (UIAA)” while leaving the other
grade-system labels unchanged.

In `@packages/web/src/components/ClimbingGradeSystemToggle.stories.tsx`:
- Around line 57-59: Expand the ClimbingGradeSystemToggle stories beyond
FontAndFrench to cover the default preference, loading, no stored preference,
and read/write error states. Use the existing ClimbingGradeSystemStoryFrame and
available preference-state controls or fixtures, preserving FontAndFrench as the
meaningful populated variant.

In `@packages/web/src/components/ClimbingGradeSystemToggle.tsx`:
- Around line 59-73: Prevent overlapping preference writes in setPreference by
disabling both selectors while mutation.isPending, or otherwise queueing writes
so they execute serially. Ensure rollback in the mutation onError handler cannot
restore an older previous value over a newer preference, and preserve optimistic
updates and settled cache invalidation.
- Around line 24-42: Replace the duplicated preferenceFrom validation with one
exported Zod schema or parser from the training package’s
ClimbingGradePreference module, adding the training package dependency if
needed. Update the settings-query boundaries in
packages/web/src/components/ClimbingGradeSystemToggle.tsx (24-42),
packages/web/src/routes/training/climbing.tsx (38-56),
packages/mobile/app/climbing-log.tsx (94-112), and
packages/mobile/app/settings.tsx (997-1015) to use that shared parser while
preserving the DEFAULT_CLIMBING_GRADE_PREFERENCE fallback behavior.
- Around line 45-50: Update ClimbingGradeSystemToggle.tsx:45-50,
training/climbing.tsx:124-125, and both settings.tsx ranges 255-279 and 527-581
to distinguish loading, error, and successful absence from preference
resolution; preserve cached data during background refetches, disable controls
and writes until settings.get succeeds, and use QueryStatePanel for applicable
web sections with explicit loading/error UI. Update climbing-log.tsx:22-24
likewise so it never derives or writes DEFAULT_CLIMBING_GRADE_PREFERENCE from an
unresolved query, while retaining the default only after a successful query with
no saved preference.

---

Outside diff comments:
In `@packages/server/src/contracts/mobile-dashboard-contracts.ts`:
- Around line 352-389: Update the gradeProgression and volumeByGrade schemas to
reject invalid climbType/gradeSystem combinations, using a discriminated union
or Zod refinement aligned with the boulder-only and route-only systems defined
in climbing-grade-preferences.ts. Preserve all existing field validation, and
add output-contract tests covering both a route with font and a boulder with
french.

In `@packages/server/src/repositories/climbing-repository.test.ts`:
- Around line 132-136: Update makeRepository to accept an optional
gradePreference override while preserving the current default, then add coverage
using { boulder: "font", route: "french" } that verifies converted display
grades and their systems. Include assertions covering getVolumeByGrade’s merging
of source grades and the unparseable-grade fallback.
🪄 Autofix

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: dfec9b0b-d23a-4a42-9a58-2f5cc65f92ae

📥 Commits

Reviewing files that changed from the base of the PR and between aa932b8 and 78a0abf.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (35)
  • cspell.json
  • docs/schema.dbml
  • docs/schema.puml
  • docs/superpowers/plans/2026-08-11-climbing-grade-systems.md
  • docs/superpowers/specs/2026-08-11-climbing-grade-systems-design.md
  • drizzle/0074_climbing_grade_systems.sql
  • drizzle/meta/_journal.json
  • packages/mobile/app/climbing-log.tsx
  • packages/mobile/app/settings.tsx
  • packages/mobile/components/ClimbingAttemptLog.test.tsx
  • packages/mobile/components/ClimbingAttemptLog.tsx
  • packages/server/src/climbing-grade-preferences.test.ts
  • packages/server/src/climbing-grade-preferences.ts
  • packages/server/src/contracts/mobile-dashboard-contracts.ts
  • packages/server/src/repositories/climbing-repository.test.ts
  • packages/server/src/repositories/climbing-repository.ts
  • packages/server/src/repositories/climbing-training-log-repository.ts
  • packages/server/src/routers/climbing.test.ts
  • packages/server/src/routers/climbing.ts
  • packages/server/src/routers/settings.ts
  • packages/server/src/services/mobile-training-tab.ts
  • packages/training/README.md
  • packages/training/package.json
  • packages/training/src/climbing-grades.test.ts
  • packages/training/src/climbing-grades.ts
  • packages/training/src/openbeta-sandbag.d.ts
  • packages/web/src/components/ClimbingAttemptLog.test.tsx
  • packages/web/src/components/ClimbingAttemptLog.tsx
  • packages/web/src/components/ClimbingGradeSystemToggle.stories.tsx
  • packages/web/src/components/ClimbingGradeSystemToggle.tsx
  • packages/web/src/pages/SettingsPage.tsx
  • packages/web/src/routes/training/climbing.test.tsx
  • packages/web/src/routes/training/climbing.tsx
  • src/db/drizzle-schema.test.ts
  • src/db/schema/enums.ts

Comment thread docs/superpowers/plans/2026-08-11-climbing-grade-systems.md Outdated
Comment thread docs/superpowers/plans/2026-08-11-climbing-grade-systems.md
Comment thread drizzle/0074_climbing_grade_systems.sql Outdated
Comment thread packages/mobile/components/ClimbingAttemptLog.tsx Outdated
Comment thread packages/server/src/repositories/climbing-repository.ts Outdated
Comment thread packages/training/src/climbing-grades.ts
Comment thread packages/web/src/components/ClimbingGradeSystemToggle.stories.tsx
Comment thread packages/web/src/components/ClimbingGradeSystemToggle.tsx Outdated
Comment thread packages/web/src/components/ClimbingGradeSystemToggle.tsx Outdated
Comment thread packages/web/src/components/ClimbingGradeSystemToggle.tsx
Comment thread packages/training/src/climbing-grades.ts Outdated
Comment thread packages/server/src/repositories/climbing-repository.ts Outdated
Comment thread packages/mobile/app/climbing-log.tsx Outdated
@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.

@Asherlc Asherlc changed the title Add climbing grade system preferences [climbing] Add grade system switcher Aug 11, 2026
@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.

@Asherlc
Asherlc enabled auto-merge (squash) August 11, 2026 21:09
@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: 3

Caution

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

⚠️ Outside diff range comments (3)
docs/superpowers/plans/2026-08-11-climbing-grade-systems.md (2)

24-29: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep the Task 1 file list and commit complete.

The stack lists cspell.json in this grade-domain cohort, but Lines 24-29 and 72-76 omit it. If Task 1 updates spelling entries for the new grade-system names, add cspell.json to both lists. Otherwise, state that no cspell change is required.

Also applies to: 72-76

🤖 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/superpowers/plans/2026-08-11-climbing-grade-systems.md` around lines 24
- 29, Update the Task 1 file lists in the climbing-grade plan to include
cspell.json wherever spelling entries for the new grade-system names are
changed; if no spelling update is needed, explicitly state that no cspell change
is required in both lists.

33-43: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add regression coverage for sortValue and grade ordering.

The interface promises sortValue, and Line 64 defines it from Sandbag score ranges. The planned test checks only displaySystem and displayGrade. Assert the expected sortValue and compare at least two valid grades, including a boundary case, so incorrect range averaging cannot pass while display conversion still succeeds.

As per path instructions: **/*.{test,integration.test}.{ts,tsx} requires tests for changed behavior.

Also applies to: 64-64

🤖 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/superpowers/plans/2026-08-11-climbing-grade-systems.md` around lines 33
- 43, Expand the Step 1 domain tests around convertClimbingGrade to assert the
expected sortValue for converted grades and verify ordering by comparing at
least two valid grades, including a Sandbag range boundary case. Keep the
existing display-system, display-grade, and cross-discipline assertions, and
ensure the tests detect incorrect range averaging.

Source: Path instructions

packages/mobile/app/settings.tsx (1)

529-529: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Split SettingsScreen before merge.

This addition brings packages/mobile/app/settings.tsx to 1,003 lines. Extract a focused settings section and its handlers so the source module stays within the limit. As per coding guidelines: “No TypeScript file may exceed 1000 lines; split large files into focused modules.”

🤖 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/mobile/app/settings.tsx` at line 529, Split SettingsScreen into
focused modules before merging, extracting the goals-models settings section and
its associated handlers from settings.tsx while preserving existing behavior and
wiring. Keep the resulting TypeScript files under the 1,000-line limit, and
update imports/exports so SettingsScreen continues rendering the extracted
section correctly.

Source: Coding guidelines

🤖 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/app/settings.tsx`:
- Around line 541-588: Update the grade-system control containers around the
BOULDER_GRADE_SYSTEMS and ROUTE_GRADE_SYSTEMS maps to use a wrapping layout or
vertical list instead of the single-row styles.unitRow. Ensure styles.unitButton
has a usable minimum width and does not shrink each choice to an unusable size
on narrow screens, while preserving the existing selection and press behavior.

In `@packages/training/src/climbing-grades.ts`:
- Around line 135-148: Define a Zod schema in the shared climbing-grades module
for the boulder and route preference values, using the existing grade-system
validators; update resolveClimbingGradePreference() to validate value via
safeParse() and return the parsed data on success. Return
DEFAULT_CLIMBING_GRADE_PREFERENCE when parsing fails, replacing the manual
unknown narrowing while preserving the current fallback behavior.

In `@packages/web/src/components/ClimbingGradeSystemToggle.stories.tsx`:
- Around line 71-85: The ClimbingGradeSystemToggle stories are missing coverage
for the pending-save state. Add a Saving story using
ClimbingGradeSystemStoryFrame that resolves settings.get while keeping
settings.set pending, then verify that selecting a grade disables both selectors
while the save is in progress.

---

Outside diff comments:
In `@docs/superpowers/plans/2026-08-11-climbing-grade-systems.md`:
- Around line 24-29: Update the Task 1 file lists in the climbing-grade plan to
include cspell.json wherever spelling entries for the new grade-system names are
changed; if no spelling update is needed, explicitly state that no cspell change
is required in both lists.
- Around line 33-43: Expand the Step 1 domain tests around convertClimbingGrade
to assert the expected sortValue for converted grades and verify ordering by
comparing at least two valid grades, including a Sandbag range boundary case.
Keep the existing display-system, display-grade, and cross-discipline
assertions, and ensure the tests detect incorrect range averaging.

In `@packages/mobile/app/settings.tsx`:
- Line 529: Split SettingsScreen into focused modules before merging, extracting
the goals-models settings section and its associated handlers from settings.tsx
while preserving existing behavior and wiring. Keep the resulting TypeScript
files under the 1,000-line limit, and update imports/exports so SettingsScreen
continues rendering the extracted section correctly.
🪄 Autofix

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: cc98da6d-dbbc-44e3-81a4-81c24f777182

📥 Commits

Reviewing files that changed from the base of the PR and between 78a0abf and 062630a.

📒 Files selected for processing (21)
  • docs/superpowers/plans/2026-08-11-climbing-grade-systems.md
  • drizzle/0074_climbing_grade_systems.sql
  • packages/mobile/app/climbing-log.tsx
  • packages/mobile/app/settings.styles.ts
  • packages/mobile/app/settings.tsx
  • packages/mobile/components/ClimbingAttemptLog.test.tsx
  • packages/mobile/components/ClimbingAttemptLog.tsx
  • packages/server/src/contracts/mobile-dashboard-contracts.test.ts
  • packages/server/src/contracts/mobile-dashboard-contracts.ts
  • packages/server/src/repositories/climbing-repository.test.ts
  • packages/server/src/repositories/climbing-repository.ts
  • packages/server/src/routers/climbing.integration.test.ts
  • packages/server/src/routers/climbing.test.ts
  • packages/server/src/routers/climbing.ts
  • packages/server/src/routers/recovery-settings-sleep-need-sport-settings.test.ts
  • packages/server/src/routers/settings.ts
  • packages/training/src/climbing-grades.test.ts
  • packages/training/src/climbing-grades.ts
  • packages/web/src/components/ClimbingGradeSystemToggle.stories.tsx
  • packages/web/src/components/ClimbingGradeSystemToggle.tsx
  • packages/web/src/routes/training/climbing.tsx

Comment thread packages/mobile/app/settings.tsx Outdated
Comment thread packages/training/src/climbing-grades.ts Outdated
Comment thread packages/web/src/components/ClimbingGradeSystemToggle.stories.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.

Asherlc commented Aug 11, 2026

Copy link
Copy Markdown
Owner Author

Addressed the remaining CodeRabbit PR-level feedback in b8224ce.

  • Documented the cspell.json change and explicit Sandbag score/order assertions in the implementation plan.
  • Added conversion, bucket, and invalid-grade fallback coverage for ClimbingRepository.
  • Extracted the mobile grade-system settings section into a focused component, keeping settings.tsx below the 1,000-line limit.
  • Added strict Zod validation for persisted grade preferences and a saving-state Storybook interaction.

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

@Asherlc
Asherlc merged commit d35f347 into main Aug 11, 2026
107 checks passed
@Asherlc
Asherlc deleted the add-climbing-grade-switcher branch August 11, 2026 22:30
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.

3 participants