Apple Fitness workout fixer + Strava uploader

Add CLAUDE.md, fix run.sh bundle ID, update list screenshot

+72
+58
CLAUDE.md
··· 1 + # CLAUDE.md 2 + 3 + This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 + 5 + ## Project Overview 6 + 7 + Overrun is an iOS app (Swift/SwiftUI, iOS 17+) for trimming HealthKit workouts that weren't stopped in time. It creates a new trimmed workout preserving all associated data (samples, routes, events, metadata, effort scores), optionally uploads to Strava, then guides the user to delete the original. 8 + 9 + ## Build & Run 10 + 11 + ```bash 12 + # Build for simulator 13 + xcodebuild -scheme "WorkoutEditor" -destination "platform=iOS Simulator,name=iPhone 17 Pro" build 14 + 15 + # Build, install, and launch on simulator 16 + ./run.sh 17 + ``` 18 + 19 + Open `WorkoutEditor.xcodeproj` in Xcode for full IDE support. No package managers (SPM/CocoaPods) — all frameworks are system-provided. 20 + 21 + ## Strava Setup 22 + 23 + Copy `Secrets.xcconfig.template` to `Secrets.xcconfig` and fill in Strava API credentials. The xcconfig is gitignored. 24 + 25 + ## Architecture 26 + 27 + All source lives in `WorkoutEditor/`. There is no test target. 28 + 29 + ### Key Files 30 + 31 + - **WorkoutEditorApp.swift** — App entry point, handles `overrun://` deep links for Strava OAuth callback 32 + - **HealthKitManager.swift** — `@Observable` singleton managing all HealthKit reads/writes: workout loading (paginated, 50 at a time), sample fetching, workout creation with data migration, deletion, and live observation via `HKObserverQuery` 33 + - **StravaManager.swift** — `@Observable` singleton for Strava OAuth (tokens in Keychain), TCX file generation, multipart upload with status polling 34 + - **WorkoutListView.swift** — Main list with search, sort (date/longest/shortest), month/year grouping, infinite scroll, pull-to-refresh 35 + - **WorkoutEditView.swift** — 3-step guided trim flow: (1) trim via range slider + activity graph, (2) optional Strava upload, (3) remove original 36 + - **ActivityGraphView.swift** — SwiftUI Charts area+line graph for heart rate/energy/distance with dimmed trim regions 37 + - **RangeSliderView.swift** — Dual-handle range slider component 38 + 39 + ### Data Flow 40 + 41 + 1. App requests HealthKit authorization → loads workouts → starts observer for external changes 42 + 2. User selects workout → fetches intensity samples (heart rate, energy, distance) for graph 43 + 3. User adjusts range slider → saves trimmed copy with migrated samples, routes, events, metadata, activities, and effort scores (iOS 18+) 44 + 4. Optionally uploads to Strava via OAuth + TCX 45 + 5. User deletes original in Apple Fitness (or auto-deleted if Overrun-created) 46 + 47 + ### Patterns 48 + 49 + - **Logging**: `os.Logger` with subsystem `"com.overrun"`, one category per feature 50 + - **Async**: Modern async/await throughout; `withCheckedThrowingContinuation` bridges legacy `HKWorkoutRouteQuery` 51 + - **Debug mode**: `#if DEBUG` auto-generates 200 sample workouts on first launch 52 + - **Localization**: Xcode String Catalogs (`.xcstrings`) — all UI strings are localization-ready 53 + 54 + ### HealthKit Constraints 55 + 56 + - Cannot modify workouts in place — must create new + delete original 57 + - Can only delete workouts the app created — others require manual deletion 58 + - Estimated effort scores are system-computed and not copyable; only user-entered effort (iOS 18+) can be migrated
+14
run.sh
··· 1 + #!/bin/bash 2 + set -e 3 + 4 + SCHEME="WorkoutEditor" 5 + DEVICE="iPhone 17 Pro" 6 + BUNDLE_ID="com.metafluff.overrun" 7 + 8 + xcodebuild -scheme "$SCHEME" -destination "platform=iOS Simulator,name=$DEVICE" build 2>&1 | tail -5 9 + 10 + APP_PATH=$(find ~/Library/Developer/Xcode/DerivedData/WorkoutEditor-*/Build/Products/Debug-iphonesimulator -name "WorkoutEditor.app" -maxdepth 1 | head -1) 11 + 12 + xcrun simctl install "$DEVICE" "$APP_PATH" 13 + xcrun simctl terminate "$DEVICE" "$BUNDLE_ID" 2>/dev/null || true 14 + xcrun simctl launch "$DEVICE" "$BUNDLE_ID"
screenshots/list.png

This is a binary file and will not be displayed.