Skip to content

Feat/v0.9.0 enhancement sweep - #406

Merged
9thLevelSoftware merged 67 commits into
mainfrom
feat/v0.9.0-enhancement-sweep
May 4, 2026
Merged

Feat/v0.9.0 enhancement sweep#406
9thLevelSoftware merged 67 commits into
mainfrom
feat/v0.9.0-enhancement-sweep

Conversation

@9thLevelSoftware

@9thLevelSoftware 9thLevelSoftware commented May 4, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Enhanced weight adjustment controls with per-cable limits, improved increment handling, and UI clamping previews.
  • Bug Fixes

    • Weight clamping now enforces per-cable maximums consistently.
    • Exercise sync improved to better preserve/resolve existing exercise identifiers.
  • Changes

    • Haptic/audio feedback effect removed.
    • Cable telemetry labeling changed from left/right to A/B.
    • Weight display strings localized (e.g., “kg per cable”, total for two cables).

9thLevelSoftware and others added 30 commits April 21, 2026 13:15
Phase 37: Foundation (#323)
Wave: 1
Requirements: FOUND-01

Create WeightDisplayFormatter utility for cable-aware total weight display.
Update primary surfaces (WorkoutHud, HistoryTab, SetSummaryCard) to show
total weight instead of per-cable values. 14 unit tests pass covering
all cable/unit/edge case combinations.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ssion Tests

Phase 37: Foundation (#323)
Wave: 2
Requirements: FOUND-01

Migrate secondary display surfaces (DashboardComponents, AnalyticsScreen,
ExerciseDetailScreen, ExercisesTab, HomeScreen, ActiveWorkoutScreen) to
WeightDisplayFormatter. Add 9 guard tests protecting sync/health/BLE paths
from double-multiplication and 30 regression tests covering all cable/unit
combinations. 53 total weight display tests pass.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 37: Foundation (#323)
Fixed 8 issues: tautological guard tests rewritten with source scanning,
CompletedSetsSection cable-aware display, CountdownCard total weight,
ModeConfirmationScreen unit conversion, weak test assertions replaced
with exact values, negative/unusual cableCount edge case tests added.
Unresolved: PersonalRecord cableCount data model gap (requires schema migration).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…idMain

Phase 37: Foundation (#323)
Fixed guard test for HealthIntegration.android.kt to scan androidMain
source set where the actual file lives, not just commonMain. Guard now
checks both source sets matching the iOS guard pattern.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 38: Weight-Dependent Features
Wave: 1
Requirements: WEIGHT-01

Wire pre-built weight increment preference into all weight control surfaces.
Settings UI picker for 0.1–5.0 lb/kg increments. WeightAdjustmentControls,
CompactWeightAdjustment, WeightPickerDialog, WeightStepper all use configured
increment. Slider capped at 200 steps. Preset % buttons round to machine
increment. Routine.kt duplicate roundToIncrement deprecated.
18 unit tests covering increment wiring, conversion, and reset behavior.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 38: Weight-Dependent Features
Wave: 2
Requirements: WEIGHT-02

BulkWeightAdjustDialog with percentage and absolute modes, preview section
showing current → new weight per exercise. PR-scaled exercises skipped with
indicator. All weights clamped [0, MAX_WEIGHT_KG] and rounded to 0.5kg
machine increment. Per-set weights (setWeightsPerCableKg) also adjusted.
Integrated into RoutineEditorScreen overflow menu (all exercises) and
SelectionActionBar (selected exercises). 25 unit tests for pure logic.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 38: Weight-Dependent Features
Wave: 3
Requirements: WEIGHT-01, WEIGHT-02

9 structural boundary guards verifying BulkWeightAdjust and weight increment
preferences don't leak into BLE, sync, or health integration layers. Follows
Phase 37 WeightDisplayGuardTest pattern — scans real source directories.
1 edge case added to BulkWeightAdjustTest (zero-weight percentage).
114 total weight-related tests across all phases, zero failures.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
All plans executed. 3/3 passed.
Overall progress: 5/21 (24%)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 38: Weight-Dependent Features
Fixed 4 issues: duplicate constants consolidated to Constants/UnitConverter,
formatWeight toInt() truncation replaced with formatDecimal, lb-to-kg
conversion tests added, hardcoded contentDescription replaced with string
resource (5 locales). WeightStepper rounding responsibility documented.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Review passed after 1 cycle.
4 warning(s) fixed, 0 blocker(s).
Reviewers: testing-qa-verification-specialist, engineering-senior-developer

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 39: Routine Cluster
Wave: 1
Requirements: ROUTINE-01

- Extract normalizeRoutine() to RoutineUtils.kt (top-level, testable)
- Add reorderExercisesInSuperset() utility function
- Wire ReorderableColumn inside superset containers for nested drag-and-drop
- Add drag handles on exercises within supersets
- preserveIntraSupersetOrder flag prevents normalizeRoutine() clobber

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 39: Routine Cluster
Wave: 1
Requirements: ROUTINE-02

- Add RoutineGroup domain model + groupId on Routine
- SQLDelight migration 27: RoutineGroup table, groupId FK with ON DELETE SET NULL
- Fix pre-existing migration 26 gap in MigrationStatements.kt
- RoutineGroup CRUD in SqlDelightWorkoutRepository
- RoutineGroupHeader + MoveToGroupDialog composables
- Transform RoutinesTab from flat list to grouped collapsible sections
- Backup/restore includes RoutineGroup data (version 3)
- Wire ViewModel + RoutineFlowManager for group operations

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2/2 plans completed
Progress: 7/21 (33%)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 39: Routine Cluster
Wave: 2
Requirements: ROUTINE-01, ROUTINE-02

- SupersetReorderTest: 7 tests for intra-superset reorder ordering
- RoutineGroupTest: 10 tests for group CRUD and routine-group associations
- RoutineRegressionGuardTest: 5 regression guards for existing routine ops
- Fix SchemaManifest: add RoutineGroup table, groupId column, index
- Fix SchemaParityTest: bump CURRENT_VERSION 26->27

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
All plans executed. 3/3 passed.
Overall progress: 8/21 (38%)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 39: Routine Cluster (#365, #307)
Fixed 8 issues found by 4-agent review panel:

BLOCKER: groupId missing from insertRoutine/upsertRoutine SQL queries
  - VitruvianDatabase.sq: added groupId to insert + upsert queries
  - SqlDelightWorkoutRepository: pass groupId in saveRoutine
  - DataBackupManager: pass groupId in backup import
  - SqlDelightSyncRepository: preserve groupId in 3 sync upsert paths
  - SchemaManifest: add groupId to Routine table definition

BLOCKER: "New Group..." flow creates group but doesn't move routines
  - RoutinesTab: wire pendingMoveRoutineIds + LaunchedEffect chain

WARNING: groupRepo unsafe cast to SqlDelightWorkoutRepository
  - RoutineFlowManager: safe cast with null-safe calls

WARNING: Group collector lacks retry logic
  - RoutineFlowManager: 3-retry exponential backoff matching Collector #1

WARNING: Selection mode not exited when last item deselected
  - RoutinesTab: auto-exit when selectedIds empty

WARNING: Test coverage gaps (3)
  - SchemaParityTest: update stale version comments
  - RoutineRegressionGuardTest: add backup v3 compat test
  - SupersetReorderTest: add out-of-bounds + orderInSuperset assertions
  - RoutineGroupTest: add ON DELETE SET NULL coverage note

Pre-existing: added groupId = null to 5 test files using insertRoutine

Reviewers: testing-qa-verification-specialist, engineering-backend-architect,
testing-test-results-analyzer, engineering-mobile-app-builder

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Review passed after 1 cycle.
2 blocker(s) fixed, 6 warning(s) fixed.
Reviewers: testing-qa-verification-specialist, engineering-backend-architect,
testing-test-results-analyzer, engineering-mobile-app-builder

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 40: Analytics
Wave: 1
Requirements: ANALYTICS-01

Wire BodyweightVolumeCalculator into ActiveSessionEngine at all 3
set completion paths. Add body weight input to Settings with kg/lbs
support. Add bodyweight exercise variant picker to SetReadyScreen.
Migrate RoutineFlowManager callers to Exercise.isBodyweight property.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nt (#225)

Phase 40: Analytics
Wave: 1
Requirements: ANALYTICS-02

Fix RoutineTimeEstimator: profileId parameter (no hardcoded "default"),
AMRAP 1.5x multiplier with range output, warmup sets at 0.7x, superset-
aware traversal via getItems(), 30s exercise transitions, 3-session
minimum threshold. Register in Koin. Wire to RoutineOverviewScreen and
RoutinesTab with clock badge.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ation

Phase 40: Analytics
Wave: 2
Requirements: ANALYTICS-01, ANALYTICS-02

Add 17 new tests: 7 bodyweight volume (decline, pull-up, edge cases),
3 sync push (bodyweight volume survives push, cable division), 7 time
estimator (multi-exercise, warmup combo, AMRAP range, long routine).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All plans executed. 3/3 passed.
Overall progress: 14/21 (67%)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Review passed after 1 cycle(s).
0 blocker(s) fixed, 5 warning(s) accepted as documented limitations.
Reviewers: testing-qa-verification-specialist, engineering-senior-developer

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 41: Quick Wins
Wave: 1
Requirements: UX-01 (#190), UX-02 (#228)

- Wire autoStartRoutine preference with LaunchedEffect redirect in RoutineOverviewScreen
- Add exercise timer pause/resume/reset for TUT/Echo timed exercises
- Timer controls are pure state manipulation — no BLE side effects
- Settings toggle for auto-start in Workout section
- 13 new tests (5 auto-start, 8 timer controls)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 41: Quick Wins
Wave: 2
Requirements: AUDIO-01 (#100)

- Add FINAL_REP HapticEvent with distinct boopbeepbeep sound + strong haptic
- Switch REP_COMPLETED from quiet beep to louder chirpchirp
- Gate warmup rep chirps by repSoundEnabled (was ungated)
- Priority chain: audioRepCount > FINAL_REP > REP_COMPLETED
- Both TOP and BOTTOM rep timing paths handle final rep detection
- iOS sound mapping + TODO for .ogg-to-.caf conversion
- 28 new tests (event identity, final rep detection, preference gating)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All plans executed. 2/2 passed.
Overall progress: 16/21 (76%)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Review passed after 1 cycle(s).
0 blocker(s) fixed, 5 warnings accepted as design trade-offs.
Reviewers: testing-qa-verification-specialist, testing-test-results-analyzer

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… Pickers

Phase 42: Platform
Wave: 1
Requirements: PLATFORM-01

- BackupDestination sealed class with Default/Custom variants, iOS bookmark support
- PreferencesManager persistence with JSON serialization and graceful fallback
- BackupLocationPicker expect/actual: Android SAF OpenDocumentTree + iOS UIDocumentPicker
- Added androidx-documentfile dependency for Android directory name extraction

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 42: Platform
Wave: 2
Requirements: PLATFORM-01

- BackupDestinationResolver interface with Android/iOS implementations
- Android: DocumentFile + persistable URI permission checks
- iOS: Security-scoped bookmark resolution via Base64 decode
- DataBackupManager routing: custom destination with fallback to default
- SettingsTab: backup location display, change/reset controls, picker integration
- Wired through SettingsManager → MainViewModel → NavGraph → SettingsTab

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 42: Platform
Wave: 3
Requirements: PLATFORM-01

- BackupDestinationTest: 15 tests (serialization, round-trip, error resilience, forward compat)
- BackupRoutingTest: 9 tests (resolver accessibility, write capture, fallback, edge cases)
- FakeBackupDestinationResolver: test double with configurable results
- Fixed FakePreferencesManager missing setBackupDestination() override (compilation blocker)
- 24 new tests total, all pass. 1612 tests in suite.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9thLevelSoftware and others added 18 commits April 28, 2026 15:34
SchemaManifest CREATE TABLE and heal operations must stay in sync with
migrations. Missing cable_count from migration 28.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PersonalRecord stored weightPerCableKg but lacked cableCount, so all PR
displays defaulted to single-cable weight. Added cable_count column via
migration 28, threaded through repository, sync DTO, backup/restore,
migration manager, and updated 4 display surfaces.

Legacy PRs default to null (formatter shows per-cable as before).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verify cableCount defaults to null for legacy data and correctly stores
single/dual cable values. Also fix pre-existing compilation failures
in FakePersonalRecordRepository, ConflictResolutionTest,
MigrationManagerTest, SqlDelightPersonalRecordRepositoryTest, and
ExerciseConfigViewModelTest caused by the cableCount parameter being
added to the PersonalRecordRepository interface and insertRecord/
insertPRIgnore SQLDelight queries but not propagated to test fakes and
call sites.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
66 .caf files exist in iosApp/VitruvianPhoenix/ bundle. The TODO was
left behind after conversion was completed via convert_sounds.sh.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dual-cable exercises with individual handles (HANDLES, ROPE, etc.) now
show per-cable weight. Only BAR and BELT exercises show combined weight.
Adds display_multiplier column (migration 29) to WorkoutSession, threads
through SetSummary, backup, sync, and all display surfaces.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Thread display_multiplier = null through 9 test call sites that broke
after migration 29 added the column to WorkoutSession.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…mits

Introduce visual delta tracking to show weight changes relative to routine
baselines. Standardize hardware weight limits across the workout flow using
centralized constants.

- Add `MAX_WEIGHT_PER_CABLE_KG` (110kg) to `Constants`.
- Enhance `SliderWithButtons` to support optional, color-coded `deltaText`
  (tertiary for increase, error for decrease).
- Implement baseline delta calculation logic in `RoutineOverviewScreen`,
  `SetReadyScreen`, and `RestTimerCard`.
- Refactor weight adjusters to use shared hardware constants and
  standardize on a 0.25kg weight step.
- Update `RoutineFlowManager` to enforce weight clamping using `coerceIn`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Merge main into feat/v0.9.0-enhancement-sweep to bring in bug fixes
that landed after the branch diverged:

- #388: V-Form legacy rep packet parsing (6..23 byte range)
- #357: warmupOverrideParams for rep counter configuration
- #402: EccentricOnly BLE NON_OVERLAP variant override
- #393: Routine complete navigation race (Idle before Complete)
- #392: Session ID leak on routine switch
- #395: Aggregate health writes for routine workouts

Conflict resolution highlights:
- 27.sqm: Combined RoutineGroup + TrainingCycle deletedAt
- MigrationStatements: v27 combined, v28-29 added with v29 fix
- VitruvianDatabase.sq: upsertRoutine gets both updatedAt + groupId
- DataBackupManager: v1 backup import nullable defaults for new cols
- ActiveSessionEngine: VBT threshold code kept, duplicate removed
- PortalApiClient: JWT prefix leak downgraded to Logger.d

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mobile-side design for exercise ID-first sync resolution, resolving
GitHub issue #404 (exercise identity lost during sync round-trip).
Covers: displayName field, sync DTO changes, ID-first pull resolution,
trailing-space name cleanup. Depends on portal-side catalog deployment.
Companion spec in phoenix-portal for Supabase/edge function changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Part of exercise catalog ID-first resolution (#404).
Adds displayName TEXT column for disambiguation of same-name exercises.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Was a computed property returning name. Now a constructor parameter
that defaults to name, populated from the DB displayName column.
All insertExercise call sites pass null for now (Task 3 will generate
display names). Mapper falls back to base name when column is NULL.
Part of exercise catalog ID-first resolution (#404).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds exerciseId to PortalExerciseDto and PortalRoutineExerciseSyncDto.
Adds displayName and exerciseEquipment to PortalRoutineExerciseSyncDto.
Adds displayName to CustomExerciseSyncDto.
Part of exercise catalog ID-first resolution (#404).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Exercises with duplicate names get equipment-suffixed display names
(e.g. 'Bicep Curl (Long Bar)'). All exercise names are trimmed on
import to remove trailing-space disambiguation hacks.
Part of exercise catalog ID-first resolution (#404).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Session exercises and routine exercises now carry the catalog exercise ID,
display name, and equipment in their push DTOs.
Part of exercise catalog ID-first resolution (#404).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Exercise resolution now checks exerciseId first (direct catalog lookup),
falling back to name-based resolution only for legacy data without IDs.
Fixes the core identity loss bug (#404).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rectness

- Add displayName to SchemaManifest CREATE TABLE and heal operations
- Register migration 30 in MigrationStatements resilient fallback
- Fix 7 test compilation errors (missing displayName/cable_count params)
- Wire exerciseId through session pull for ID-first resolution
- Fix telemetry cable values from "left"/"right" to canonical "A"/"B"
- Raise weight clamp from MAX_WEIGHT_KG (100) to MAX_WEIGHT_PER_CABLE_KG (110)
- Extract WeightStepper labels to localizable string resources
- Remove dead legacy HapticFeedbackEffect in androidApp/ui/

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 4, 2026 02:21
@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 674e1640-7d77-4e52-b9c1-f85b7ddde8a7

📥 Commits

Reviewing files that changed from the base of the PR and between 0b9d521 and 9c75a22.

📒 Files selected for processing (1)
  • androidApp/src/androidTest/kotlin/com/devil/phoenixproject/e2e/AppE2ETest.kt

📝 Walkthrough

Walkthrough

Removed the Android haptic/audio composable; introduced two-cable weight UI/localization changes and per-cable clamping; propagated portal exercise IDs through sync lookup and updated telemetry cable labels; added schema heal for Exercise.displayName and test adjustments (cable_count, clamping expectations, fake repo ID resolution).

Changes

Two‑Cable Weight System

Layer / File(s) Summary
Localization
shared/src/commonMain/composeResources/values/strings.xml
Added weight_kg_per_cable and weight_total_two_cables.
Schema Heal
shared/src/commonMain/kotlin/com/devil/phoenixproject/data/local/SchemaManifest.kt
Added SchemaHealOperation to ensure Exercise.displayName column exists (Migration 30 comment updated).
Weight Adjustment Logic
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/components/BulkWeightAdjustDialog.kt
applyBulkAdjust clamping updated to use Constants.MAX_WEIGHT_PER_CABLE_KG; preview clamped detection aligned.
Weight UI & Controls
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/components/WeightAdjustmentControls.kt
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/components/WeightStepper.kt
Added optional weightIncrementKg to controls; increment selection uses provided value when >0; upper-bound checks for + button and clamping use MAX_WEIGHT_PER_CABLE_KG; replaced hardcoded "kg per cable" and total display with localized string resources. Slider step cap and rounding adjustments in dialog.
Tests
shared/src/commonTest/.../BulkWeightAdjustTest.kt
Assertions and docs updated to expect clamping at Constants.MAX_WEIGHT_PER_CABLE_KG and related test inputs adjusted.
Backup Import Test
shared/src/androidHostTest/.../BackupJsonNavigatorTest.kt
Inserted cable_count = 2 into the seeded personal record for round-trip test.

Sync & Exercise ID Propagation

Layer / File(s) Summary
Adapter Signature
shared/src/commonMain/kotlin/com/devil/phoenixproject/data/sync/PortalPullAdapter.kt
toWorkoutSessionsWithLookup exerciseLookup now accepts an additional nullable exerciseId parameter and callers pass the portal exerciseId.
Sync Orchestration
shared/src/commonMain/kotlin/com/devil/phoenixproject/data/sync/SyncManager.kt
Lookup lambda updated to forward existingExerciseId to syncRepository.findExerciseId(name, muscleGroup, existingExerciseId).
Telemetry Labeling
shared/src/commonMain/kotlin/com/devil/phoenixproject/data/sync/PortalSyncAdapter.kt
PortalRepTelemetryDto.cable values changed from "left"/"right" to "A"/"B" for concentric and eccentric phases.
Fake Repo Behavior
shared/src/commonTest/kotlin/com/devil/phoenixproject/testutil/FakeSyncRepository.kt
findExerciseId(...) now resolves by provided exerciseId first, falling back to name+muscleGroup lookup.
E2E Test Text Update
androidApp/src/androidTest/kotlin/com/devil/phoenixproject/e2e/AppE2ETest.kt
UI assertions updated to expect "STOP" instead of "Stop Set".

Haptic Feedback Removal

Layer / File(s) Summary
Composable Deletion
androidApp/src/main/kotlin/com/devil/phoenixproject/ui/HapticFeedbackEffect.kt
File removed. Deleted the @Composable HapticFeedbackEffect(...), SoundPool construction and management, sound/haptic dispatch logic, event collection, and resource cleanup.

Sequence Diagram(s)

sequenceDiagram
participant Portal as Portal API
participant SyncMgr as SyncManager
participant PullAdapter as PortalPullAdapter
participant Repo as SyncRepository

Portal->>SyncMgr: deliver PullWorkoutSessionDto (includes exerciseId)
SyncMgr->>PullAdapter: toWorkoutSessionsWithLookup(portalSession,..., lookup)
PullAdapter->>Lookup: lookup(name, muscleGroup, exerciseId)
activate Repo
Lookup->>Repo: findExerciseId(name, muscleGroup, existingExerciseId)
Repo-->>Lookup: resolvedExerciseId (uses exerciseId-first)
Lookup-->>PullAdapter: resolvedExerciseId
PullAdapter-->>SyncMgr: WorkoutSession with resolvedExerciseId
SyncMgr->>Repo: persist/merge sessions
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

"I hopped through code with tiny paws,
Two cables now share lifting laws,
Haptics hushed, strings sung true,
IDs linked up, telemetry too —
A rabbit cheers the build anew! 🐇"

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Feat/v0.9.0 enhancement sweep' is vague and generic, using non-descriptive terms that don't convey meaningful information about the specific changes. Replace with a specific title that highlights the primary change, such as 'Add cable-count support for weight exercises' or 'Support dual-cable weight adjustment with per-cable limits'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/v0.9.0-enhancement-sweep

Review rate limit: 2/5 reviews remaining, refill in 30 minutes and 19 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 122b9527a0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 1186 to 1188
profile_id = routine.profileId ?: "default",
groupId = null,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve routine group assignment in streaming imports

In the streaming import path (importFromStream), routines are always inserted with groupId = null, so backups larger than the streaming threshold lose all routine-to-group links even when the backup contains valid groupId values. This silently flattens grouped routines after restore for large backups, while non-streaming import preserves the field.

Useful? React with 👍 / 👎.

Comment on lines 1065 to 1067
profile_id = session.profileId ?: "default",
display_multiplier = null,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep display multiplier when streaming session backups

The streaming import branch hard-codes display_multiplier = null when restoring workout sessions, which drops newly added display metadata from v3 backups. For imports that take the streaming path, restored history can render weights differently than before export because the formatter must fall back to heuristics/legacy fields.

Useful? React with 👍 / 👎.

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.

Pull request overview

This PR is a broad “enhancement sweep” across the shared KMP module and both platforms, adding new workout UX signals (haptics/VBT), routine grouping + backup schema updates, and improving weight display semantics (cable-aware presentation) alongside sync/backup plumbing updates.

Changes:

  • Adds new haptic events (FINAL_REP, VELOCITY_THRESHOLD_REACHED) and threads VBT settings through coordinator/engine/preferences.
  • Introduces cable-/equipment-aware weight display via displayMultiplier, plus DB + backup + sync DTO schema updates.
  • Adds custom backup destination support (model/picker/resolver) and routine grouping support (model/UI/backup/migration).

Reviewed changes

Copilot reviewed 180 out of 277 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
shared/src/iosMain/kotlin/com/devil/phoenixproject/presentation/components/HapticFeedbackEffect.ios.kt iOS sound + haptic routing for new haptic events
shared/src/iosMain/kotlin/com/devil/phoenixproject/di/PlatformModule.ios.kt iOS DI wiring for backup destination resolver + updated backup manager deps
shared/src/commonTest/kotlin/com/devil/phoenixproject/testutil/FakeWorkoutRepository.kt Test fake updated for new repository API
shared/src/commonTest/kotlin/com/devil/phoenixproject/testutil/FakeSyncRepository.kt Test fake updated for ID-first exercise resolution
shared/src/commonTest/kotlin/com/devil/phoenixproject/testutil/FakePreferencesManager.kt Test fake updated for backup + VBT prefs setters
shared/src/commonTest/kotlin/com/devil/phoenixproject/testutil/FakePersonalRecordRepository.kt Test fake updated for PR cableCount parameter
shared/src/commonTest/kotlin/com/devil/phoenixproject/testutil/FakeBackupDestinationResolver.kt New test double for backup destination I/O
shared/src/commonTest/kotlin/com/devil/phoenixproject/presentation/util/WeightDisplayGuardTest.kt Reflection-based guard test for formatter layering
shared/src/commonTest/kotlin/com/devil/phoenixproject/presentation/manager/WorkoutCoordinatorEventTest.kt New tests for haptic event coexistence/routing
shared/src/commonTest/kotlin/com/devil/phoenixproject/domain/model/WorkoutMetricTest.kt Extends singleton equality test coverage for new haptic event
shared/src/commonTest/kotlin/com/devil/phoenixproject/data/sync/PortalSyncAdapterTest.kt Adds bodyweight volume sync push regression tests
shared/src/commonTest/kotlin/com/devil/phoenixproject/data/sync/ConflictResolutionTest.kt Updates fixtures for new nullable DB columns
shared/src/commonTest/kotlin/com/devil/phoenixproject/data/repository/PersonalRecordCableCountTest.kt New unit tests for PR cableCount backward compat
shared/src/commonMain/sqldelight/com/devil/phoenixproject/database/migrations/30.sqm Adds Exercise.displayName column
shared/src/commonMain/sqldelight/com/devil/phoenixproject/database/migrations/29.sqm Adds WorkoutSession.display_multiplier column
shared/src/commonMain/sqldelight/com/devil/phoenixproject/database/migrations/28.sqm Adds PersonalRecord.cable_count column
shared/src/commonMain/sqldelight/com/devil/phoenixproject/database/migrations/27.sqm Expands migration 27 to include RoutineGroup + Routine.groupId FK
shared/src/commonMain/kotlin/com/devil/phoenixproject/util/Constants.kt Updates app version + adjusts weight constants surface
shared/src/commonMain/kotlin/com/devil/phoenixproject/util/BackupModels.kt Backup schema v3: routine groups + new fields (displayMultiplier, cableCount, etc.)
shared/src/commonMain/kotlin/com/devil/phoenixproject/util/BackupLocationPicker.kt expect API for directory picking (custom backup destination)
shared/src/commonMain/kotlin/com/devil/phoenixproject/util/BackupDestinationResolver.kt New interface for platform-specific directory access
shared/src/commonMain/kotlin/com/devil/phoenixproject/util/BackupDestination.kt New serializable preference model for backup destination
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/viewmodel/MainViewModel.kt Exposes routine groups + new settings setters + exercise timer controls
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/util/WeightDisplayFormatter.kt New centralized display-only weight formatting (cable-aware)
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/screen/WorkoutUiState.kt Adds exercise timer pause state + VBT threshold in UI state/actions
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/screen/WorkoutTab.kt Threads exercise timer controls + applies display multiplier in countdown card
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/screen/TrainingCyclesScreen.kt Wires user preference increment into cycle mode confirmation
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/screen/RoutineGroupHeader.kt New composable for collapsible routine group headers
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/screen/ModeConfirmationScreen.kt Displays per-cable weight in user unit + configurable weight step
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/screen/HomeScreen.kt Uses WeightDisplayFormatter for recent activity weights
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/screen/ExercisesTab.kt Updates best-weight/1RM computations for cable awareness
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/screen/ExerciseDetailScreen.kt Updates 1RM + trends + tables to be cable-aware
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/screen/DailyRoutinesScreen.kt Wires routine group CRUD into routines UI
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/screen/AnalyticsScreen.kt Uses WeightDisplayFormatter for PR display
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/screen/ActiveWorkoutScreen.kt Threads exercise timer pause + uses WeightDisplayFormatter in PR dialog
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/navigation/NavGraph.kt Wires new settings: backup destination, weight increment, body weight, VBT
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/manager/WorkoutCoordinator.kt Adds routineGroups state + VBT threshold/config plumbing + timer pause state
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/manager/SettingsManager.kt Adds setters for backup destination + VBT prefs
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/manager/GamificationManager.kt Threads cableCount into PR persistence + celebration event
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/manager/DefaultWorkoutSessionManager.kt Constructs coordinator with VBT prefs + collects updates; delegates group CRUD and timer controls
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/components/WeightStepper.kt i18n string resources + clarifies BLE rounding responsibilities
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/components/SliderWithButtons.kt Adds optional delta indicator for sliders
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/components/SelectionActionBar.kt Adds “bulk weight adjust” action button with localized a11y text
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/components/ExerciseConfigModal.kt Threads configurable weight step into mode config panels
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/components/DashboardComponents.kt Uses WeightDisplayFormatter for PR/top-exercise displays
shared/src/commonMain/kotlin/com/devil/phoenixproject/domain/usecase/BodyweightVolumeCalculator.kt Adds more variants + variant picker support
shared/src/commonMain/kotlin/com/devil/phoenixproject/domain/premium/BiomechanicsEngine.kt Makes velocity loss threshold mutable at runtime
shared/src/commonMain/kotlin/com/devil/phoenixproject/domain/model/UserPreferences.kt Adds backup destination + VBT preferences
shared/src/commonMain/kotlin/com/devil/phoenixproject/domain/model/Routine.kt Adds RoutineGroup + Routine.groupId; deprecates duplicate roundToIncrement
shared/src/commonMain/kotlin/com/devil/phoenixproject/domain/model/Models.kt Adds PR cableCount, session displayMultiplier, new haptic events, PR celebration cableCount
shared/src/commonMain/kotlin/com/devil/phoenixproject/domain/model/Exercise.kt Adds Exercise.displayName + isBodyweight + displayMultiplier logic
shared/src/commonMain/kotlin/com/devil/phoenixproject/di/DomainModule.kt Registers RoutineTimeEstimator use case
shared/src/commonMain/kotlin/com/devil/phoenixproject/data/sync/SyncModels.kt Adds sync DTO fields: displayMultiplier, PR cableCount, exercise displayName
shared/src/commonMain/kotlin/com/devil/phoenixproject/data/sync/SyncManager.kt Updates pull exercise lookup to support ID-first resolution
shared/src/commonMain/kotlin/com/devil/phoenixproject/data/sync/PortalSyncDtos.kt Adds exerciseId/displayName/equipment snapshot fields; documents canonical cable A/B
shared/src/commonMain/kotlin/com/devil/phoenixproject/data/sync/PortalSyncAdapter.kt Includes catalog IDs in portal DTOs + uses canonical cable A/B telemetry
shared/src/commonMain/kotlin/com/devil/phoenixproject/data/sync/PortalPullAdapter.kt Adds exerciseId-aware lookup on pull
shared/src/commonMain/kotlin/com/devil/phoenixproject/data/sync/PortalApiClient.kt Reduces auth logging verbosity (token prefix removed)
shared/src/commonMain/kotlin/com/devil/phoenixproject/data/repository/WorkoutRepository.kt Adds session-count API for time estimation
shared/src/commonMain/kotlin/com/devil/phoenixproject/data/repository/SyncRepository.kt Extends findExerciseId signature with optional exerciseId
shared/src/commonMain/kotlin/com/devil/phoenixproject/data/repository/SqlDelightPersonalRecordRepository.kt Threads cableCount through PR persistence and mapping
shared/src/commonMain/kotlin/com/devil/phoenixproject/data/repository/SqlDelightExerciseRepository.kt Maps Exercise.displayName from DB
shared/src/commonMain/kotlin/com/devil/phoenixproject/data/repository/PersonalRecordRepository.kt Adds optional cableCount params to PR update APIs
shared/src/commonMain/kotlin/com/devil/phoenixproject/data/preferences/PreferencesManager.kt Adds backup destination + VBT preference persistence
shared/src/commonMain/kotlin/com/devil/phoenixproject/data/migration/MigrationManager.kt Carries PR cable_count through profile migrations
shared/src/commonMain/kotlin/com/devil/phoenixproject/data/local/MigrationStatements.kt Adds statement lists for migrations 27–30 (resilient executor)
shared/src/commonMain/kotlin/com/devil/phoenixproject/data/local/ExerciseImporter.kt Generates disambiguated display names during import
shared/src/commonMain/composeResources/values/strings.xml Adds new string keys for weight stepper + adjust weights
shared/src/commonMain/composeResources/values-nl/strings.xml Adds localized cd_adjust_weights
shared/src/commonMain/composeResources/values-fr/strings.xml Adds localized cd_adjust_weights (currently English)
shared/src/commonMain/composeResources/values-es/strings.xml Adds localized cd_adjust_weights (currently English)
shared/src/commonMain/composeResources/values-de/strings.xml Adds localized cd_adjust_weights (currently English)
shared/src/androidMain/kotlin/com/devil/phoenixproject/util/BackupLocationPicker.android.kt Android directory picker implementation (SAF + persistable permissions)
shared/src/androidMain/kotlin/com/devil/phoenixproject/util/BackupDestinationResolver.android.kt Android resolver implementation (DocumentFile I/O + permission validation)
shared/src/androidMain/kotlin/com/devil/phoenixproject/di/PlatformModule.android.kt Android DI wiring for resolver + updated backup manager deps
shared/src/androidHostTest/kotlin/com/devil/phoenixproject/util/DataBackupManagerRoutineNameTest.kt Updates fixtures for new nullable columns (groupId/display_multiplier)
shared/src/androidHostTest/kotlin/com/devil/phoenixproject/util/BackupJsonNavigatorTest.kt Adds cable_count fixture for round-trip
shared/src/androidHostTest/kotlin/com/devil/phoenixproject/presentation/viewmodel/ExerciseConfigViewModelTest.kt Updates fixtures for new Exercise.displayName + PR cable_count
shared/src/androidHostTest/kotlin/com/devil/phoenixproject/data/repository/SqlDelightUserProfileRepositoryTest.kt Updates session fixture for display_multiplier
shared/src/androidHostTest/kotlin/com/devil/phoenixproject/data/repository/SqlDelightTrainingCycleRepositoryTest.kt Updates routine fixture for groupId
shared/src/androidHostTest/kotlin/com/devil/phoenixproject/data/repository/SqlDelightSmartSuggestionsRepositoryTest.kt Updates session fixture for display_multiplier
shared/src/androidHostTest/kotlin/com/devil/phoenixproject/data/repository/SqlDelightPersonalRecordRepositoryTest.kt Updates fixtures for Exercise.displayName + PR cable_count
shared/src/androidHostTest/kotlin/com/devil/phoenixproject/data/repository/SqlDelightGamificationRepositoryTest.kt Updates session fixture for display_multiplier
shared/src/androidHostTest/kotlin/com/devil/phoenixproject/data/repository/SqlDelightExerciseRepositoryTest.kt Updates insertExercise fixture for displayName
shared/src/androidHostTest/kotlin/com/devil/phoenixproject/data/repository/SqlDelightCompletedSetRepositoryTest.kt Updates routine/session fixtures for groupId/display_multiplier
shared/src/androidHostTest/kotlin/com/devil/phoenixproject/data/migration/MigrationManagerTest.kt Updates fixtures for new nullable columns
shared/src/androidHostTest/kotlin/com/devil/phoenixproject/data/local/SchemaParityTest.kt Updates schema parity docs/version constant (but not yet to latest migrations)
shared/build.gradle.kts Bumps compileSdk and updates dependency management + SQLDelight schema version
iosApp/VitruvianPhoenix/VitruvianPhoenix.xcodeproj/project.pbxproj Updates iOS marketing version
gradle/libs.versions.toml Adds DocumentFile and Health Connect coordinates
audit/AUDIT-SUMMARY.md Updates audit summary reporting table and totals
androidApp/src/androidTest/kotlin/com/devil/phoenixproject/testutil/FakeWorkoutRepository.kt Android test fake updated for new repository API
androidApp/build.gradle.kts Bumps compileSdk/versionName and enables resource shrinking for release
.planning/phases/44-integration-validation/44-REVIEW.md Adds validation review notes and verdict
.planning/phases/44-integration-validation/44-02-SUMMARY.md Adds regression suite/build verification summary
.planning/phases/44-integration-validation/44-01-SUMMARY.md Adds integration test plan summary
.planning/phases/43-advanced-vbt/43-REVIEW.md Adds VBT phase review summary
.planning/phases/43-advanced-vbt/43-03-SUMMARY.md Adds VBT tests/integration validation summary
.planning/phases/43-advanced-vbt/43-02-SUMMARY.md Adds VBT real-time tracking/auto-end summary
.planning/phases/43-advanced-vbt/43-01-SUMMARY.md Adds VBT settings/threshold model summary
.planning/phases/42-platform/42-REVIEW.md Adds platform phase review summary
.planning/phases/42-platform/42-03-SUMMARY.md Adds platform tests/fixtures summary
.planning/phases/42-platform/42-02-SUMMARY.md Adds platform UI integration + backup routing summary
.planning/phases/42-platform/42-01-SUMMARY.md Adds backup destination model + picker summary
.planning/phases/41-quick-wins/41-REVIEW.md Adds quick wins phase review summary
.planning/phases/40-analytics/40-REVIEW.md Adds analytics phase review summary
.planning/phases/40-analytics/40-02-SUMMARY.md Adds routine time estimate enhancement summary
.planning/phases/40-analytics/40-01-SUMMARY.md Adds bodyweight volume integration summary
.planning/memory/RETRO.md Adds retrospective/action items log
.planning/phases/36-integration-validation/36-CONTEXT.md Removes obsolete phase context
.planning/phases/36-integration-validation/36-03-PLAN.md Removes obsolete plan doc
.planning/phases/36-integration-validation/36-02-PLAN.md Removes obsolete plan doc
.planning/phases/36-integration-validation/36-01-PLAN.md Removes obsolete plan doc
.planning/phases/35-ios-platform-parity/35-CONTEXT.md Removes obsolete phase context
.planning/phases/35-ios-platform-parity/35-03-PLAN.md Removes obsolete plan doc
.planning/phases/35-ios-platform-parity/35-02-PLAN.md Removes obsolete plan doc
.planning/phases/35-ios-platform-parity/35-01-PLAN.md Removes obsolete plan doc
.planning/phases/34-lifecycle-security/34-REVIEW.md Removes obsolete phase review doc
.planning/phases/34-lifecycle-security/34-CONTEXT.md Removes obsolete phase context
.planning/phases/34-lifecycle-security/34-03-PLAN.md Removes obsolete plan doc
.planning/phases/34-lifecycle-security/34-02-PLAN.md Removes obsolete plan doc
.planning/phases/33-sync-data-integrity/33-REVIEW.md Removes obsolete phase review doc
.planning/phases/33-sync-data-integrity/33-CONTEXT.md Removes obsolete phase context
.planning/phases/33-sync-data-integrity/33-03-SUMMARY.md Removes obsolete summary doc
.planning/phases/33-sync-data-integrity/33-03-PLAN.md Removes obsolete plan doc
.planning/phases/33-sync-data-integrity/33-02-SUMMARY.md Removes obsolete summary doc
.planning/phases/33-sync-data-integrity/33-01-SUMMARY.md Removes obsolete summary doc
.planning/phases/32-ble-reliability/32-REVIEW.md Removes obsolete phase review doc
.planning/phases/32-ble-reliability/32-CONTEXT.md Removes obsolete phase context
.planning/phases/32-ble-reliability/32-03-SUMMARY.md Removes obsolete summary doc
.planning/phases/32-ble-reliability/32-02-SUMMARY.md Removes obsolete summary doc
.planning/phases/32-ble-reliability/32-01-SUMMARY.md Removes obsolete summary doc

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread shared/build.gradle.kts Outdated
Comment on lines 211 to 213
// Version 30 = initial schema (1) + 29 migrations (1.sqm through 29.sqm).
version = 30
}
Comment on lines 64 to 66
bestOneRepMax = calculateBestOneRepMax(sessions),
bestWeight = sessions.maxOf { it.weightPerCableKg },
bestWeight = sessions.maxOf { it.weightPerCableKg * (it.cableCount ?: 1) },
lastPerformed = sessions.maxOf { it.timestamp },
Comment on lines +75 to +76
override suspend fun getSessionCountForExercise(exerciseId: String, profileId: String): Long =
sessions.values.count { it.exerciseId == exerciseId }.toLong()
Comment on lines 92 to 95
if (session.weightPerCableKg > 0) {
session.timestamp to session.weightPerCableKg
val totalWeight = session.weightPerCableKg * (session.cableCount ?: 1)
session.timestamp to totalWeight
} else {
Comment on lines 151 to 153
companion object {
private const val CURRENT_VERSION = 26L
private const val CURRENT_VERSION = 27L

Comment on lines 79 to 82
if (session.workingReps > 0) {
val oneRm = calculateOneRepMax(session.weightPerCableKg, session.workingReps)
val totalWeight = session.weightPerCableKg * (session.cableCount ?: 1)
val oneRm = calculateOneRepMax(totalWeight, session.workingReps)
session.timestamp to oneRm
Comment on lines +76 to +78
override suspend fun getSessionCountForExercise(exerciseId: String, profileId: String): Long =
sessions.values.count { it.exerciseId == exerciseId }.toLong()

Comment on lines 6 to 9
object Constants {
// App version
const val APP_VERSION = "0.7.0"
const val APP_VERSION = "0.8.0"

Comment on lines +42 to +46
val job = launch(UnconfinedTestDispatcher(testScheduler)) {
coordinator.hapticEvents.toList(collected)
}

hapticFlow.emit(HapticEvent.FINAL_REP)

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0b9d521c42

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +112 to +113
onClick = { onWeightChange((currentWeightKg + incrementKg).coerceAtMost(Constants.MAX_WEIGHT_PER_CABLE_KG)) },
enabled = enabled && currentWeightKg < Constants.MAX_WEIGHT_PER_CABLE_KG,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Align all weight-entry paths to the same max limit

Updating the + buttons to allow MAX_WEIGHT_PER_CABLE_KG here introduces inconsistent behavior because other paths in the same component still clamp to MAX_WEIGHT_KG (e.g., WeightPresets and WeightPickerDialog). After a user increments above 100 kg, using +5% or opening the picker can silently force the value back to 100 kg, making 100–110 kg settings unstable and surprising.

Useful? React with 👍 / 👎.

@kilo-code-bot

kilo-code-bot Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: Issues Found | Recommendation: Address before merge

Overview

Severity Count
WARNING 0
SUGGESTION 0
Other Observations (not in diff)
File Line Issue
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/components/WeightAdjustmentControls.kt 259 Uses Constants.MAX_WEIGHT_KG (100kg) instead of MAX_WEIGHT_PER_CABLE_KG (110kg) for +5% preset clamp.
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/components/WeightAdjustmentControls.kt 314 maxWeightDisplay and comment "220 lbs or 100 kg" use old constant; should use 110kg ceiling.
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/components/WeightAdjustmentControls.kt 318 rawSteps computed using MAX_WEIGHT_KG; should use MAX_WEIGHT_PER_CABLE_KG.
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/components/WeightAdjustmentControls.kt 404 Slider coerceIn(0f, MAX_WEIGHT_KG) limits to 100kg; should be 110kg.
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/components/WeightAdjustmentControls.kt 426 Quick-adjust button clamps to MAX_WEIGHT_KG; should use 110kg limit.
shared/src/commonMain/kotlin/com/devil/phoenixproject/util/Constants.kt 9 PR title references v0.9.0 but APP_VERSION is "0.8.0" (already flagged in existing comment).
Files Reviewed (1 file in increment)
  • androidApp/src/androidTest/kotlin/com/devil/phoenixproject/e2e/AppE2ETest.kt (test text update: "Stop Set" → "STOP")

Reviewed by step-3.5-flash · 295,222 tokens

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

Caution

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

⚠️ Outside diff range comments (1)
shared/src/androidHostTest/kotlin/com/devil/phoenixproject/util/BackupJsonNavigatorTest.kt (1)

530-543: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Assert cable_count in the round-trip, not just the record count.

This test now seeds cable_count = 2, but it never verifies that the value survives export → import → re-export. A regression that drops the new field would still pass.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/components/WeightAdjustmentControls.kt`:
- Around line 111-113: The picker and preset paths are still using
Constants.MAX_WEIGHT_KG while the +/- buttons and allowed UI range use
Constants.MAX_WEIGHT_PER_CABLE_KG (110kg); update any usage of
WeightPickerDialog(...) and the "+5%" preset calculation in
WeightAdjustmentControls (including the handler that clamps values for presets)
to use Constants.MAX_WEIGHT_PER_CABLE_KG instead of Constants.MAX_WEIGHT_KG, and
ensure onWeightChange calls and any coerceAtMost/coerceIn clamping logic
reference Constants.MAX_WEIGHT_PER_CABLE_KG so all adjustment paths (buttons,
preset, picker) share the same ceiling.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 722ece41-dadb-40ae-b744-f0f38dd03673

📥 Commits

Reviewing files that changed from the base of the PR and between 93203bd and 0b9d521.

📒 Files selected for processing (12)
  • androidApp/src/main/kotlin/com/devil/phoenixproject/ui/HapticFeedbackEffect.kt
  • shared/src/androidHostTest/kotlin/com/devil/phoenixproject/util/BackupJsonNavigatorTest.kt
  • shared/src/commonMain/composeResources/values/strings.xml
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/data/local/SchemaManifest.kt
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/data/sync/PortalPullAdapter.kt
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/data/sync/PortalSyncAdapter.kt
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/data/sync/SyncManager.kt
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/components/BulkWeightAdjustDialog.kt
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/components/WeightAdjustmentControls.kt
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/components/WeightStepper.kt
  • shared/src/commonTest/kotlin/com/devil/phoenixproject/presentation/components/BulkWeightAdjustTest.kt
  • shared/src/commonTest/kotlin/com/devil/phoenixproject/testutil/FakeSyncRepository.kt
💤 Files with no reviewable changes (1)
  • androidApp/src/main/kotlin/com/devil/phoenixproject/ui/HapticFeedbackEffect.kt

Comment on lines 111 to +113
icon = Icons.Default.Add,
onClick = { onWeightChange((currentWeightKg + incrementKg).coerceAtMost(Constants.MAX_WEIGHT_KG)) },
enabled = enabled && currentWeightKg < Constants.MAX_WEIGHT_KG,
onClick = { onWeightChange((currentWeightKg + incrementKg).coerceAtMost(Constants.MAX_WEIGHT_PER_CABLE_KG)) },
enabled = enabled && currentWeightKg < Constants.MAX_WEIGHT_PER_CABLE_KG,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Align the picker/preset paths with the new 110kg ceiling.

These buttons now allow 100f..110f, but WeightPickerDialog(...) and the +5% preset in this same file still cap at Constants.MAX_WEIGHT_KG. That leaves the component internally inconsistent: once a user reaches, say, 105kg via the buttons, opening the picker can feed an out-of-range value into the slider and any picker interaction will clamp it back to 100kg.

♻️ Minimal follow-up to keep all weight-adjustment paths consistent
- val maxWeightDisplay = if (isLbs) UnitConverter.kgToLb(Constants.MAX_WEIGHT_KG).toInt() else Constants.MAX_WEIGHT_KG.toInt()
+ val maxWeightDisplay = if (isLbs) {
+     UnitConverter.kgToLb(Constants.MAX_WEIGHT_PER_CABLE_KG).toInt()
+ } else {
+     Constants.MAX_WEIGHT_PER_CABLE_KG.toInt()
+ }

- .coerceAtMost(Constants.MAX_WEIGHT_KG)
+ .coerceAtMost(Constants.MAX_WEIGHT_PER_CABLE_KG)

- .coerceIn(0f, Constants.MAX_WEIGHT_KG)
+ .coerceIn(0f, Constants.MAX_WEIGHT_PER_CABLE_KG)

- ).coerceIn(0f, Constants.MAX_WEIGHT_KG)
+ ).coerceIn(0f, Constants.MAX_WEIGHT_PER_CABLE_KG)

Also applies to: 511-512

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/components/WeightAdjustmentControls.kt`
around lines 111 - 113, The picker and preset paths are still using
Constants.MAX_WEIGHT_KG while the +/- buttons and allowed UI range use
Constants.MAX_WEIGHT_PER_CABLE_KG (110kg); update any usage of
WeightPickerDialog(...) and the "+5%" preset calculation in
WeightAdjustmentControls (including the handler that clamps values for presets)
to use Constants.MAX_WEIGHT_PER_CABLE_KG instead of Constants.MAX_WEIGHT_KG, and
ensure onWeightChange calls and any coerceAtMost/coerceIn clamping logic
reference Constants.MAX_WEIGHT_PER_CABLE_KG so all adjustment paths (buttons,
preset, picker) share the same ceiling.

@9thLevelSoftware
9thLevelSoftware merged commit 78ba830 into main May 4, 2026
6 checks passed
@9thLevelSoftware
9thLevelSoftware deleted the feat/v0.9.0-enhancement-sweep branch May 4, 2026 02:46
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.

2 participants