Skip to content

fix: Just Lift muscle-group classification + retroactive/prompted exercise tagging#483

Merged
9thLevelSoftware merged 7 commits into
mainfrom
fix/just-lift-classification-and-tagging
May 30, 2026
Merged

fix: Just Lift muscle-group classification + retroactive/prompted exercise tagging#483
9thLevelSoftware merged 7 commits into
mainfrom
fix/just-lift-classification-and-tagging

Conversation

@9thLevelSoftware

Copy link
Copy Markdown
Owner

Summary

Fixes the root cause of the portal Body tab showing everything as "General" and exercises showing as "Unknown Exercise", and makes "Just Lift" (freestyle) sessions taggable so they stop producing unlabeled data.

Data fixes (baseline commits):

  • Mobile push previously hardcoded muscle_group = "General" for every session exercise, collapsing the portal's Muscle Distribution/Radar into a single bucket. Now resolves the real catalog muscle group via a new SyncRepository.getExerciseMuscleGroup lookup (id → name → case-insensitive); defaults to "General" only for ad-hoc movements.
  • Untagged Just Lift sessions synced with a null session name after PR Replace Just Lift auto-detection with post-set exercise tagging #435 removed exercise auto-detection. PortalSyncAdapter now falls back to "Just Lift" when the session is isJustLift with no routine/exercise name.

Tagging UX (new) — makes untagged Just Lift sessions recoverable:

  • #8a Retroactive tagging from the History view (generalized the SetSummaryCard tag-section gate; wired both HistoryTab render sites + the AnalyticsScreen callback). Reuses the existing tagJustLiftSessionExercise backend.
  • #8b A one-time, skippable "Tag this lift?" prompt when tapping Done on an untagged Just Lift live summary (UI-layer interception in WorkoutTab; no state-machine changes).
  • #8c Made the untagged tag affordance prominent (primaryContainer + FilledTonalButton); tagged state left calm.

Background

"Unknown Exercise" = untagged Just Lift sessions in the post-detection world (auto-detection was replaced with manual tagging in PR #435). These changes restore meaningful labels going forward and let the user salvage the existing backlog. A companion portal change relabels "Unknown Exercise" → "Just Lift (untagged)" and classifies the Body tab by exercise name; the historical exercises.muscle_group rows were backfilled.

⚠️ Verification status

All three tagging features are Compose UI that has NOT been run on a device/emulator. Each passed spec + code-quality review and an integration review, and the module compiles + unit tests pass (./gradlew :shared:testAndroidHostTest → BUILD SUCCESSFUL). Runtime/visual behavior is unverified — see Test Plan.

Test Plan (on-device)

  • History retro-tagging — From History (standalone Just Lift cards AND the Analytics-hosted History tab), tag an old untagged session: name updates, the affordance disappears on re-expand, a completed set/PR is created, and it no longer syncs as "Unknown Exercise".
  • #8b prompt — Finish an untagged Just Lift set with reps: prompt appears; "Tag" opens the picker and persists; "Skip"/back/scrim dismiss proceeds; re-pressing Done does NOT re-prompt; zero-rep / already-tagged / routine sets never prompt.
  • Autoplay interaction — With the summary auto-continue countdown enabled, an untagged Just Lift set's countdown expiry pops the prompt (rather than silently advancing); resolving once lets the next Done proceed.
  • #8c prominence — Untagged affordance shows primaryContainer + "Tag exercise" filled button + "Label this lift to track it"; tapping opens the picker exactly once.
  • No regression — Routine sets and single-exercise sets show no tag affordance; Done advances exactly as before (Next Set / Next Exercise / Complete Routine).
  • Muscle classification — After a sync, the portal Body tab shows a distributed muscle breakdown (not 100% General).

Notes

  • New strings in #8b/#8c are hardcoded English, consistent with the existing Just Lift tagging surface (localizing only these would be inconsistent; whole-surface i18n is separate scope).
  • GroupedRoutineCard's tag block is defensive/unreachable today (Just Lift sessions are always standalone) — harmless.

🤖 Generated with Claude Code

9thLevelSoftware and others added 5 commits May 29, 2026 22:38
…eral

Mobile push hardcoded muscle_group="General" for every session exercise (SyncManager), collapsing the portal Body tab into a single bucket. Resolve the real catalog muscle group via a new SyncRepository.getExerciseMuscleGroup lookup (id -> exact name -> case-insensitive name); default to "General" only for ad-hoc movements not in the catalog.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
After PR #435 removed Just Lift exercise auto-detection, untagged freestyle sessions synced with a null session name. PortalSyncAdapter now falls back to "Just Lift" when the session isJustLift and has no routine/exercise name.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Untagged Just Lift sessions (isJustLift=true, null exerciseId) could only be
tagged during the live workout summary, leaving older ones stuck as "Unknown
Exercise" forever. This reuses the existing live tagging mechanism so users can
tag them from HistoryTab.

- SetSummaryCard: drop the `!isHistoryView` clause gating the tag section so
  visibility is driven purely by `isJustLiftTaggingEnabled` (caller decides).
- HistoryTab: thread an `onTagJustLiftSessionExercise` callback into both
  SetSummaryCard sites (WorkoutHistoryCard and GroupedRoutineCard), wiring
  per-session picker state and MiniExercisePickerDialog. The affordance shows
  only for untagged Just Lift sessions (isJustLift && exerciseId.isNullOrBlank()).
- AnalyticsScreen: pass MainViewModel::tagJustLiftSessionExercise to HistoryTab.

isAmrap is defaulted to false: WorkoutSession persists no amrap flag and the
value only affects cosmetic CompletedSet.setType labeling. The history list
refreshes automatically via the SQLDelight-backed groupedWorkoutHistory
StateFlow; no manual reload needed.

Verified: ./gradlew :shared:testAndroidHostTest — BUILD SUCCESSFUL (compiles +
unit tests pass). Not device-verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When the user taps Done/Continue on a Just Lift set summary that is still
untagged and has real reps (repCount > 0), surface a one-time AlertDialog
nudging them to tag the exercise (reusing the existing MiniExercisePickerDialog
and onTagJustLiftSessionExercise path). "Tag" opens the picker; "Skip" or
dismiss proceeds exactly as before via onProceedFromSummary().

UI-layer only (WorkoutTab.kt). The workout state machine, proceedFromSummary,
DefaultWorkoutSessionManager, and ActiveSessionEngine are unchanged. The prompt
is one-time per summary (tagPromptResolved keyed on summarySessionId), so there
is no nag loop, and the skip path preserves existing save/reset behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
UNTAGGED state now uses primaryContainer background with onPrimaryContainer
content colour, stronger title/subtitle copy, and a FilledTonalButton("Tag
exercise") CTA. TAGGED state is unchanged (muted surfaceContainerHigh, calm
TextButton("Change")). No signature or behaviour change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 30, 2026 03:14

@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 the ability to retroactively tag "Just Lift" sessions with an exercise from the history tab, adds a one-time nudge dialog prompting users to tag untagged "Just Lift" sets before dismissing the workout summary, and resolves real muscle groups from the exercise catalog during sync push instead of hardcoding "General". Feedback on these changes suggests ensuring that ID-based lookups in SqlDelightSyncRepository do not fall back to name-based lookups if the muscle group is null, avoiding auto-advancing the summary screen when dismissing the tag prompt dialog by tapping outside, and externalizing hardcoded strings in the dialog to shared resources for internationalization.

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: 3dd1361e1e

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

@kilo-code-bot

kilo-code-bot Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor

Code Review Roast 🔥

Verdict: No Issues Found | Recommendation: Merge

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

📊 Overall: Like a first pancake — the shape was wrong before, but someone actually flipped it and now it's golden.

Files Reviewed (10 files)
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/data/repository/SqlDelightSyncRepository.kt - 0 issues
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/data/repository/SyncRepository.kt - 0 issues
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/data/sync/PortalSyncAdapter.kt - 0 issues
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/data/sync/SyncManager.kt - 0 issues
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/screen/AnalyticsScreen.kt - 0 issues
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/screen/HistoryTab.kt - 0 issues
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/screen/SetSummaryCard.kt - 0 issues
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/screen/WorkoutTab.kt - 0 issues
  • shared/src/commonTest/kotlin/com/devil/phoenixproject/testutil/FakeSyncRepository.kt - 0 issues (fixed in this commit)
  • shared/src/commonTest/kotlin/com/devil/phoenixproject/testutil/FakeSyncRepositoryTest.kt - 0 issues (new test file)
  • shared/src/androidHostTest/kotlin/com/devil/phoenixproject/data/repository/SqlDelightSyncRepositoryTest.kt - 0 issues (test file)

Reviewed by laguna-m.1-20260312:free · 408,346 tokens

@9thLevelSoftware 9thLevelSoftware merged commit 289c94f into main May 30, 2026
9 checks passed
@9thLevelSoftware 9thLevelSoftware deleted the fix/just-lift-classification-and-tagging branch May 30, 2026 04:19
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