Skip to content

Fix dynamic % of PR routine weights and saved snapshots#472

Merged
9thLevelSoftware merged 3 commits into
mainfrom
codex/investigate-issue-471
May 24, 2026
Merged

Fix dynamic % of PR routine weights and saved snapshots#472
9thLevelSoftware merged 3 commits into
mainfrom
codex/investigate-issue-471

Conversation

@9thLevelSoftware

Copy link
Copy Markdown
Owner

Summary

  • Sync % of PR routine set weights in the editor to the latest resolved PR values so stale 5 kg / 11 lb snapshots no longer leak into visible sets or warm-up preview input.
  • Preserve per-set % of PR metadata while saving resolved absolute weights as fallback snapshots for weightPerCableKg and setWeightsPerCableKg.
  • Fix half-kilogram rounding to use nearest rounding instead of truncation.
  • Add regression coverage for editor syncing, rounding, and routine-load resolution against the current PR.

Testing

  • Added/updated host and common tests for % of PR editor sync, saved snapshot resolution, nearest-half-kg rounding, and routine load resolution from current PR.
  • Verified with Gradle: :shared:testAndroidHostTest, :shared:compileKotlinMetadata, and :shared:check.

Copilot AI review requested due to automatic review settings May 24, 2026 21:25
@9thLevelSoftware 9thLevelSoftware linked an issue May 24, 2026 that may be closed by this pull request
2 tasks
@9thLevelSoftware 9thLevelSoftware linked an issue May 24, 2026 that may be closed by this pull request
2 tasks

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.

@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 introduces logic to synchronize exercise set weights based on a percentage of the user's Personal Record (PR). Key changes include the addition of setWeightsPercentOfPR to track per-set percentages, logic to normalize and sync these weights when the PR or global percentage changes, and updated rounding logic using roundToInt for half-kilogram increments. Comprehensive tests were added to verify synchronization and rounding behavior. Feedback focuses on preventing the loss of custom per-set percentages when adjusting the global slider, handling edge cases where manual weight edits occur before a PR is loaded, and optimizing the updateWeight function to avoid redundant state updates and potential UI flickering.

Comment on lines 328 to 335
fun onWeightPercentOfPRChange(percent: Int) {
_weightPercentOfPR.value = percent.coerceIn(50, 120)
val coercedPercent = percent.coerceIn(50, 120)
_weightPercentOfPR.value = coercedPercent
if (_usePercentOfPR.value) {
setWeightsPercentOfPR = List(_sets.value.size) { coercedPercent }
syncSetWeightsToPercentOfPR()
}
}

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

Changing the global weightPercentOfPR while _usePercentOfPR is enabled will overwrite all individual set percentages with the new global value. If a user has carefully customized specific sets (e.g., Set 1 at 80%, Set 2 at 90%), moving the global slider will erase these customizations without warning.

It might be safer to only use _weightPercentOfPR as a default for new sets, or to prompt the user before performing a bulk overwrite of custom per-set percentages.

Comment on lines +456 to +466
private fun updateSetPercentFromDisplayWeight(setIndex: Int, displayWeight: Float) {
val pr = _currentExercisePR.value ?: return
if (!_usePercentOfPR.value || pr.weightPerCableKg <= 0f) return

ensureSetWeightPercentages()
val weightKg = displayToKg(displayWeight, weightUnit)
val percent = ((weightKg / pr.weightPerCableKg) * 100f).roundToInt().coerceIn(50, 120)
setWeightsPercentOfPR = setWeightsPercentOfPR.mapIndexed { index, existing ->
if (index == setIndex) percent else existing
}
}

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

There is a potential for inconsistent state if a user edits a weight while _usePercentOfPR is enabled but the personal record (PR) has not yet loaded (e.g., due to network latency or being offline). In this scenario, updateSetPercentFromDisplayWeight returns early without updating setWeightsPercentOfPR. Consequently, the UI shows the manually entered weight, but the underlying percentage remains stale. If the PR loads later or the routine is saved, the manual edit may be lost or saved alongside an unrelated percentage.

Consider stashing the manual weight to resolve the percentage once the PR becomes available, or providing a visual indication that edits cannot be converted to percentages while offline.

Comment on lines 474 to 483
fun updateWeight(setId: String, weight: Float) {
val setIndex = _sets.value.indexOfFirst { it.id == setId }
_sets.value = _sets.value.map { set ->
if (set.id == setId) set.copy(weightPerCable = weight) else set
}
if (setIndex >= 0 && _usePercentOfPR.value) {
updateSetPercentFromDisplayWeight(setIndex, weight)
syncSetWeightsToPercentOfPR()
}
}

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

When _usePercentOfPR is enabled, updating a set's weight currently triggers a double update to the _sets state flow. First, the raw weight is applied, and then syncSetWeightsToPercentOfPR() overwrites it with a value recalculated from the newly derived percentage. This can cause UI flickering or cursor jumps in the text field if the recalculated weight differs slightly due to rounding (e.g., typing 38.1 might snap back to 38.0).

Consider optimizing this to a single update by only performing the manual weight update if the percentage-based sync cannot be performed.

    fun updateWeight(setId: String, weight: Float) {
        val setIndex = _sets.value.indexOfFirst { it.id == setId }
        if (setIndex < 0) return

        val pr = _currentExercisePR.value
        if (_usePercentOfPR.value && pr != null && pr.weightPerCableKg > 0f) {
            updateSetPercentFromDisplayWeight(setIndex, weight)
            syncSetWeightsToPercentOfPR()
        } else {
            _sets.value = _sets.value.map { set ->
                if (set.id == setId) set.copy(weightPerCable = weight) else set
            }
        }
    }

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

ℹ️ 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 +568 to +570
if (_usePercentOfPR.value) {
setWeightsPercentOfPR = setWeightsPercentOfPR.filterIndexed { i, _ -> i != index }
syncSetWeightsToPercentOfPR()

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 per-set PR percentages aligned after set deletion

When % of PR is temporarily disabled, deleteSet does not update setWeightsPercentOfPR because of the _usePercentOfPR guard, so re-enabling % of PR later can shift percentages onto the wrong sets. For example, deleting the first set from [80, 90, 100] while disabled leaves the stored list unchanged, and re-enable normalization truncates it to [80, 90] instead of preserving [90, 100], which produces incorrect resolved loads and saves mismatched per-set metadata.

Useful? React with 👍 / 👎.

@kilo-code-bot

kilo-code-bot Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

Code Review Roast 🔥

Verdict: No Issues Found | Recommendation: Merge

Oh wait, this PR is actually clean. I need to sit down. I had my flamethrower warmed up and everything.

📊 Overall: Like finding a unicorn in production — this is solid, test-driven Kotlin with proper state management and no obvious bugs. The percent-of-PR feature shows thoughtful design with:

  • Pending edits that resolve when PR becomes available
  • Proper half-kg rounding using roundToInt() instead of truncation
  • Clean separation between pending edits and resolved weights
  • Comprehensive test coverage for edge cases

Reviewed by laguna-m.1-20260312:free · 443,800 tokens

@9thLevelSoftware 9thLevelSoftware merged commit 959d6bc into main May 24, 2026
5 checks passed
@9thLevelSoftware 9thLevelSoftware deleted the codex/investigate-issue-471 branch May 24, 2026 21:49

@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: 3621eea65f

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

ensureSetWeightPercentages()
if (setWeightsPercentOfPR.all { it == previousPercent }) {
setWeightsPercentOfPR = List(_sets.value.size) { coercedPercent }
pendingPercentOfPREditWeights.clear()

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 pending per-set edits when global % changes

When PR data is not loaded yet, updateWeight stores manual per-set edits in pendingPercentOfPREditWeights, but this map is cleared here whenever all stored percentages match the previous global value. In the common sequence "edit one set -> change global % -> PR loads later", that clear drops the pending custom edit and the later PR sync rewrites the set to the new global percent, silently losing user input in delayed/offline PR lookup scenarios.

Useful? React with 👍 / 👎.

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.

[Bug]: Percent of PR doesn't directly modify routine weights

2 participants