From 3f7e81c85628290e10894520783b55115ef56bd7 Mon Sep 17 00:00:00 2001 From: Johnny Huynh <27847622+johnnyhuy@users.noreply.github.com> Date: Thu, 30 Jul 2026 22:50:56 +1000 Subject: [PATCH 1/2] fix(ci): remove platform-dependent build failures Keep video pipeline functions sourceable on clean Linux runners and build iOS against a generic simulator destination when no device execution is required. Co-authored-by: opencode-agent --- .github/workflows/build.yml | 10 ++++----- mise.toml | 8 +++++++ scripts/exercise-video-pipeline/generate.sh | 8 ++++++- .../exercise-video-pipeline/test_pipeline.sh | 21 +++++-------------- 4 files changed, 25 insertions(+), 22 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7bf7448..98f7f6c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,10 +19,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Setup mise - uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3.5.1 + uses: jdx/mise-action@9e7f7633ff6f6d6048a9418a68d48f288f50eb14 # v4.2.3 - name: Build run: mise run build @@ -35,13 +35,13 @@ jobs: runs-on: macos-15 steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Setup mise - uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3.5.1 + uses: jdx/mise-action@9e7f7633ff6f6d6048a9418a68d48f288f50eb14 # v4.2.3 - name: Install xcodegen run: brew install xcodegen - name: Build iOS - run: mise run ios:build:dev + run: mise run ios:build:ci diff --git a/mise.toml b/mise.toml index 3aaafe9..7f9102e 100644 --- a/mise.toml +++ b/mise.toml @@ -79,6 +79,14 @@ cd ../.. && bash scripts/update_scheme_env.sh 2>/dev/null || echo "⚠️ No .e cd Apps/iOS && open BetterFit.xcodeproj """ +[tasks."ios:build:ci"] +run = """ +cd Apps/iOS +xcodegen generate +xcodebuild -project BetterFit.xcodeproj -scheme BetterFit -destination 'generic/platform=iOS Simulator' CODE_SIGNING_ALLOWED=NO build +""" +description = "Build iOS for CI without querying or booting CoreSimulator devices" + [tasks."watch:gen"] run = "cd Apps/iOS && xcodegen generate" diff --git a/scripts/exercise-video-pipeline/generate.sh b/scripts/exercise-video-pipeline/generate.sh index af8641a..af8edbc 100755 --- a/scripts/exercise-video-pipeline/generate.sh +++ b/scripts/exercise-video-pipeline/generate.sh @@ -168,6 +168,7 @@ normalize_output () { # Stage 1 — anchors via fal.ai # --------------------------------------------------------------------------- +main () { for entry in "${EXERCISES[@]}"; do slug="$(slug_for "$entry")" pose="$(pose_for "$entry")" @@ -295,4 +296,9 @@ echo " image model: $FAL_IMAGE_ENDPOINT" echo " video model (default): $FAL_VIDEO_ENDPOINT" echo " video overrides: ${FAL_VIDEO_OVERRIDES:-}" echo " anchors: $ANCHORS_DIR" -echo " videos: $VIDEOS_DIR" \ No newline at end of file +echo " videos: $VIDEOS_DIR" +} + +if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then + main "$@" +fi diff --git a/scripts/exercise-video-pipeline/test_pipeline.sh b/scripts/exercise-video-pipeline/test_pipeline.sh index d2d8c7d..6794f59 100755 --- a/scripts/exercise-video-pipeline/test_pipeline.sh +++ b/scripts/exercise-video-pipeline/test_pipeline.sh @@ -37,25 +37,14 @@ for s in generate.sh prompts.sh exercises.sh; do fi done -# Source the pipeline functions without running the main loop. -# generate.sh is large — extract just the function definitions and -# the override-lookup helper. +# Source the pipeline functions without running the guarded main loop. echo "==> sourcing function definitions" -TMPF=$(mktemp) -# Strip the executable blocks (everything from the first 'for' loop on -# Stage 1 onwards is a script body, not a function we want to run). -sed -n '1,/^# Stage 2/p' "$PIPELINE_DIR/generate.sh" > "$TMPF" -# Append a no-op so the last function definition closes cleanly. -cat >> "$TMPF" <<'EOF' -exit 0 -EOF -if bash "$TMPF" >/dev/null 2>&1; then +if bash -c "source '$PIPELINE_DIR/generate.sh'" >/dev/null 2>&1; then ok "function definitions source cleanly" else - bad "sourcing generated function block" - bash "$TMPF" + bad "sourcing generate.sh" + bash -c "source '$PIPELINE_DIR/generate.sh'" fi -rm -f "$TMPF" # Source the smaller files directly. if bash -c "source '$PIPELINE_DIR/prompts.sh' && source '$PIPELINE_DIR/exercises.sh' && exit 0" 2>/dev/null; then @@ -169,4 +158,4 @@ fi # ---- summary -------------------------------------------------------- echo "" echo "==> summary: $PASS passed, $FAIL failed" -exit "$FAIL" \ No newline at end of file +exit "$FAIL" From df395f5d60ccc9529de6b7d7b111f1d178981f1b Mon Sep 17 00:00:00 2001 From: Johnny Huynh <27847622+johnnyhuy@users.noreply.github.com> Date: Thu, 30 Jul 2026 23:12:54 +1000 Subject: [PATCH 2/2] fix(core): preserve history during startup Merge persisted workouts with in-memory completions and synchronize history access so asynchronous initialization cannot discard newly completed workouts. Co-authored-by: opencode-agent --- Sources/BetterFit/BetterFit.swift | 34 +++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/Sources/BetterFit/BetterFit.swift b/Sources/BetterFit/BetterFit.swift index 3d6f451..9f46b4b 100644 --- a/Sources/BetterFit/BetterFit.swift +++ b/Sources/BetterFit/BetterFit.swift @@ -36,6 +36,7 @@ public class BetterFit { // MARK: - State private var workoutHistory: [Workout] = [] + private let workoutHistoryLock = NSLock() // MARK: - Initialization @@ -67,7 +68,13 @@ public class BetterFit { private func loadPersistedData() async { do { // Load workout history - workoutHistory = try await persistenceService.getWorkouts() + let persistedWorkouts = try await persistenceService.getWorkouts() + withWorkoutHistory { currentWorkouts in + let persistedIds = Set(persistedWorkouts.map(\.id)) + currentWorkouts = persistedWorkouts + currentWorkouts.filter { + !persistedIds.contains($0.id) + } + } // Load user profile if let profile = try await persistenceService.getUserProfile() { @@ -138,7 +145,10 @@ public class BetterFit { autoTrackingService.stopTracking() // Record in history - workoutHistory.append(workout) + let history = withWorkoutHistory { currentWorkouts in + currentWorkouts.append(workout) + return currentWorkouts + } // Persist workout Task { @@ -170,7 +180,7 @@ public class BetterFit { // Analyze and adapt plan if needed if let activePlan = planManager.getActivePlan() { let adaptations = aiAdaptationService.analyzePerformance( - workouts: workoutHistory, + workouts: history, currentPlan: activePlan ) @@ -189,7 +199,7 @@ public class BetterFit { /// Get workout history public func getWorkoutHistory() -> [Workout] { - return workoutHistory + withWorkoutHistory { $0 } } // MARK: - Personal Records @@ -207,7 +217,8 @@ public class BetterFit { public func getPersonalRecords() -> [PersonalRecordEntry] { var records: [String: (weight: Double, date: Date)] = [:] - for workout in workoutHistory { + let history = withWorkoutHistory { $0 } + for workout in history { for workoutExercise in workout.exercises { let name = workoutExercise.exercise.name for set in workoutExercise.sets where set.isCompleted { @@ -274,13 +285,24 @@ public class BetterFit { /// Schedule smart notifications private func scheduleWorkoutNotifications() { + let history = withWorkoutHistory { $0 } notificationManager.scheduleNotifications( userProfile: socialManager.getUserProfile(), - workoutHistory: workoutHistory, + workoutHistory: history, activePlan: planManager.getActivePlan() ) } + // MARK: - Private Helpers + + private func withWorkoutHistory( + _ operation: (inout [Workout]) -> Result + ) -> Result { + workoutHistoryLock.lock() + defer { workoutHistoryLock.unlock() } + return operation(&workoutHistory) + } + // MARK: - Health Integration /// Process motion data from Watch