Skip to content

Codex/stu experiment#436

Merged
9thLevelSoftware merged 7 commits into
mainfrom
codex/stu_experiment
May 10, 2026
Merged

Codex/stu experiment#436
9thLevelSoftware merged 7 commits into
mainfrom
codex/stu_experiment

Conversation

@9thLevelSoftware

Copy link
Copy Markdown
Owner

No description provided.

Copilot AI review requested due to automatic review settings May 10, 2026 15:23
@9thLevelSoftware

Copy link
Copy Markdown
Owner Author

@codex resolve issues: gemini-code-assist Bot reviewed 1 minute ago
gemini-code-assist Bot
left a comment
Code Review
This pull request removes the exercise auto-detection system and replaces it with a manual tagging flow for 'Just Lift' sessions, allowing users to associate exercises and set types with their workouts from the summary screen. Feedback identifies a high-severity issue where the new tagging process bypasses Personal Record (PR) checks, which were previously handled during session saving. Additionally, a data consistency concern was raised regarding the retagging logic, which currently fails to update existing completed set records when properties like AMRAP status are changed.

...monMain/kotlin/com/devil/phoenixproject/presentation/manager/DefaultWorkoutSessionManager.kt
Comment on lines +359 to +390
suspend fun tagJustLiftSessionExercise(sessionId: String, exercise: Exercise, isAmrap: Boolean) {
val exerciseId = exercise.id
if (exerciseId.isNullOrBlank()) {
Logger.w { "Cannot tag Just Lift session $sessionId with exercise '${exercise.name}' because it has no ID" }
return
}

    val session = workoutRepository.getSession(sessionId)
    if (session == null) {
        Logger.w { "Cannot tag Just Lift session $sessionId because the saved session was not found" }
        return
    }
    if (!session.isJustLift) {
        Logger.w { "Ignoring Just Lift tag request for non-Just Lift session $sessionId" }
        return
    }

    workoutRepository.updateSessionExerciseTag(sessionId, exerciseId, exercise.name)
    val taggedSession = session.copy(exerciseId = exerciseId, exerciseName = exercise.name)
    completedSetRepository.ensureCompletedSetForTaggedJustLift(taggedSession, isAmrap)

    val currentState = coordinator._workoutState.value
    if (currentState is WorkoutState.SetSummary && currentState.sessionId == sessionId) {
        coordinator._workoutState.value = currentState.copy(
            taggedExerciseId = exerciseId,
            taggedExerciseName = exercise.name,
        )
    }

    syncTriggerManager?.onWorkoutCompleted()
    coordinator._userFeedbackEvents.emit("Tagged ${exercise.name}")
}

@gemini-code-assist
gemini-code-assist Bot
1 minute ago
high

The new post-set tagging flow for Just Lift sessions does not trigger a Personal Record (PR) check. Previously, auto-detection would assign an exerciseId before the session was saved, allowing the PR logic in ActiveSessionEngine to run. Now, the exerciseId is null at save time, and tagging the exercise later via tagJustLiftSessionExercise creates a CompletedSet but never invokes personalRecordRepository.checkAndSavePR. This results in Just Lift workouts failing to contribute to the user's PR history or trigger celebrations.

@9thLevelSoftware Reply...
...mmonMain/kotlin/com/devil/phoenixproject/data/repository/SqlDelightCompletedSetRepository.kt
Comment on lines +176 to +179
override suspend fun ensureCompletedSetForTaggedJustLift(session: WorkoutSession, isAmrap: Boolean): CompletedSet? = withContext(Dispatchers.IO) {
val existing = queries.selectCompletedSetsBySession(session.id, ::mapToCompletedSet).executeAsList()
.firstOrNull()
if (existing != null) return@withContext existing
@gemini-code-assist
gemini-code-assist Bot
1 minute ago
medium

The ensureCompletedSetForTaggedJustLift implementation returns the existing CompletedSet without updating it if one already exists for the session. This creates a data consistency issue during retagging. If a user changes the exercise tag or AMRAP status, the existing CompletedSet record in the database will retain its original setType and other properties. While the exercise link is updated in the WorkoutSession table, the CompletedSet row should also be synchronized to reflect the latest state (e.g., updating setType if isAmrap changed).

@9thLevelSoftware

Copy link
Copy Markdown
Owner Author

@codex Resolve merge conflicts This branch has conflicts that must be resolved
Use the web editor or the command line to resolve conflicts before continuing.

shared/src/commonMain/kotlin/com/devil/phoenixproject/util/BlePacketFactory.kt
shared/src/commonTest/kotlin/com/devil/phoenixproject/util/BlePacketFactoryTest.kt

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request removes the exercise auto-detection system and introduces a manual tagging feature for Just Lift sessions, allowing users to associate exercises with their sets post-workout. It includes the addition of a new exercise picker dialog and updates to the session summary UI. Furthermore, the BLE packet construction logic was refactored to ensure that target weight and soft-max values are anchored to the selected force rather than being adjusted by per-rep progression. Reviewers identified several instances of hardcoded strings in the new UI components that should be moved to string resources to support localization.

Comment on lines +73 to +103
"Tag exercise",
style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.Bold,
)
},
text = {
Column(
modifier = Modifier
.fillMaxWidth()
.heightIn(max = 520.dp),
) {
OutlinedTextField(
value = searchQuery,
onValueChange = { searchQuery = it },
label = { Text(stringResource(Res.string.search_exercises_short)) },
leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
)

Spacer(modifier = Modifier.height(Spacing.small))

if (filteredExercises.isEmpty()) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(180.dp),
contentAlignment = Alignment.Center,
) {
Text(
"No exercises found",

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.

medium

Several strings in this component are hardcoded. To support localization and maintain consistency with the rest of the project, please move these to string resources (e.g., Res.string.tag_exercise_title, Res.string.no_exercises_found).

Comment on lines +425 to +440
text = taggedExerciseName ?: "Tag exercise",
style = MaterialTheme.typography.titleSmall,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.onSurface,
)
taggedExerciseName?.let {
Text(
text = "Exercise tagged",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}
}
TextButton(onClick = onClick) {
Text(if (taggedExerciseName == null) "Select" else "Change")

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.

medium

The labels in the ExerciseTagSection are hardcoded. Please use string resources for "Tag exercise", "Exercise tagged", "Select", and "Change" to ensure the UI can be localized.

@9thLevelSoftware

Copy link
Copy Markdown
Owner Author

@codex resolve issues gemini-code-assist Bot reviewed 2 minutes ago
gemini-code-assist Bot
left a comment
Code Review
This pull request removes the exercise auto-detection system and introduces a manual tagging feature for Just Lift sessions, allowing users to associate exercises with their sets post-workout. It includes the addition of a new exercise picker dialog and updates to the session summary UI. Furthermore, the BLE packet construction logic was refactored to ensure that target weight and soft-max values are anchored to the selected force rather than being adjusted by per-rep progression. Reviewers identified several instances of hardcoded strings in the new UI components that should be moved to string resources to support localization.

...mmonMain/kotlin/com/devil/phoenixproject/presentation/components/MiniExercisePickerDialog.kt
Comment on lines +73 to +103
"Tag exercise",
style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.Bold,
)
},
text = {
Column(
modifier = Modifier
.fillMaxWidth()
.heightIn(max = 520.dp),
) {
OutlinedTextField(
value = searchQuery,
onValueChange = { searchQuery = it },
label = { Text(stringResource(Res.string.search_exercises_short)) },
leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
)

            Spacer(modifier = Modifier.height(Spacing.small))

            if (filteredExercises.isEmpty()) {
                Box(
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(180.dp),
                    contentAlignment = Alignment.Center,
                ) {
                    Text(
                        "No exercises found",

@gemini-code-assist
gemini-code-assist Bot
2 minutes ago
medium

Several strings in this component are hardcoded. To support localization and maintain consistency with the rest of the project, please move these to string resources (e.g., Res.string.tag_exercise_title, Res.string.no_exercises_found).

@9thLevelSoftware Reply...
shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/screen/SetSummaryCard.kt
Comment on lines +425 to +440
text = taggedExerciseName ?: "Tag exercise",
style = MaterialTheme.typography.titleSmall,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.onSurface,
)
taggedExerciseName?.let {
Text(
text = "Exercise tagged",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}
}
TextButton(onClick = onClick) {
Text(if (taggedExerciseName == null) "Select" else "Change")
@gemini-code-assist
gemini-code-assist Bot
2 minutes ago
medium

The labels in the ExerciseTagSection are hardcoded. Please use string resources for "Tag exercise", "Exercise tagged", "Select", and "Change" to ensure the UI can be localized.

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

ℹ️ 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 +376 to +379
workoutRepository.updateSessionExerciseTag(sessionId, exerciseId, exercise.name)
val taggedSession = session.copy(exerciseId = exerciseId, exerciseName = exercise.name)
completedSetRepository.ensureCompletedSetForTaggedJustLift(taggedSession, isAmrap)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Recompute PRs when tagging a Just Lift session

This tagging path only updates WorkoutSession.exerciseId and inserts a fallback CompletedSet, then immediately syncs, but it never runs PR/gamification evaluation for the newly attributed exercise. In the current flow, PR detection is only executed during saveWorkoutSession() using params.selectedExerciseId; Just Lift sessions are saved with that value unset, so post-set tagging cannot produce new PRs or mark the created set as PR even when the weight/reps beat prior records.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector

Copy link
Copy Markdown

Summary

Testing

  • ./gradlew :shared:commonTest --tests "com.devil.phoenixproject.util.BlePacketFactoryTest" (task :shared:commonTest does not exist).
  • ./gradlew -Pskip.supabase.check=true :shared:commonTest --tests "com.devil.phoenixproject.util.BlePacketFactoryTest" (same missing task).
  • ./gradlew -Pskip.supabase.check=true :shared:test --tests "com.devil.phoenixproject.util.BlePacketFactoryTest" (ambiguous task name :shared:test).
  • ⚠️ ./gradlew -Pskip.supabase.check=true :shared:testAndroidHostTest --tests "com.devil.phoenixproject.util.BlePacketFactoryTest" (blocked by missing Android SDK configuration: ANDROID_HOME/sdk.dir).

View task →

@chatgpt-codex-connector

Copy link
Copy Markdown

Summary

Testing

  • ./gradlew :shared:compileKotlinMetadata (failed due to missing Supabase credentials check in build config).
  • ./gradlew :shared:compileKotlinMetadata -Pskip.supabase.check=true

View task →

@chatgpt-codex-connector

Copy link
Copy Markdown

Summary

Testing

  • ./gradlew :shared:compileKotlinMetadata -q (failed due missing Supabase credentials check in project build logic).
  • ./gradlew :shared:compileKotlinMetadata -Pskip.supabase.check=true -q

View task →

@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: 24687c0fbe

ℹ️ 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 +377 to +381
workoutRepository.updateSessionExerciseTag(sessionId, exerciseId, exercise.name)
val taggedSession = session.copy(exerciseId = exerciseId, exerciseName = exercise.name)
val completedSet = completedSetRepository.ensureCompletedSetForTaggedJustLift(taggedSession, isAmrap)

if (completedSet != null && completedSet.actualReps > 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Prevent awarding PRs when a Just Lift tag is changed

Retagging a Just Lift session can permanently attribute the same lift to multiple exercises because this path always updates PRs for the newly selected exercise without compensating for an existing prior tag on the same session. In the common flow where a user tags exercise A, then taps “Change” and tags exercise B, updatePRsIfBetter can write PR rows for both A and B from one set, which corrupts PR history and leaderboards over time. The tagging path needs to detect retags (session.exerciseId already set and different) and avoid or explicitly reconcile PR side effects before applying the new tag.

Useful? React with 👍 / 👎.

@kilo-code-bot

kilo-code-bot Bot commented May 10, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found (beyond existing comments) | Recommendation: Merge

This PR is a substantial refactoring that removes the exercise auto-detection feature and replaces it with manual exercise tagging for Just Lift sessions.

Key Changes:

  • Deleted: ExerciseDetectionManager, AutoDetectionSheet, DetectionModels.kt, SignatureExtractor, ExerciseClassifier, related tests
  • Added: MiniExercisePickerDialog for manual tagging
  • Added: tagJustLiftSessionExercise functionality with completed set creation and PR updates
  • Fixed: WorkoutHud.kt "PER CABLE" label for live force display
  • Fixed: BlePacketFactory.kt progression regression handling (Issue [Bug]: Wonky Calf Raise starting weight #390 - target weight calculation)

Existing Comments Verified:

  • MiniExercisePickerDialog.kt:103 - existing comment confirmed
  • SetSummaryCard.kt:440 - existing comment confirmed
  • DefaultWorkoutSessionManager.kt:395 - existing comment confirmed

No new issues identified beyond the existing comments.

Files Reviewed (31 files)
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/components/MiniExercisePickerDialog.kt
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/screen/SetSummaryCard.kt
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/manager/DefaultWorkoutSessionManager.kt
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/screen/WorkoutHud.kt
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/util/BlePacketFactory.kt
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/data/repository/CompletedSetRepository.kt
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/data/repository/WorkoutRepository.kt
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/data/repository/SyncRepository.kt
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/data/sync/SyncManager.kt
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/domain/model/Models.kt
  • shared/src/androidMain/kotlin/com/devil/phoenixproject/di/PlatformModule.android.kt
  • shared/src/iosMain/kotlin/com/devil/phoenixproject/di/PlatformModule.ios.kt
  • shared/src/commonMain/sqldelight/com/devil/phoenixproject/database/VitruvianDatabase.sq
  • And 18 test files

Reviewed by laguna-m.1:free · 0 tokens

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

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

ℹ️ 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".

@@ -510,6 +505,10 @@ fun WorkoutTab(
}

is WorkoutState.SetSummary -> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep a tagging path when Just Lift summary is skipped

This new tagging flow is only reachable inside the WorkoutState.SetSummary branch, but ActiveSessionEngine.handleSetCompletion still skips SetSummary and goes straight to Idle when summaryCountdownSeconds < 0 ("Summary OFF"). In that configuration, Just Lift sessions now have no UI path to tag an exercise at all (auto-detection was removed in this commit), so sessions remain untagged and never receive post-tag PR/gamification processing. Please provide a non-summary tagging entry point (or prevent skipping summary until tagging is done) for Just Lift.

Useful? React with 👍 / 👎.

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

ℹ️ 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 +384 to +395
personalRecordRepository.updatePRsIfBetter(
exerciseId = exerciseId,
weightPRWeightPerCableKg = taggedSession.weightPerCableKg,
volumePRWeightPerCableKg = taggedSession.weightPerCableKg,
reps = completedSet.actualReps,
workoutMode = taggedSession.mode,
timestamp = currentTimeMillis(),
profileId = taggedSession.profileId,
cableCount = taggedSession.displayMultiplier,
).onFailure { error ->
Logger.e(error) { "Failed to update PRs while tagging Just Lift session $sessionId" }
}

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 Mark tagged Just Lift completed set as PR when applicable

When a Just Lift session is tagged, this path now invokes updatePRsIfBetter, but it ignores the success payload and never calls markAsPr for the associated CompletedSet. Fresh evidence in this commit is that PR updates are attempted at this call site, yet the set row remains is_pr = false even when a new PR is created, which leaves session history and any CompletedSet.isPr-based UI/reporting inconsistent with the PR table.

Useful? React with 👍 / 👎.

@9thLevelSoftware 9thLevelSoftware merged commit e7cd8c8 into main May 10, 2026
9 of 10 checks passed
@9thLevelSoftware 9thLevelSoftware deleted the codex/stu_experiment branch May 10, 2026 16:09
9thLevelSoftware added a commit that referenced this pull request May 14, 2026
* Refresh Smart Insights now timestamp on fetch (#412)

* Refresh time-sensitive Smart Insights and recompute on clock updates (#418)

* Align Smart Insights nowMs refresh with fetch and timer

* Stabilize Smart Insights timer effect lifecycle

* Remove periodic SmartInsights timer refresh loop

* Harden SmartInsights loading state during refresh

* Avoid mutating compose state from IO dispatcher

* Align SmartInsights query and weekly-volume computation to shared nowMs (Option A) (#413)

* Align insights query and weekly volume with shared nowMs

* Harden insights anchor timestamp lifecycle

* Fix SmartInsights loading guard for null anchor timestamp

* Reset insights anchor before each profile refresh

* Expand SmartSuggestions muscle-group taxonomy and add unknown-group fallback tracking (#415)

* Expand muscle-group aliases and track unknown fallback usage

* Refine unknown muscle-group fallback tracking

* Keep SmartSuggestions fallback tracking stateless

* Resolve review nits in muscle-group fallback docs/tests

* Standardize inclusive cutoff policy (>=) in ReadinessEngine and add boundary tests (#416)

* Define inclusive readiness cutoff policy and add boundary tests

* Refine readiness boundary tests and add exclusion coverage

* Address review cleanup in readiness boundary tests

* Centralize inclusive readiness cutoff check

* Unify insights into Snapshot→Trends→Diagnostics→Actions and add per-card metadata (#417)

* Refactor analytics insights into unified hierarchy

* Address review feedback on insight timeframe badges

* Resolve review feedback on insights cards and cleanup

* Resolve follow-up comments on readiness insight presentation

* Use average intensity for Time-of-Day insights with confidence gating (#414)

* Refine time-of-day insight to average intensity with confidence gating

* Align time insight session-threshold copy with confidence gating

* Add time-of-day tests for distinct-day gating and intensity scoring

* Generalize time-window confidence copy across locales

* Codex/fix high priority bug in insightstab.kt (#420)

* Refactor analytics insights into unified hierarchy

* Address review feedback on insight timeframe badges

* Resolve review feedback on insights cards and cleanup

* Resolve follow-up comments on readiness insight presentation

* Fix missing RoundedCornerShape import in InsightsTab

* Refresh Smart Insights now timestamp on fetch (#419)

* Refresh Smart Insights now timestamp on fetch

* Fix Compose state mutation in IO dispatcher and reset loading state on profile change

Agent-Logs-Url: https://github.com/9thLevelSoftware/Project-Phoenix-MP/sessions/23e4c8a0-93e7-428f-8d00-b84a01429a58

Co-authored-by: 9thLevelSoftware <69057727+9thLevelSoftware@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>

* fix(rep-counter): ignore firmware warmup sync for variable warmup sets (#422)

* Request transient duck audio focus for Android workout cues (#423)

* Fix Android cue audio precedence over background music

* Fix audio focus release timing for workout cues

* Align audio focus attributes with playback usage

* Add focusChangeListener as identity token in requestTransientDuckAudioFocus

Agent-Logs-Url: https://github.com/9thLevelSoftware/Project-Phoenix-MP/sessions/1d2923b8-cdaf-4080-bc22-8839a6940e94

Co-authored-by: 9thLevelSoftware <69057727+9thLevelSoftware@users.noreply.github.com>

* Add focusHoldDurationMs helper and use event-specific delay in abandonAfterDelay

Agent-Logs-Url: https://github.com/9thLevelSoftware/Project-Phoenix-MP/sessions/cd565f28-356a-4acd-9102-45b2602e7e02

Co-authored-by: 9thLevelSoftware <69057727+9thLevelSoftware@users.noreply.github.com>

* Tie ducking focus hold to cue duration metadata

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>

* build: update dependencies and add device metadata to Health Connect

Update Health Connect to the stable 1.1.0 release and include device attribution in synced exercise records.

- **Health**: Update `androidx-health-connect` to `1.1.0` stable and remove alpha-pinning workarounds in `shared/build.gradle.kts`.
- **Health**: Implement `Metadata.activelyRecorded` for `ExerciseSessionRecord` and `TotalCaloriesBurnedRecord` to identify the "Vitruvian Trainer" device.
- **Versions**: Upgrade Kotlin to `2.3.21`, AGP to `9.2.1`, and Gradle to `9.5.0`.
- **Versions**: Bump Compose BOM to `2026.05.00`, Compose Navigation to `2.9.8`, and Ktor to `3.4.3`.

* Revert audio focus duck attempt (#423) — restore audio output for #421 (#428)

* Revert "Request transient duck audio focus for Android workout cues (#423)"

This reverts commit 6371274.

* fix(audio): release MediaPlayer on exception, dedupe sound name lists

Address gemini-code-assist review on PR #428:

- playWithMediaPlayer: hoist `mediaPlayer` declaration out of the `try`
  block and release it from `catch`. Previous structure leaked the native
  player if setVolume/setOnCompletionListener/start threw.
- Extract BADGE_SOUND_NAMES and PR_SOUND_NAMES to private top-level vals.
  Eliminates duplication between the SoundPool-loading `remember` block
  and getRandomBadgeSound/getRandomPRSound, and removes per-call list
  allocations on the random helpers.

---------

Co-authored-by: Claude <noreply@anthropic.com>

* Restore Android workout cue audio routing and ignore code-review artifacts (#429)

* Address PR #429 review feedback: route workout cues to STREAM_MUSIC (#430)

* Address PR #429 review feedback: route cues to STREAM_MUSIC

P1 (chatgpt-codex-connector / gemini-code-assist): the previous change to
USAGE_ASSISTANCE_SONIFICATION routed cues to STREAM_SYSTEM, breaking the
existing volumeControlStream = STREAM_MUSIC contract. Hardware volume
buttons would no longer adjust workout cue loudness, leaving users with
silent or unbalanced cues.

- Switch STANDARD_ANDROID_CUE_USAGE to USAGE_MEDIA so SoundPool and
  MediaPlayer both route through STREAM_MUSIC, matching the activity's
  volumeControlStream and the existing media-volume warning logic.
- Update warnIfMediaVolumeMuted comment/message to reflect USAGE_MEDIA.
- Reword MainActivity volumeControlStream comment to reflect the actual
  scope (entire activity lifetime, not just the workout UI).
- Update structural test assertions to match USAGE_MEDIA and add a guard
  preventing accidental reintroduction of USAGE_ASSISTANCE_SONIFICATION.
- Replace silent fallbacks in HapticFeedbackAudioRoutingGuardTest with
  explicit checks so a missing project root, source file, or raw resource
  directory fails with an actionable message instead of an opaque
  FileNotFoundException or "every cue missing" report.

* Address PR #430 review feedback: harden cue source parser

- gemini-code-assist (high) / chatgpt-codex-connector (P2): the
  multi-line branch in soundNamesInConstantList searched for `\n)`,
  which only matches a closing paren at column 0. Replace with a
  simple `indexOf(")", start)`. Sound names only contain `[a-z0-9_]`
  so the first `)` after `start` is reliably the listOf(...) closer
  regardless of single-line, multi-line, or indented formatting.
- gemini-code-assist (medium): extract the rep-count range
  (`(1..N).mapNotNull`) from the source instead of duplicating
  `1..25`, so the guard stays in sync if the upper bound changes.

Skipped: the suggestion to skip SoundPool initialization on Fire OS
(pre-existing behavior, out of scope for this PR's audio-routing
focus).

---------

Co-authored-by: Claude <noreply@anthropic.com>

* Fix top-level superset reorder persistence and restore audio routing

* Fix Android cue packaging and audio routing (#431)

* Harden Compose layouts for iOS accessibility scaling (#432)

* Harden iOS accessibility layouts for issue 389

* Fix cue audio resource lookup in release builds

* Fix per-cable weight display and softMax handling (#433)

* fix: preserve stall detection in portal sync

* chore: remove stale generated artifacts

* Codex/stu experiment (#436)

* Fix per-cable weight display and softMax handling

* Fix BLE force target to keep progression separate

* Replace Just Lift auto-detection with post-set exercise tagging (#435)

* Fix Just Lift retag PR tracking and completed set sync

* fix: prevent Just Lift retag PR duplication

* test: align AMRAP target weight expectation

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
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