Skip to content

feat(#665): add Previously completed exercise filter to Single Exercise, Routine, and Profile#669

Merged
9thLevelSoftware merged 1 commit into
mainfrom
feat/issue-665-previously-completed-filter
Jul 21, 2026
Merged

feat(#665): add Previously completed exercise filter to Single Exercise, Routine, and Profile#669
9thLevelSoftware merged 1 commit into
mainfrom
feat/issue-665-previously-completed-filter

Conversation

@9thLevelSoftware

Copy link
Copy Markdown
Owner

Summary

Implements the signed-off enhancement for Issue #665: a shared, off-by-default Previously completed filter chip in the exercise picker, wired to exactly three callers.

What this adds

A Previously completed filter chip in the exercise picker filter shelf (after Favorites/Custom, before the muscle/equipment divider). When selected, it narrows the exercise list to only exercises the active profile has actually completed in workout history.

Affected flows

  1. Single Exercise — exercise picker with the new filter
  2. Routine Editor → Add exercise — exercise picker with the new filter
  3. Profile → Exercise Insights — exercise picker with the new filter (no custom exercise creation)

Key design decisions

  • Default off: the chip is never selected when a picker opens
  • Profile-safe: uses flatMapLatest on activeProfileContext to prevent cross-profile ID leaks during profile switches
  • Shared predicate: one pure AND-composed filter helper (filterExercisePickerCandidates) for all narrowing filters
  • No backend changes: reuses existing getHistoryVisibleSessions query
  • Qualifying criteria: profileId == active profile AND deletedAt IS NULL AND (workingReps > 0 OR totalReps > 0) AND exerciseId is not blank

Changes (18 files, +521/-148)

New files

  • CompletedExerciseIds.kt — profile-tagged state + history ID extraction
  • ExercisePickerFilters.kt — pure AND-composed filter predicate
  • ExercisePickerFiltersTest.kt — filter contract tests
  • FakeWorkoutRepositoryTest.kt — profile-scoped fake coverage

Modified files

  • ExercisePicker.kt — opt-in params, loading state, shared filter wiring
  • ExerciseFilterShelf.kt — Previously completed chip
  • MainViewModel.kt / ProfileViewModel.kt — profile-safe completed-history state
  • SingleExerciseScreen.kt / RoutineEditorScreen.kt / ProfileScreen.kt — caller opt-ins
  • FakeWorkoutRepository.kt — profile-scoped + positive-rep filtering
  • 6 locale string bundles (en, de, es, fr, it, nl)

Testing

JAVA_HOME=/opt/homebrew/opt/openjdk@17 ANDROID_HOME=/opt/homebrew/share/android-commandlinetools ./gradlew spotlessCheck :shared:testAndroidHostTest :androidApp:assembleDebug   -Pskip.supabase.check=true --console=plain

Result: BUILD SUCCESSFUL — all tests pass, spotless check passes, debug APK assembles.

Visual Evidence

Final emulator screenshots captured on phoenix_phantom_api36 (API 36):

  • 01-single-exercise-default-off.png — default state, chip unselected
  • 02-single-exercise-filtered.png — chip selected, empty state with Clear recovery
  • 03-routine-add-exercise-default.png — Routine Editor picker, chip visible
  • 04-routine-add-exercise-filtered.png — chip selected, empty state
  • 05-profile-insights-default.png — Profile Insights picker, no custom exercise button
  • 06-profile-insights-filtered.png — chip selected, empty state with Clear recovery

Evidence stored in pipeline artifact directory: ~/.hermes/phoenix-enhancements/issue-665-1527694753049874593/final-visuals/

Release Notes

Previously completed exercise filter — A new filter chip in the exercise picker lets you quickly find exercises you have actually done before. Available in Single Exercise, Routine Editor, and Profile Exercise Insights. Off by default; tap the chip to activate.

Closes #665

…se, Routine, and Profile

Implements the signed-off enhancement for Issue #665: a shared, off-by-default
'Previously completed' filter chip in the exercise picker, wired to exactly three
callers: Single Exercise, Routine Editor add-exercise, and Profile Exercise Insights.

Changes:
- CompletedExerciseIds.kt: profile-tagged state + history ID extraction
- ExercisePickerFilters.kt: pure AND-composed filter predicate
- ExerciseFilterShelf: Previously completed chip (after Custom, before muscle divider)
- MainViewModel/ProfileViewModel: flatMapLatest profile-safe completed-history state
- SingleExerciseScreen/RoutineEditorScreen/ProfileScreen: opt-in wiring
- FakeWorkoutRepository: profile-scoped + positive-rep getHistoryVisibleSessions
- Locale strings in all 6 bundles (en, de, es, fr, it, nl)
- Focused tests: ExercisePickerFiltersTest, FakeWorkoutRepositoryTest

Closes #665
@kilo-code-bot

kilo-code-bot Bot commented Jul 21, 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, and the worst I can find is some whitespace churn and a pre-existing UX bug in unchanged code that this PR accidentally exposes.

Overview

Severity Count
🚨 critical 0
⚠️ warning 0
💡 suggestion 0
🤏 nitpick 0
Issue Details (click to expand)

No correctness or safety findings on changed lines. One summary-only follow-up:

  • GroupedExerciseList.kt:209-265 (unchanged): ExerciseListEmptyState checks hasActiveFilters before isLoading. With the new "Previously completed" filter, when a user toggles it on while completedExerciseIdsState is still loading (e.g., immediately after a profile switch via ActiveProfileContext.Switching), the picker shows "No exercises found" + Clear filters instead of the loading spinner. Fix is one branch reorder — isLoading should win over hasActiveFilters — but the file is unchanged by this PR, so it's a follow-up.

🏆 Best part: The CompletedExerciseIdsState data class with a tagged profileId + isLoading sentinel is genuinely good defensive coding. The flatMapLatest outer/inner split plus takeIf { profileId == activeProfileId } ?: emptySet() at every call site means a stale profile can never bleed IDs into the wrong picker's filter — even mid-switch. Senior-level paranoia, and exactly the right paranoia.

💀 Worst part: The whitespace churn in ProfileScreen.kt adds ~15 blank lines inside when branches. It's not wrong, it's just noise that inflates the diff and makes review harder. A future reviewer will scroll past it wondering if something changed.

📊 Overall: Like a well-rehearsed stage play — three actors (SingleExercise, RoutineEditor, Profile) hit their marks identically, the cue sheet (ExercisePickerFilters.kt) is shared, and the understudy (FakeWorkoutRepository) actually matches the lead's script this time (profileId + rep guard).

Files Reviewed (18 files)
  • shared/src/commonMain/composeResources/values-de/strings.xml
  • shared/src/commonMain/composeResources/values-es/strings.xml
  • shared/src/commonMain/composeResources/values-fr/strings.xml
  • shared/src/commonMain/composeResources/values-it/strings.xml
  • shared/src/commonMain/composeResources/values-nl/strings.xml
  • shared/src/commonMain/composeResources/values/strings.xml
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/components/ExercisePicker.kt
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/components/exercisepicker/CompletedExerciseIds.kt
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/components/exercisepicker/ExerciseFilterShelf.kt
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/components/exercisepicker/ExercisePickerFilters.kt
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/screen/ProfileScreen.kt
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/screen/RoutineEditorScreen.kt
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/screen/SingleExerciseScreen.kt
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/viewmodel/MainViewModel.kt
  • shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/viewmodel/ProfileViewModel.kt
  • shared/src/commonTest/kotlin/com/devil/phoenixproject/presentation/components/exercisepicker/ExercisePickerFiltersTest.kt
  • shared/src/commonTest/kotlin/com/devil/phoenixproject/testutil/FakeWorkoutRepository.kt
  • shared/src/commonTest/kotlin/com/devil/phoenixproject/testutil/FakeWorkoutRepositoryTest.kt

Ponytail: Lean already. Ship.
Ponytail net: 0 lines.


Reviewed by minimax-m3 · Input: 115.3K · Output: 34.5K · Cached: 5M

@9thLevelSoftware
9thLevelSoftware merged commit 7935b37 into main Jul 21, 2026
10 checks passed
@9thLevelSoftware
9thLevelSoftware deleted the feat/issue-665-previously-completed-filter branch July 22, 2026 00:07
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.

feat: filter exercise pickers to previously completed exercises

1 participant