From c217e50b28607a37a63c21557811ddd687590021 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 21 Aug 2026 12:30:33 -0400 Subject: [PATCH 1/2] fix: remove remaining Vitruvian IP outliers after catalogue swap E2E audit follow-up to #706/#708 for the Vitruvian takedown request. HEAD was already clean of the exercise dataset, the mux/jwplayer streaming stack and the decompilation docs; this removes what survived: - Drop stale "572 exercises with video" copy (TESTFLIGHT_NOTES, docs/index.html), add non-affiliation line to TestFlight notes. - Delete final-visuals/ QA screenshots that render the old catalogue (three show a live stream.mux.com playback URL). Unreferenced by code. - Migration 39: replace the six legacy catalogue IDs (+ catalogue-name comments) with the equivalent name-based heal on non-custom rows; drop the redundant RoutineExercise ID block. Idempotent, data-only; Kotlin mirror and SchemaParityTest updated to synthetic IDs. - Reword ~70 comments/identifiers that cited the official app or decompiled internals as the source of behaviour (BLE/protocol, diagnostics, UI, tests, third-party Kable patch, almanac). createOfficialStopPacket() -> createSoftStopPacket(); DiagnosticFaultCategory.VITRUVIAN("Vee") -> CONTROLLER("Controller"); fault labels re-authored in Phoenix wording. - Delete HardwareValidationTest scaffold (described the official app's Sample struct); update BlePacketCapture comments. - Remove dead media3/HLS version-catalog aliases and unused video strings; "video" wording -> demo images in en/de/es/fr/nl. - Neutralise safe branding strings (backup filenames, export headers, install guides, bug template, disconnect prompt); persisted names (vitruvian.db, vitruvian_preferences, video_playback key) untouched. - LegacyCatalogueIdMap KDoc now states keys are migration-only opaque IDs. Verified: :shared:testAndroidHostTest + :androidApp:testDebugUnitTest + verifyCommonMainVitruvianDatabaseMigration -> 3,724 tests, 0 failures. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01RpYk6iyjaMzC4DjiMEBwvK --- .almanac/pages/machine-diagnostics.md | 2 +- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .phoenix-review/BUG-FIX-PLAN.md | 2 +- .phoenix-review/CONSOLIDATED-REPORT.md | 2 +- .phoenix-review/utilities-part-2.md | 4 +- ANDROID_INSTALL.md | 10 +- androidApp/src/main/res/xml/backup_rules.xml | 2 +- .../main/res/xml/data_extraction_rules.xml | 2 +- docs/index.html | 10 +- ...06-16-per-cable-weight-handling-design.md} | 8 +- final-visuals/android/01-master-off.png | Bin 362808 -> 0 bytes final-visuals/android/02-master-on.png | Bin 349905 -> 0 bytes final-visuals/android/03-master-vulgar-on.png | Bin 299911 -> 0 bytes final-visuals/android/04-modal.png | Bin 289972 -> 0 bytes .../android/05-dominatrix-unlocked.png | Bin 313825 -> 0 bytes .../android/ux-phase1/01-home-dark.png | Bin 119281 -> 0 bytes .../android/ux-phase1/01-home-light.png | Bin 121135 -> 0 bytes .../android/ux-phase1/02-just-lift-dark.png | Bin 166450 -> 0 bytes .../android/ux-phase1/02-just-lift-light.png | Bin 171446 -> 0 bytes .../ux-phase1/03-exercise-picker-dark.png | Bin 333163 -> 0 bytes .../ux-phase1/03-exercise-picker-light.png | Bin 331576 -> 0 bytes .../android/ux-phase1/04-routines-dark.png | Bin 119583 -> 0 bytes .../android/ux-phase1/04-routines-light.png | Bin 126364 -> 0 bytes .../android/ux-phase1/05-history-dark.png | Bin 204563 -> 0 bytes .../android/ux-phase1/05-history-light.png | Bin 210452 -> 0 bytes .../android/ux-phase1/06-settings-dark.png | Bin 238047 -> 0 bytes .../android/ux-phase1/06-settings-light.png | Bin 251594 -> 0 bytes .../ux-phase1/07-integrations-dark.png | Bin 223963 -> 0 bytes .../ux-phase1/07-integrations-light.png | Bin 244514 -> 0 bytes .../android/ux-phase2/01-home-dark.png | Bin 121754 -> 0 bytes .../android/ux-phase2/01-home-light.png | Bin 122102 -> 0 bytes .../android/ux-phase2/02-just-lift-dark.png | Bin 169765 -> 0 bytes .../android/ux-phase2/02-just-lift-light.png | Bin 173861 -> 0 bytes .../android/ux-phase2/03-analytics-dark.png | Bin 204742 -> 0 bytes .../android/ux-phase2/03-analytics-light.png | Bin 210296 -> 0 bytes .../ux-phase2/04-smart-insights-dark.png | Bin 222470 -> 0 bytes .../ux-phase2/04-smart-insights-light.png | Bin 222807 -> 0 bytes .../android/ux-phase2/05-settings-dark.png | Bin 239491 -> 0 bytes .../android/ux-phase2/05-settings-light.png | Bin 251466 -> 0 bytes .../ux-phase2/06-training-cycles-dark.png | Bin 135593 -> 0 bytes .../06-training-cycles-editor-dark.png | Bin 220341 -> 0 bytes .../06-training-cycles-editor-light.png | Bin 224778 -> 0 bytes .../ux-phase2/06-training-cycles-light.png | Bin 140827 -> 0 bytes .../ux-phase2/07-exercise-picker-dark.png | Bin 334383 -> 0 bytes .../ux-phase2/07-exercise-picker-light.png | Bin 331890 -> 0 bytes .../android/ux-phase3/cards/badges-dark.png | Bin 237547 -> 0 bytes .../android/ux-phase3/cards/badges-light.png | Bin 223064 -> 0 bytes .../ux-phase3/cards/cycles-dark-empty.png | Bin 135797 -> 0 bytes .../ux-phase3/cards/cycles-light-empty.png | Bin 142488 -> 0 bytes .../ux-phase3/cards/history-dark-empty.png | Bin 105190 -> 0 bytes .../ux-phase3/cards/history-light-empty.png | Bin 110340 -> 0 bytes .../android/ux-phase3/cards/insights-dark.png | Bin 228602 -> 0 bytes .../cards/insights-dashboard-dark.png | Bin 205454 -> 0 bytes .../cards/insights-dashboard-light.png | Bin 211750 -> 0 bytes .../ux-phase3/cards/insights-light.png | Bin 229471 -> 0 bytes .../ux-phase3/cards/integrations-dark.png | Bin 225707 -> 0 bytes .../ux-phase3/cards/integrations-light.png | Bin 246850 -> 0 bytes .../android/ux-phase3/delete-dialog-dark.png | Bin 218737 -> 0 bytes .../android/ux-phase3/delete-dialog-light.png | Bin 221432 -> 0 bytes .../android/ux-phase3/echo-selector-dark.png | Bin 169817 -> 0 bytes .../android/ux-phase3/echo-selector-light.png | Bin 169407 -> 0 bytes .../ux-phase3/integrations-spinners-dark.png | Bin 227840 -> 0 bytes .../ux-phase3/integrations-spinners-light.png | Bin 241116 -> 0 bytes .../android/ux-phase3/justlift-dark.png | Bin 172447 -> 0 bytes .../android/ux-phase3/justlift-light.png | Bin 170813 -> 0 bytes .../android/ux-phase3/routines-dark.png | Bin 123671 -> 0 bytes .../android/ux-phase3/routines-light.png | Bin 123037 -> 0 bytes .../android/ux-phase3/smartinsights-dark.png | Bin 232729 -> 0 bytes .../android/ux-phase3/smartinsights-light.png | Bin 224961 -> 0 bytes .../ux-phase3/workoutsetup-picker-dark.png | Bin 338562 -> 0 bytes .../ux-phase3/workoutsetup-picker-light.png | Bin 329625 -> 0 bytes .../android/ux-phase4a/backup-dialog-dark.png | Bin 240665 -> 0 bytes .../ux-phase4a/backup-dialog-light.png | Bin 275039 -> 0 bytes .../ux-phase4a/banner-disconnected-dark.png | Bin 121637 -> 0 bytes .../ux-phase4a/banner-disconnected-light.png | Bin 121244 -> 0 bytes .../ux-phase4a/disconnect-confirm-dark.png | Bin 227840 -> 0 bytes .../ux-phase4a/disconnect-confirm-light.png | Bin 241116 -> 0 bytes .../android/ux-phase4a/eula-age-row-dark.png | Bin 323146 -> 0 bytes .../android/ux-phase4a/eula-age-row-light.png | Bin 316693 -> 0 bytes .../ux-phase4a/exercise-edit-sheet-dark.png | Bin 138364 -> 0 bytes .../ux-phase4a/exercise-edit-sheet-light.png | Bin 142685 -> 0 bytes .../routine-editor-discard-dark.png | Bin 116570 -> 0 bytes .../routine-editor-discard-light.png | Bin 124690 -> 0 bytes .../android/ux-phase4b/00-home-baseline.png | Bin 123360 -> 0 bytes .../01-assessment-entry-progress-tab.png | Bin 170153 -> 0 bytes .../01b-assessment-picker-step1of4.png | Bin 279090 -> 0 bytes .../ux-phase4b/02a-single-exercise-list.png | Bin 338562 -> 0 bytes .../02b-exercise-detail-altbenchpress.png | Bin 140490 -> 0 bytes .../ux-phase4b/03a-routine-overview.png | Bin 142163 -> 0 bytes .../03b-routine-overview-inworkout.png | Bin 73265 -> 0 bytes .../ux-phase4b/03c-exit-dialog-sysback.png | Bin 87000 -> 0 bytes .../ux-phase4b/03d-exit-dialog-topbarback.png | Bin 87000 -> 0 bytes .../ux-phase4b/03e-exit-dialog-stopbutton.png | Bin 87000 -> 0 bytes .../ux-phase4b/05a-justlift-setready.png | Bin 172496 -> 0 bytes .../06a-routine-overview-withexercise.png | Bin 260779 -> 0 bytes ...utine-overview-withexercise-bleblocked.png | Bin 260779 -> 0 bytes .../06c-exit-dialog-overview-withexercise.png | Bin 224253 -> 0 bytes .../android/ux-phase5a/badges-streak-dark.png | Bin 242044 -> 0 bytes .../ux-phase5a/badges-streak-light.png | Bin 226447 -> 0 bytes .../android/ux-phase5a/daystrip-dark.png | Bin 272648 -> 0 bytes .../android/ux-phase5a/daystrip-light.png | Bin 302092 -> 0 bytes .../ux-phase5a/diagnostics-waiting-dark.png | Bin 100966 -> 0 bytes .../ux-phase5a/diagnostics-waiting-light.png | Bin 103124 -> 0 bytes .../android/ux-phase5a/empty-state-dark.png | Bin 105995 -> 0 bytes .../android/ux-phase5a/empty-state-light.png | Bin 108913 -> 0 bytes .../exercise-config-header-dark.png | Bin 134781 -> 0 bytes .../exercise-config-header-light.png | Bin 137349 -> 0 bytes .../android/ux-phase5a/home-gradient-dark.png | Bin 122820 -> 0 bytes .../ux-phase5a/home-gradient-light.png | Bin 123852 -> 0 bytes .../ux-phase5a/reducemotion-badges-dark.png | Bin 242396 -> 0 bytes .../ux-phase5a/reducemotion-badges-light.png | Bin 227167 -> 0 bytes .../reducemotion-empty-state-dark.png | Bin 105995 -> 0 bytes .../reducemotion-empty-state-light.png | Bin 108913 -> 0 bytes .../ux-phase5a/routine-groupheader-dark.png | Bin 111782 -> 0 bytes .../ux-phase5a/routine-groupheader-light.png | Bin 119906 -> 0 bytes .../ux-phase5b/autostart-disconnected.png | Bin 169652 -> 0 bytes .../android/ux-phase5b/justlift-baseline.png | Bin 169652 -> 0 bytes .../android/ux-phase5b/sweep-history.png | Bin 433985 -> 0 bytes .../android/ux-phase5b/sweep-routines.png | Bin 206681 -> 0 bytes .../android/ux-phase5b/sweep-settings.png | Bin 457189 -> 0 bytes .../workoutsetup-sheet-scrolled.png | Bin 164765 -> 0 bytes .../android/ux-phase5b/workoutsetup-sheet.png | Bin 135346 -> 0 bytes gradle/libs.versions.toml | 8 - iOS_INSTALL.md | 8 +- iosApp/README.md | 7 +- iosApp/TESTFLIGHT_NOTES.txt | 9 +- .../data/local/SchemaParityTest.kt | 14 +- .../SqlDelightSyncRepositoryTest.kt | 4 +- .../phoenixproject/e2e/WorkoutFlowE2ETest.kt | 2 +- .../viewmodel/DiagnosticsViewModelTest.kt | 8 +- .../viewmodel/MainViewModelTest.kt | 2 +- .../presentation/screen/WorkoutTabPreviews.kt | 8 +- .../util/CsvExporter.android.kt | 4 +- .../util/DataBackupManager.android.kt | 14 +- .../phoenixproject/util/DeviceInfo.android.kt | 2 +- .../composeResources/values-de/strings.xml | 9 +- .../composeResources/values-es/strings.xml | 9 +- .../composeResources/values-fr/strings.xml | 9 +- .../composeResources/values-nl/strings.xml | 9 +- .../composeResources/values/strings.xml | 9 +- .../data/ble/BleCompatibilityMode.kt | 10 +- .../phoenixproject/data/ble/BleExceptions.kt | 2 +- .../data/ble/BlePacketCapture.kt | 6 +- .../data/ble/DiagnosticFaultDecoder.kt | 44 +- .../data/ble/KableBleConnectionManager.kt | 14 +- .../data/ble/MetricPollingEngine.kt | 2 +- .../data/ble/MonitorDataProcessor.kt | 2 +- .../phoenixproject/data/ble/ProtocolModels.kt | 2 +- .../phoenixproject/data/ble/ProtocolParser.kt | 4 +- .../data/local/LegacyCatalogueIdMap.kt | 11 +- .../data/local/MigrationStatements.kt | 22 +- .../data/migration/MigrationManager.kt | 2 +- .../data/repository/BleRepository.kt | 4 +- .../repository/ConnectionLogRepository.kt | 2 +- .../data/repository/KableBleRepository.kt | 4 +- .../phoenixproject/domain/model/Exercise.kt | 2 +- .../phoenixproject/domain/model/Models.kt | 6 +- .../domain/model/UserPreferences.kt | 2 +- .../domain/usecase/RepCounterFromMachine.kt | 2 +- .../components/ConnectingOverlay.kt | 2 +- .../components/EnhancedCablePositionBar.kt | 6 +- .../manager/WorkoutCoordinator.kt | 2 +- .../presentation/navigation/NavGraph.kt | 2 +- .../presentation/screen/JustLiftScreen.kt | 4 +- .../presentation/screen/SetSummaryCard.kt | 4 +- .../presentation/screen/SplashScreen.kt | 2 +- .../presentation/screen/WorkoutHud.kt | 4 +- .../presentation/screen/WorkoutUiState.kt | 2 +- .../util/WeightDisplayFormatter.kt | 2 +- .../viewmodel/DiagnosticsViewModel.kt | 2 +- .../devil/phoenixproject/util/BleConstants.kt | 11 +- .../phoenixproject/util/BlePacketFactory.kt | 22 +- .../devil/phoenixproject/util/ColorScheme.kt | 2 +- .../devil/phoenixproject/util/Constants.kt | 4 +- .../phoenixproject/util/DataBackupManager.kt | 2 +- .../phoenixproject/util/HardwareDetection.kt | 8 +- .../phoenixproject/database/migrations/39.sqm | 31 +- .../data/ble/DiagnosticFaultDecoderTest.kt | 66 +-- .../data/ble/HardwareValidationTest.kt | 444 ------------------ .../data/ble/ProtocolParserTest.kt | 10 +- .../data/integration/HealthDataMappingTest.kt | 2 +- .../util/WeightDisplayFormatterTest.kt | 2 +- .../phoenixproject/util/BleConstantsTest.kt | 4 +- .../util/BlePacketFactoryTest.kt | 16 +- .../phoenixproject/util/CsvExporter.ios.kt | 2 +- .../util/DataBackupManager.ios.kt | 4 +- .../phoenixproject/util/DeviceInfo.ios.kt | 2 +- .../kable-core-android-patched/README.md | 2 +- .../src/androidMain/BluetoothGatt.kt | 2 +- 189 files changed, 260 insertions(+), 730 deletions(-) rename docs/superpowers/specs/{2026-06-16-official-weight-handling-design.md => 2026-06-16-per-cable-weight-handling-design.md} (93%) delete mode 100644 final-visuals/android/01-master-off.png delete mode 100644 final-visuals/android/02-master-on.png delete mode 100644 final-visuals/android/03-master-vulgar-on.png delete mode 100644 final-visuals/android/04-modal.png delete mode 100644 final-visuals/android/05-dominatrix-unlocked.png delete mode 100644 final-visuals/android/ux-phase1/01-home-dark.png delete mode 100644 final-visuals/android/ux-phase1/01-home-light.png delete mode 100644 final-visuals/android/ux-phase1/02-just-lift-dark.png delete mode 100644 final-visuals/android/ux-phase1/02-just-lift-light.png delete mode 100644 final-visuals/android/ux-phase1/03-exercise-picker-dark.png delete mode 100644 final-visuals/android/ux-phase1/03-exercise-picker-light.png delete mode 100644 final-visuals/android/ux-phase1/04-routines-dark.png delete mode 100644 final-visuals/android/ux-phase1/04-routines-light.png delete mode 100644 final-visuals/android/ux-phase1/05-history-dark.png delete mode 100644 final-visuals/android/ux-phase1/05-history-light.png delete mode 100644 final-visuals/android/ux-phase1/06-settings-dark.png delete mode 100644 final-visuals/android/ux-phase1/06-settings-light.png delete mode 100644 final-visuals/android/ux-phase1/07-integrations-dark.png delete mode 100644 final-visuals/android/ux-phase1/07-integrations-light.png delete mode 100644 final-visuals/android/ux-phase2/01-home-dark.png delete mode 100644 final-visuals/android/ux-phase2/01-home-light.png delete mode 100644 final-visuals/android/ux-phase2/02-just-lift-dark.png delete mode 100644 final-visuals/android/ux-phase2/02-just-lift-light.png delete mode 100644 final-visuals/android/ux-phase2/03-analytics-dark.png delete mode 100644 final-visuals/android/ux-phase2/03-analytics-light.png delete mode 100644 final-visuals/android/ux-phase2/04-smart-insights-dark.png delete mode 100644 final-visuals/android/ux-phase2/04-smart-insights-light.png delete mode 100644 final-visuals/android/ux-phase2/05-settings-dark.png delete mode 100644 final-visuals/android/ux-phase2/05-settings-light.png delete mode 100644 final-visuals/android/ux-phase2/06-training-cycles-dark.png delete mode 100644 final-visuals/android/ux-phase2/06-training-cycles-editor-dark.png delete mode 100644 final-visuals/android/ux-phase2/06-training-cycles-editor-light.png delete mode 100644 final-visuals/android/ux-phase2/06-training-cycles-light.png delete mode 100644 final-visuals/android/ux-phase2/07-exercise-picker-dark.png delete mode 100644 final-visuals/android/ux-phase2/07-exercise-picker-light.png delete mode 100644 final-visuals/android/ux-phase3/cards/badges-dark.png delete mode 100644 final-visuals/android/ux-phase3/cards/badges-light.png delete mode 100644 final-visuals/android/ux-phase3/cards/cycles-dark-empty.png delete mode 100644 final-visuals/android/ux-phase3/cards/cycles-light-empty.png delete mode 100644 final-visuals/android/ux-phase3/cards/history-dark-empty.png delete mode 100644 final-visuals/android/ux-phase3/cards/history-light-empty.png delete mode 100644 final-visuals/android/ux-phase3/cards/insights-dark.png delete mode 100644 final-visuals/android/ux-phase3/cards/insights-dashboard-dark.png delete mode 100644 final-visuals/android/ux-phase3/cards/insights-dashboard-light.png delete mode 100644 final-visuals/android/ux-phase3/cards/insights-light.png delete mode 100644 final-visuals/android/ux-phase3/cards/integrations-dark.png delete mode 100644 final-visuals/android/ux-phase3/cards/integrations-light.png delete mode 100644 final-visuals/android/ux-phase3/delete-dialog-dark.png delete mode 100644 final-visuals/android/ux-phase3/delete-dialog-light.png delete mode 100644 final-visuals/android/ux-phase3/echo-selector-dark.png delete mode 100644 final-visuals/android/ux-phase3/echo-selector-light.png delete mode 100644 final-visuals/android/ux-phase3/integrations-spinners-dark.png delete mode 100644 final-visuals/android/ux-phase3/integrations-spinners-light.png delete mode 100644 final-visuals/android/ux-phase3/justlift-dark.png delete mode 100644 final-visuals/android/ux-phase3/justlift-light.png delete mode 100644 final-visuals/android/ux-phase3/routines-dark.png delete mode 100644 final-visuals/android/ux-phase3/routines-light.png delete mode 100644 final-visuals/android/ux-phase3/smartinsights-dark.png delete mode 100644 final-visuals/android/ux-phase3/smartinsights-light.png delete mode 100644 final-visuals/android/ux-phase3/workoutsetup-picker-dark.png delete mode 100644 final-visuals/android/ux-phase3/workoutsetup-picker-light.png delete mode 100644 final-visuals/android/ux-phase4a/backup-dialog-dark.png delete mode 100644 final-visuals/android/ux-phase4a/backup-dialog-light.png delete mode 100644 final-visuals/android/ux-phase4a/banner-disconnected-dark.png delete mode 100644 final-visuals/android/ux-phase4a/banner-disconnected-light.png delete mode 100644 final-visuals/android/ux-phase4a/disconnect-confirm-dark.png delete mode 100644 final-visuals/android/ux-phase4a/disconnect-confirm-light.png delete mode 100644 final-visuals/android/ux-phase4a/eula-age-row-dark.png delete mode 100644 final-visuals/android/ux-phase4a/eula-age-row-light.png delete mode 100644 final-visuals/android/ux-phase4a/exercise-edit-sheet-dark.png delete mode 100644 final-visuals/android/ux-phase4a/exercise-edit-sheet-light.png delete mode 100644 final-visuals/android/ux-phase4a/routine-editor-discard-dark.png delete mode 100644 final-visuals/android/ux-phase4a/routine-editor-discard-light.png delete mode 100644 final-visuals/android/ux-phase4b/00-home-baseline.png delete mode 100644 final-visuals/android/ux-phase4b/01-assessment-entry-progress-tab.png delete mode 100644 final-visuals/android/ux-phase4b/01b-assessment-picker-step1of4.png delete mode 100644 final-visuals/android/ux-phase4b/02a-single-exercise-list.png delete mode 100644 final-visuals/android/ux-phase4b/02b-exercise-detail-altbenchpress.png delete mode 100644 final-visuals/android/ux-phase4b/03a-routine-overview.png delete mode 100644 final-visuals/android/ux-phase4b/03b-routine-overview-inworkout.png delete mode 100644 final-visuals/android/ux-phase4b/03c-exit-dialog-sysback.png delete mode 100644 final-visuals/android/ux-phase4b/03d-exit-dialog-topbarback.png delete mode 100644 final-visuals/android/ux-phase4b/03e-exit-dialog-stopbutton.png delete mode 100644 final-visuals/android/ux-phase4b/05a-justlift-setready.png delete mode 100644 final-visuals/android/ux-phase4b/06a-routine-overview-withexercise.png delete mode 100644 final-visuals/android/ux-phase4b/06b-routine-overview-withexercise-bleblocked.png delete mode 100644 final-visuals/android/ux-phase4b/06c-exit-dialog-overview-withexercise.png delete mode 100644 final-visuals/android/ux-phase5a/badges-streak-dark.png delete mode 100644 final-visuals/android/ux-phase5a/badges-streak-light.png delete mode 100644 final-visuals/android/ux-phase5a/daystrip-dark.png delete mode 100644 final-visuals/android/ux-phase5a/daystrip-light.png delete mode 100644 final-visuals/android/ux-phase5a/diagnostics-waiting-dark.png delete mode 100644 final-visuals/android/ux-phase5a/diagnostics-waiting-light.png delete mode 100644 final-visuals/android/ux-phase5a/empty-state-dark.png delete mode 100644 final-visuals/android/ux-phase5a/empty-state-light.png delete mode 100644 final-visuals/android/ux-phase5a/exercise-config-header-dark.png delete mode 100644 final-visuals/android/ux-phase5a/exercise-config-header-light.png delete mode 100644 final-visuals/android/ux-phase5a/home-gradient-dark.png delete mode 100644 final-visuals/android/ux-phase5a/home-gradient-light.png delete mode 100644 final-visuals/android/ux-phase5a/reducemotion-badges-dark.png delete mode 100644 final-visuals/android/ux-phase5a/reducemotion-badges-light.png delete mode 100644 final-visuals/android/ux-phase5a/reducemotion-empty-state-dark.png delete mode 100644 final-visuals/android/ux-phase5a/reducemotion-empty-state-light.png delete mode 100644 final-visuals/android/ux-phase5a/routine-groupheader-dark.png delete mode 100644 final-visuals/android/ux-phase5a/routine-groupheader-light.png delete mode 100644 final-visuals/android/ux-phase5b/autostart-disconnected.png delete mode 100644 final-visuals/android/ux-phase5b/justlift-baseline.png delete mode 100644 final-visuals/android/ux-phase5b/sweep-history.png delete mode 100644 final-visuals/android/ux-phase5b/sweep-routines.png delete mode 100644 final-visuals/android/ux-phase5b/sweep-settings.png delete mode 100644 final-visuals/android/ux-phase5b/workoutsetup-sheet-scrolled.png delete mode 100644 final-visuals/android/ux-phase5b/workoutsetup-sheet.png delete mode 100644 shared/src/commonTest/kotlin/com/devil/phoenixproject/data/ble/HardwareValidationTest.kt diff --git a/.almanac/pages/machine-diagnostics.md b/.almanac/pages/machine-diagnostics.md index b47cbdda6..31d8df6f3 100644 --- a/.almanac/pages/machine-diagnostics.md +++ b/.almanac/pages/machine-diagnostics.md @@ -56,7 +56,7 @@ The screen is driven by live BLE state. `BleRepository` exposes `diagnostics` as The binary contract is broader than just four fault words. `parseDiagnosticPacket()` accepts an empty payload as a valid zero snapshot, rejects non-empty payloads shorter than `18` bytes, then decodes uptime seconds, four unsigned `16-bit` fault words, six required temperatures, two optional extra temperatures, an optional `52`-byte crash block, and an optional `32-bit` warnings field [@protocol-parser] [@protocol-models]. [[vitruvian-ble-protocol]] is the neighboring page for the broader scan, rep, monitor, and command surface around this diagnostic characteristic. -Fault decoding is intentionally category-specific. `DiagnosticFaultDecoder` always projects the packet into four display slots named `Vee`, `Other`, `Motor A`, and `Motor B`, then maps bitmasks to labels such as `TI restarted`, `Over voltage`, `Encoder`, or `Motor overtemp` instead of exposing only raw integers [@fault-decoder]. The view-model tests pin that labeling contract and verify that the export text includes both the human label and the raw hex code [@diagnostics-tests]. +Fault decoding is intentionally category-specific. `DiagnosticFaultDecoder` always projects the packet into four display slots named `Controller`, `Other`, `Motor A`, and `Motor B`, then maps bitmasks to labels such as `Controller restarted`, `Overvoltage`, `Encoder fault`, or `Motor over-temperature` instead of exposing only raw integers [@fault-decoder]. The view-model tests pin that labeling contract and verify that the export text includes both the human label and the raw hex code [@diagnostics-tests]. The export contract is deliberately narrow. `buildDiagnosticsExportText()` prepends app version, export timestamp, `REDACTED_DIAGNOSTICS` classification, and a privacy line that excludes workout history, profiles, auth or session tokens, Supabase config, and keystore data before listing the current packet contents [@diagnostics-vm]. The copy button stays disabled until a packet exists, so the feature exports the latest machine snapshot rather than an empty template [@diagnostics-screen]. diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index e5ca2d3a6..73c9a1b41 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -28,7 +28,7 @@ body: id: app-version attributes: label: App Version - description: What version of Vitruvian Phoenix are you using? + description: What version of Project Phoenix are you using? placeholder: "e.g., 1.0.0" validations: required: true diff --git a/.phoenix-review/BUG-FIX-PLAN.md b/.phoenix-review/BUG-FIX-PLAN.md index 9ba4f91f3..5720bf042 100644 --- a/.phoenix-review/BUG-FIX-PLAN.md +++ b/.phoenix-review/BUG-FIX-PLAN.md @@ -142,7 +142,7 @@ - F173 | impact=250 | Domain | medium / failure-point | file=shared/src/commonMain/kotlin/com/devil/phoenixproject/domain/voice/SafeWordListener.kt | lines=26, 38-44 | The common listener contract exposes `startListening()`, `isListening`, and `detectedWord`, but no error/status channel for unavailable recognition, denied microphone/speech permission, unsupported on-device recognition, or start failures. The platform implementations can fail by logging and leaving `isListening` false, which leaves callers unable to distinguish “not yet started”, “already stopped”, and “emergency voice stop is unavailable”. For a safety-oriented emergency stop path, silent non-operation can make the workout UI appear configured while no voice stop will ever fire. | fix: Add a shared status/error flow or sealed state such as `Idle`, `Listening`, `PermissionDenied`, `RecognitionUnavailable`, and `StartFailed`, and have calibration/workout UI surface failures instead of relying only on logs and `isListening`. - F174 | impact=250 | Domain | medium / failure-point | file=shared/src/commonMain/kotlin/com/devil/phoenixproject/domain/voice/SafeWordListener.kt | lines=41-44 | The API documents that emitted detections occur when results “match the safe word”, but it does not define or enforce what a valid safe word is. The current platform implementations perform whole-token matching after splitting transcripts on whitespace, while settings/calibration paths only require a non-blank string. A user can configure a multi-word phrase or punctuation-bearing value that appears valid in common code but can never match the actual token-based listener. | fix: Define the safe-word normalization/validation contract in common code. Either restrict configured safe words to one normalized token and reject/trim punctuation at save/calibration time, or update platform matching to normalize transcripts and support the same phrase rules accepted by the UI. - F304 | impact=250 | Utilities | medium / bug | file=shared/src/commonMain/kotlin/com/devil/phoenixproject/util/DataBackupManager.kt | lines=1355-1380 | The streaming personal-record importer drops `PersonalRecordBackup.cableCount` by passing `cable_count = null` to `queries.upsertPR`. The non-streaming importer preserves this field at lines 631-643, so large backups imported through the streaming path lose cable-count metadata while small backups do not. | fix: Pass `pr.cableCount?.toLong()` in the streaming path to match `importFromJson()`. -- F306 | impact=250 | Utilities | medium / bug | file=shared/src/commonMain/kotlin/com/devil/phoenixproject/util/BlePacketFactory.kt | lines=75-86 | `createStopCommand()` is documented as the primary STOP command but emits legacy opcode `0x05`, while `BleConstants.Commands.STOP_COMMAND` and `createOfficialStopPacket()` use official stop opcode `0x50`. This creates an attractive but unsafe API footgun: callers choosing `createStopCommand()` by name/comment may send a different stop semantics than the official stop/clear-fault packet. | fix: Rename/deprecate the legacy helper (for example `createLegacyStopCommand()`), make the primary helper delegate to `createOfficialStopPacket()`, and keep tests explicit about the legacy opcode only where it is intentionally required. +- F306 | impact=250 | Utilities | medium / bug | file=shared/src/commonMain/kotlin/com/devil/phoenixproject/util/BlePacketFactory.kt | lines=75-86 | `createStopCommand()` is documented as the primary STOP command but emits legacy opcode `0x05`, while `BleConstants.Commands.STOP_COMMAND` and `createSoftStopPacket()` use 0x50 soft-stop opcode `0x50`. This creates an attractive but unsafe API footgun: callers choosing `createStopCommand()` by name/comment may send a different stop semantics than the 0x50 soft-stop/clear-fault packet. | fix: Rename/deprecate the legacy helper (for example `createLegacyStopCommand()`), make the primary helper delegate to `createSoftStopPacket()`, and keep tests explicit about the legacy opcode only where it is intentionally required. - F311 | impact=250 | Utilities | medium / bug | file=shared/src/commonMain/kotlin/com/devil/phoenixproject/util/HardwareDetection.kt | lines=8-13, 20-29 | The file-level documentation says name-prefix hardware detection was flawed and should be avoided, but `detectModel()` still infers `VFormTrainer`/`TrainerPlus` from `Vee_` and `VIT` prefixes. That stale detection result is used when reporting `ConnectionState.Connected`, so UI or downstream logic can still receive a model classification the comments say is unreliable. | fix: Either remove model inference and return `Unknown` until firmware-backed detection exists, or update the documentation and tests to explain why these prefixes are now reliable. Prefer firmware/version-characteristic detection before exposing a concrete hardware model. - F313 | impact=250 | Utilities | medium / bug | file=shared/src/commonMain/kotlin/com/devil/phoenixproject/util/KmpUtils.kt | lines=232-236 | `formatFloat()` builds the decimal part from `rounded - intPart`, but `toLong()` truncates negative values toward zero. Negative values with decimal places therefore format with a negative fractional component, e.g. the current algorithm formats `-1.23` with two decimals as `-1.-23` and `-0.25` as `0.-25`. Any UI using this helper for signed deltas, percentages, velocities, or corrections can display malformed numbers. | fix: Format the absolute fractional component separately from the sign, or use a locale-stable multiplatform formatter that rounds the whole scaled integer first and then reconstructs sign, integer, and padded fractional digits from absolute values. Add tests for negative values such as `-1.23`, `-5.5`, and `-0.25`. - F314 | impact=250 | Utilities | medium / bug | file=shared/src/commonMain/kotlin/com/devil/phoenixproject/util/KmpUtils.kt | lines=245, 283 | `formatDouble()` downcasts every `Double` to `Float` before formatting. This loses precision for normal `Double` values and turns large finite doubles outside the `Float` range into infinities, which then hit the non-finite guard and display as zero. The `Double.format()` extension exposes this behavior broadly, so any future Double-valued analytics/statistics display can silently show rounded or completely wrong values. | fix: Implement `formatDouble()` using Double arithmetic end-to-end and a Double finite check rather than delegating through Float. Add regression tests with a high-precision value and a finite value greater than `Float.MAX_VALUE` to prevent silent zeroing. diff --git a/.phoenix-review/CONSOLIDATED-REPORT.md b/.phoenix-review/CONSOLIDATED-REPORT.md index c1f10bebd..ef94a7a0c 100644 --- a/.phoenix-review/CONSOLIDATED-REPORT.md +++ b/.phoenix-review/CONSOLIDATED-REPORT.md @@ -323,7 +323,7 @@ - F303 | module=Utilities | category=failure-point | file=shared/src/commonMain/kotlin/com/devil/phoenixproject/util/DataBackupManager.kt | lines=1138-1215 | description: Metric batching counts only metrics that are actually inserted (`batchCount++` is inside `metric.sessionId in importedSessionIds`). If a large backup contains mostly metrics for skipped/pre-existing sessions, the second batching loop can consume the entire remaining `metricSamples` array in one transaction because `batchCount` never reaches `IMPORT_BATCH_SIZE`. This defeats the OOM/lock-avoidance goal of streaming import for exactly the large-file path. | fix: Limit each transaction by rows processed/seen, not rows inserted, or maintain separate `rowsInBatch` and `insertedInBatch` counters. - F304 | module=Utilities | category=bug | file=shared/src/commonMain/kotlin/com/devil/phoenixproject/util/DataBackupManager.kt | lines=1355-1380 | description: The streaming personal-record importer drops `PersonalRecordBackup.cableCount` by passing `cable_count = null` to `queries.upsertPR`. The non-streaming importer preserves this field at lines 631-643, so large backups imported through the streaming path lose cable-count metadata while small backups do not. | fix: Pass `pr.cableCount?.toLong()` in the streaming path to match `importFromJson()`. - F305 | module=Utilities | category=failure-point | file=shared/src/commonMain/kotlin/com/devil/phoenixproject/util/DataBackupManager.kt | lines=976-980, 1025-1737 | description: The streaming importer is order-dependent even though JSON object member order is not semantically significant. Child arrays such as `metricSamples`, `routineExercises`, `cycleDays`, `plannedSets`, and `completedSets` are imported only if their parent IDs have already been seen in earlier fields. A valid backup with fields reordered can silently skip children or log warnings while returning success. | fix: Make streaming import robust to object order by doing multiple passes over a seekable source, buffering small parent/child ID sets and deferred child rows, or enforcing/exporting a documented field order and rejecting out-of-order backups instead of silently skipping data. -- F306 | module=Utilities | category=bug | file=shared/src/commonMain/kotlin/com/devil/phoenixproject/util/BlePacketFactory.kt | lines=75-86 | description: `createStopCommand()` is documented as the primary STOP command but emits legacy opcode `0x05`, while `BleConstants.Commands.STOP_COMMAND` and `createOfficialStopPacket()` use official stop opcode `0x50`. This creates an attractive but unsafe API footgun: callers choosing `createStopCommand()` by name/comment may send a different stop semantics than the official stop/clear-fault packet. | fix: Rename/deprecate the legacy helper (for example `createLegacyStopCommand()`), make the primary helper delegate to `createOfficialStopPacket()`, and keep tests explicit about the legacy opcode only where it is intentionally required. +- F306 | module=Utilities | category=bug | file=shared/src/commonMain/kotlin/com/devil/phoenixproject/util/BlePacketFactory.kt | lines=75-86 | description: `createStopCommand()` is documented as the primary STOP command but emits legacy opcode `0x05`, while `BleConstants.Commands.STOP_COMMAND` and `createSoftStopPacket()` use 0x50 soft-stop opcode `0x50`. This creates an attractive but unsafe API footgun: callers choosing `createStopCommand()` by name/comment may send a different stop semantics than the 0x50 soft-stop/clear-fault packet. | fix: Rename/deprecate the legacy helper (for example `createLegacyStopCommand()`), make the primary helper delegate to `createSoftStopPacket()`, and keep tests explicit about the legacy opcode only where it is intentionally required. - F307 | module=Utilities | category=failure-point | file=shared/src/commonMain/kotlin/com/devil/phoenixproject/util/ConnectivityChecker.kt | lines=7-12 | description: The common contract only exposes a synchronous `isOnline()` method and has no lifecycle/dispose hook or observable connectivity-change signal. Platform implementations that allocate long-lived resources or background monitors cannot be cancelled through the common API, and common sync code cannot subscribe to a reconnect event after setting a waiting-for-connectivity state. | fix: Add lifecycle and state-change semantics to the expect contract, such as `close()`/`dispose()` plus a `StateFlow` or callback registration, and make DI scopes own the checker lifecycle explicitly. - F308 | module=Utilities | category=bug | file=shared/src/commonMain/kotlin/com/devil/phoenixproject/util/Constants.kt | lines=139-142 | description: `ProtocolConstants` declares packet sizes that contradict the actual packet builders and tests. `ECHO_PACKET_SIZE` is `29`, but `BlePacketFactory.createEchoControl()` builds a 32-byte frame and tests assert 32. `ACTIVATION_PACKET_SIZE` is `97`, but `BleConstants.ActivationPacket.SIZE`, `BlePacketFactory.createProgramParams()`, model documentation, and tests all use 96 bytes. Any code using these constants for allocation, validation, or MTU checks would reject valid packets or allocate the wrong size. | fix: Define packet sizes from a single source of truth (`BleConstants.ActivationPacket.SIZE` and the factory Echo size), update Echo to 32 and activation to 96, and add tests covering `ProtocolConstants` parity with factory output. - F309 | module=Utilities | category=failure-point | file=shared/src/commonMain/kotlin/com/devil/phoenixproject/util/DeviceInfo.kt | lines=71-73 | description: The common API promises device info as a JSON `String`, but the contract does not require escaping or a structured serializer-backed result. Platform values such as model names, device names, build strings, or bundle values can contain quotes, backslashes, or control characters; manual string-building actual implementations can then return invalid JSON or malformed metadata. | fix: Replace `toJson(): String` with a serializable data model or require all actual implementations to use `kotlinx.serialization`/a shared escaping helper. Add tests with quotes, backslashes, and newlines in representative fields. diff --git a/.phoenix-review/utilities-part-2.md b/.phoenix-review/utilities-part-2.md index 6bba2cfa9..6d4196654 100644 --- a/.phoenix-review/utilities-part-2.md +++ b/.phoenix-review/utilities-part-2.md @@ -35,8 +35,8 @@ Findings: 11 total - Category: bug - Severity: medium - Line numbers: 75-86 -- Description: `createStopCommand()` is documented as the primary STOP command but emits legacy opcode `0x05`, while `BleConstants.Commands.STOP_COMMAND` and `createOfficialStopPacket()` use official stop opcode `0x50`. This creates an attractive but unsafe API footgun: callers choosing `createStopCommand()` by name/comment may send a different stop semantics than the official stop/clear-fault packet. -- Suggested fix direction: Rename/deprecate the legacy helper (for example `createLegacyStopCommand()`), make the primary helper delegate to `createOfficialStopPacket()`, and keep tests explicit about the legacy opcode only where it is intentionally required. +- Description: `createStopCommand()` is documented as the primary STOP command but emits legacy opcode `0x05`, while `BleConstants.Commands.STOP_COMMAND` and `createSoftStopPacket()` use 0x50 soft-stop opcode `0x50`. This creates an attractive but unsafe API footgun: callers choosing `createStopCommand()` by name/comment may send a different stop semantics than the 0x50 soft-stop/clear-fault packet. +- Suggested fix direction: Rename/deprecate the legacy helper (for example `createLegacyStopCommand()`), make the primary helper delegate to `createSoftStopPacket()`, and keep tests explicit about the legacy opcode only where it is intentionally required. #### Finding 3 - Category: bug diff --git a/ANDROID_INSTALL.md b/ANDROID_INSTALL.md index 886a640ef..6a93197d3 100644 --- a/ANDROID_INSTALL.md +++ b/ANDROID_INSTALL.md @@ -1,6 +1,6 @@ # Android Installation Guide -This guide explains how to install Vitruvian Phoenix on your Android device. +This guide explains how to install Project Phoenix on your Android device. ## Prerequisites @@ -47,7 +47,7 @@ Android requires permission to install apps from outside the Play Store. 1. Open the downloaded APK file 2. Tap **Install** 3. Wait for installation to complete -4. Tap **Open** or find **Vitruvian Phoenix** in your app drawer +4. Tap **Open** or find **Project Phoenix** in your app drawer --- @@ -64,7 +64,7 @@ When you first launch the app, you'll be asked to grant permissions: - This is an Android requirement - the app does not track your location - Tap **Allow** when prompted -**Note:** If you deny permissions, the app cannot connect to your trainer. You can always grant permissions later in Settings > Apps > Vitruvian Phoenix > Permissions. +**Note:** If you deny permissions, the app cannot connect to your trainer. You can always grant permissions later in Settings > Apps > Project Phoenix > Permissions. --- @@ -95,12 +95,12 @@ When a new version is released: ### App Crashes on Launch - Make sure your device is running Android 8.0 or later -- Try clearing app data: Settings > Apps > Vitruvian Phoenix > Storage > Clear data +- Try clearing app data: Settings > Apps > Project Phoenix > Storage > Clear data - Report the issue on GitHub with your device model and Android version ### Bluetooth Permission Denied -1. Go to **Settings > Apps > Vitruvian Phoenix > Permissions** +1. Go to **Settings > Apps > Project Phoenix > Permissions** 2. Enable **Nearby devices** (Android 12+) or **Location** (Android 11 and below) 3. Restart the app diff --git a/androidApp/src/main/res/xml/backup_rules.xml b/androidApp/src/main/res/xml/backup_rules.xml index b327f184d..0871fb1f6 100644 --- a/androidApp/src/main/res/xml/backup_rules.xml +++ b/androidApp/src/main/res/xml/backup_rules.xml @@ -1,6 +1,6 @@ - + diff --git a/androidApp/src/main/res/xml/data_extraction_rules.xml b/androidApp/src/main/res/xml/data_extraction_rules.xml index e75113bf4..4e95d1f7a 100644 --- a/androidApp/src/main/res/xml/data_extraction_rules.xml +++ b/androidApp/src/main/res/xml/data_extraction_rules.xml @@ -1,6 +1,6 @@ - + diff --git a/docs/index.html b/docs/index.html index b6c199de5..4fd3fa9a3 100644 --- a/docs/index.html +++ b/docs/index.html @@ -4,14 +4,14 @@ Project Phoenix — Full Bluetooth Training Control - + - + @@ -499,7 +499,7 @@

Full Bluetooth control.
Built for serious training.
-
572
Exercises, with video
+
870+
Exercises, with demo images
7
Ways to train
2
Machines supported
5
Languages
@@ -565,7 +565,7 @@

Real-time intelligence

Build & program

-

A 572-exercise library and a routine builder powerful enough for real periodized programming.

+

A library of 870+ openly licensed exercises and a routine builder powerful enough for real periodized programming.