# FitnessApp Project Knowledge Base

## Overview
- **Platform:** iOS (SwiftUI)
- **Goal:** Strava-like fitness tracker (no social features)
- **Core Features:** Activity recording, mapping, stats, progress, manual entry, HealthKit import, photo upload/display, goal tracking, device-based elevation tracking.
- **Backend:** PHP/MySQL (REST endpoints, hosted at truaxcreative.com/FitnessApp/)

## Architecture
- **Frontend:**
  - SwiftUI app, modularized into Views, ViewModels, Models, and Managers.
  - Uses CoreLocation for GPS, MapKit for maps, HealthKit for Apple Health integration, CoreMotion (CMAltimeter) for relative altitude.
  - All network calls use async/await and Codable for JSON.
- **Backend:**
  - PHP scripts for CRUD on workouts, users, goals, and photos.
  - MySQL DB with tables: workouts, users, goals, photos, etc.
  - SFTP auto-sync for code deployment.

## Key Features & Status
- **Recording & Saving Workouts:**
  - Live map, stats, and activity type selection.
  - Save sheet for title, stats, and photo upload.
  - Elevation gain calculated with accuracy filtering and scaling.
  - Speed metrics fully implemented (avgSpeed and maxSpeed).
- **Manual Entry:**
  - Supports weight training and other types.
  - Exercise/sets UI, JSON storage.
- **HealthKit Import:**
  - Imports all supported types, deduplicates with backend.
  - Maps many HKWorkoutActivityTypes to app types.
- **Progress & Goals:**
  - Progress tab with charts, stats, and filters.
  - Goal creation, editing, deletion, and progress calculation (Distance and Workout count implemented; Weight goal calculation temporarily removed for refactoring).
- **Photo Upload/Display:**
  - Users can add a photo to a workout (on save or edit).
  - Photos stored on server, URLs in DB, fetched and shown in detail view.
- **Editing Workouts:**
  - Edit all fields, including exercises and photos.
  - HealthKit workouts can be edited and saved as new manual entries.
- **Elevation Processing:**
  - Hybrid system prioritizing on-device altimeter data (CMAltimeter) when available.
  - Falls back to calculating gain from embedded altitude data in GPS (`route_data`) if sufficient points exist.
  - Further fallback to Google Elevation API if route altitude data is insufficient.
  - Backend processing (`process_elevation.php`) handles prioritization.
  - Final calculated gain stored in `elevation_gain` column, source tracked in `elevation_calculation_source`.
  - Raw altimeter gain stored in `altimeter_elevation_gain` (sent from app).
- **Speed Metrics:**
  - Support for average and maximum speed tracking in workouts.
  - Data displayed in workout details and supporting charts.

## Backend Integration
- **Endpoints:** store_workout.php, get_workouts.php, update_workout.php, delete_workout.php, upload_photo.php, get_photos.php, plus goal/user endpoints.
- **Photo Upload:** Use multipart/form-data POST; only set body in the `from:` parameter, not `httpBody`.
- **Error Handling:** All endpoints return JSON with `success` and `message` fields.
- **Elevation Processing:** run_elevation_processing.php, process_elevation.php, check_elevation.php, create_elevation_dir.php.
  - `store_workout.php` & `update_workout.php` now accept `altimeter_elevation_gain`.
  - `process_elevation.php` now prioritizes `altimeter_elevation_gain` before calculating from route/API, stores final gain in `elevation_gain`, and logs source in `elevation_calculation_source`.

## Best Practices & Lessons Learned
- **SwiftUI:**
  - Keep shared components (e.g., ImagePicker) in one file to avoid redeclaration errors.
  - Clean up unused variables and unnecessary `try` statements.
  - Use `.sheet(item:)` for complex sheet logic to avoid SwiftUI type-checking errors.
  - Resolve gesture conflicts within `List` rows containing buttons by applying `.buttonStyle(.plain)` to the inner button to ensure its action fires correctly instead of being intercepted by the row's `.onTapGesture`.
- **CoreMotion:**
  - Request `NSMotionUsageDescription` permission before starting altimeter updates.
  - Check `CMAltimeter.isRelativeAltitudeAvailable()` and `CMAltimeter.authorizationStatus()`.
  - Calculate cumulative *positive* gain from `CMAltitudeData.relativeAltitude` changes.
- **Networking:**
  - For uploads, use `URLSession.upload(for:from:)` and do not set both `httpBody` and `from:`.
  - Handle all possible backend data types in Decodable (e.g., elevation_gain as String, Double, Int, or null).
- **HealthKit:**
  - Map as many activity types as possible; default unknowns to weight training.
  - Deduplicate by date, type, and duration, always preferring backend data.
- **Backend:**
  - Normalize all enums/cases (e.g., workout_type) to avoid case mismatch bugs.
  - Use explicit SQL and error logging for easier debugging.
- **General:**
  - Document all changes and fixes in this file for future onboarding.

## Recent Changes (as of 2024-05-17)
- **Max Elevation Stat Added (May 2024):**
  - Added `maxElevationInFeet` computed property to `Workout` model to calculate the highest elevation point from route data.
  - Displayed "Max Elevation" in `WorkoutDetailView` below "Max Speed", formatted as "X ft".
  - This stat uses existing altitude data within `route_data` and does not require DB changes.
- **Speed Metric Support Added (May 2024):**
  - Added avgSpeed and maxSpeed parameters to the Workout model across the app.
  - Updated preview providers in WorkoutDetailView and EditWorkoutView to include these parameters.
  - Added support in the mapHKWorkoutToWorkout function to handle speed metrics from HealthKit.
  - Ensured compatibility with both distance-based workouts (run, ride) and non-distance workouts (weight training).
- **Elevation Processing System Added (April 2024):**
  - Implemented complete system for calculating elevation gain from route data.
  - Added backend files: run_elevation_processing.php, process_elevation.php, check_elevation.php, create_elevation_dir.php.
  - Created config.php for Google Elevation API key management.
  - Added functionality to HomeView to trigger processing by clicking on "Activity Feed" title.
  - System processes workouts in batches of 5 to avoid API rate limits.
  - Automatically detects and uses existing altitude data when available.
  - Falls back to Google Elevation API for routes without altitude data.
  - Elevation data is stored in the database and displayed in workout details.
- **UI Refinements (HomeView & ContentView - May 2024):**
  - Replaced `HomeView` background (parallax blobs, grid) with a new angled dark cobalt blue linear gradient and static radial splashes.
  - Refined custom tab bar in `ContentView`: Adjusted height, color (deep cobalt blue), removed top corner radius, and ensured background extends fully to the bottom edge.
  - Updated `HomeView` "Activity Feed" title font to `.largeTitle` (matching other views) and adjusted vertical padding.
  - Replaced custom pull-to-refresh in `HomeView` with standard `.refreshable` modifier to fix layout issues and simplify code.
- **Record Button Animation Removed:**
  - The pulsing and side-to-side animation for the record (start/stop) button in RecordingView was fully removed. The button now remains static in all states after saving or discarding a recording.
  - Ensured resetRecordingState() and related UI logic do not trigger any animation or movement for the record button.
- **Photo upload/display fully implemented:**
  - Added endpoints and UI for photo selection, upload, and display.
  - Fixed Swift compile errors (duplicate ImagePicker, unused vars, try statements).
  - Documented and explained common Xcode warnings (CFNetwork, QUIC, etc.).
- **HealthKit import and deduplication improved.**
- **Goal tracking, editing, and deletion fully functional.**
- **Elevation gain and pace calculations now robust and Strava-like.**
- **App Icon Update:**
  - Updated all required icon sizes in `Assets.xcassets/AppIcon.appiconset`.
  - Ensured `Info.plist` explicitly references the `AppIcon` set.
  - Verified and replaced outdated icons to resolve caching issues.
  - Cleaned build folder and rebuilt app to reflect changes.
- **CMAltimeter Elevation Integration (Current Date):**
  - Integrated `CoreMotion`'s `CMAltimeter` to capture relative altitude changes during recording.
  - Added `NSMotionUsageDescription` to `Info.plist` and permission request flow in `RecordingView`/`LocationManager`.
  - `LocationManager` now accumulates positive elevation gain (`relativeAltitudeGain`).
  - `Workout` model updated with optional `altimeterElevationGain`.
  - `SaveWorkoutSheet` sends `altimeterElevationGain` to `store_workout.php`.
  - Backend (`store_workout.php`, `update_workout.php`) modified to store `altimeter_elevation_gain`.
  - `process_elevation.php` updated to prioritize `altimeter_elevation_gain` over route/API methods for calculating the final `elevation_gain`.
  - Added `elevation_calculation_source` column to `workouts` table to track method used.
  - Fixed bug in `Workout.swift` where duplicate `CodingKeys` (for `avgSpeed` and `averageSpeed` mapping to "avg_speed") were introduced; removed duplicate property and key.
  - Fixed bug in `GoalsView.swift` where the `GoalRow`'s `.onTapGesture` intercepted taps meant for the inner "Log Weight" `Button`; resolved by applying `.buttonStyle(.plain)` to the button.
- **Goals View/ViewModel Cleanup (Current Date):**
  - Removed previous weight goal UI (`WeightGoalProgressChart`, `LogWeightSheet`, related buttons) from `GoalsView.swift`.
  - Removed weight log data handling (`weightLogs` property, `fetchWeightLogs`, `logWeight` functions) from `WorkoutViewModel.swift`.
  - Removed weight-specific calculation logic from `calculateGoalProgress` in `WorkoutViewModel.swift`.
  - This prepares the codebase for a clean reimplementation of weight goal tracking.
- **AI Coach Debugging (Current Date):**
  - Resolved OpenAI API connection issues by verifying and correcting the API key in `Info.plist` (fixed 401 Unauthorized error).
  - Corrected parameter mismatch in the `makeAPICall` function within `GPTService.swift`.
  - Fixed the dismiss functionality for the `AICoachView` by removing the explicit 'X' button and implementing an `.onTapGesture` on the entire view bubble to call `dismissCoach()` in `AICoachViewModel.swift`.
  - Added and subsequently removed extensive debug logging in relevant ViewModels and Managers.
  - Adjusted `GPTService` system prompts for both `reply(for:)` (post-workout feedback) and `getGreeting(activeGoals:)` (initial greeting) to use a more realistic, direct, and sarcastic tone as requested.
  - Fixed a race condition where `AICoachViewModel.fetchGreeting()` in `ContentView.task` could run before `WorkoutViewModel.fetchGoals()` completed in `HomeView.task`, causing the greeting to incorrectly state no goals were set. Moved the `fetchGreeting()` call into `HomeView.task` *after* `fetchWorkouts()` (which includes `fetchGoals()`) to ensure goals are loaded first.
- **Conversational AI Coach Implemented (Current Date):**
  - Added opt-in AI Coach feature accessible via a button (`brain.head.profile` icon) in `HomeView` header.
  - Integrated `Speech` framework via `SpeechManager` to handle speech-to-text input.
  - Added necessary `NSSpeechRecognitionUsageDescription` and `NSMicrophoneUsageDescription` keys to `Info.plist`.
  - Updated `AICoachView` to display chat history and provide controls (mic toggle, submit button, close button).
  - Implemented context preparation in `AICoachViewModel` to send workout/goal summaries to the AI.
  - Added `GPTService.getConversationalReply` function to handle queries based on provided context and chat history.
  - **Debugging:**
    - Fixed build errors related to Optional/Non-Optional types (`Goal.currentValue`/`targetValue`) and `Date`/`String` formatting in context helpers.
    - Resolved UI state issues where the Submit button would be prematurely disabled or automatically triggered after stopping speech recognition by refining Combine publisher filters and state management in `AICoachViewModel`.
    - Addressed confusing console logs by ignoring intentional cancellation errors (`kLSRErrorDomain 301`) from `SFSpeechRecognizer` and clarifying log messages.
- **Backend URL Troubleshooting (2024-05-17):**
  - **Issue:** App received persistent 404 Not Found errors when contacting PHP backend endpoints.
  - **Investigation:**
    - Checked/modified URL constants in `WorkoutViewModel.swift` (toggled `/FitnessApp/` path).
    - Examined local file structure (`find`, `list_dir`) and consolidated PHP files into the project root.
    - Direct `curl` test to `https://truaxcreative.com/get_workouts.php` still resulted in 404.
    - Web search results confirmed live server hosts files under `/FitnessApp/` subdirectory.
  - **Resolution:** Updated URL constants in `WorkoutViewModel.swift` to include the `/FitnessApp/` path (e.g., `https://truaxcreative.com/FitnessApp/get_workouts.php`), matching the server structure.
- **AI Coach Context & HealthKit Speed Fixes (Current Date):**
  - Updated the system prompt for the conversational AI Coach (`GPTService.getConversationalReply`) to provide more detailed instructions on personality, structure, and data usage.
  - Modified `AICoachViewModel.prepareContextForAI` and its helper `getRecentWorkoutsSummary` to include a summary of workouts from the last 30 days (up to 20), formatted with key stats (date, type, distance, duration, avg/max speed, elevation gain).
  - Fixed bug in `AICoachViewModel.getRecentWorkoutsSummary` where workout duration was accessed incorrectly (`workout.durationFormatted` instead of using the `formattedDuration` helper).
  - Fixed bug in `AICoachViewModel.getRecentWorkoutsSummary` where speed and elevation were formatted incorrectly (using raw string values instead of computed properties like `workout.avgSpeedInMph`, `workout.maxSpeedInMph`, `workout.elevationGainInFeet`).
  - Fixed bug in `WorkoutViewModel.mapHKWorkoutToWorkout` where it incorrectly tried to fetch non-existent `averageSpeed` and `maximumSpeed` statistics from HealthKit.
  - Updated `WorkoutViewModel.mapHKWorkoutToWorkout` to calculate average speed (m/s) from HealthKit workout distance and duration, storing it in `avgSpeed`. Max speed (`maxSpeed`) is set to `nil` for HK workouts as it's not directly available from standard statistics.
- **Workout Cloning Feature Added (2024-06-07):**
  - Implemented a "Clone" button in `WorkoutDetailView` for weight training activities.
  - Added `cloneWorkout` function to `WorkoutViewModel` to create a copy with a new ID, current date, and copied exercises/details.
  - Cloned workouts open in `ManualAddActivityView`, pre-filled for editing and saving as a new entry.
  - Optimized save/update logic in `WorkoutViewModel` and view loading in `WorkoutDetailView` to prevent unnecessary processing (like HealthKit sync or full data reloads) after cloning or saving, reducing hang time.
  - Fixed decoding errors in `SaveWorkoutResponse` and optional unwrapping errors in `WorkoutDetailView` related to `isManualEntry`.
- **Recent UI/UX Improvements (2024-06-07):**
  - Restored swipe-to-delete for activities in the HomeView activity feed by switching from ScrollView/LazyVStack to a List with .onDelete, using the existing ViewModel logic for backend and local deletion.
  - Fixed an issue where tapping an activity required a long press to open details by removing a conflicting .simultaneousGesture from the NavigationLink, ensuring a single tap opens the detail view.
  - Adjusted the bottom padding and safe area inset of the activity feed List to maximize visible content, making the scroll container taller and allowing more activities to be seen above the tab bar.
- **AI Coach Context/Greeting Fixes (Current Date):**
  - Fixed bug where the conversational AI context summarized workouts from the last 30 days instead of the current calendar month, leading to incorrect answers about monthly stats. Updated `AICoachViewModel.getRecentWorkoutsSummary` to filter by calendar month.
  - Fixed bug where the conversational AI context formatted workout distance incorrectly (e.g., -0.00 mi) due to using raw `distance` instead of `distanceInMiles`. Updated formatting in `AICoachViewModel.getRecentWorkoutsSummary`.
  - Fixed bug where the AI Coach greeting prompt only included goal targets, not current progress, leading to inaccurate greetings about remaining effort. Updated `GPTService.createGreetingPrompt` to include `goal.currentValue`.
- **Duplicate Activity Feed Issue Resolved (Current Date):**
  - Fixed HealthKit import deduplication logic in `WorkoutViewModel.processHealthKitWorkoutsInBackground` by adding a time tolerance (60 seconds) when comparing workout start dates, preventing minor time differences from creating duplicates.
  - Created and executed `cleanup_duplicates.php` script (using PDO via `db_connect.php`) to remove existing duplicate workouts from the backend database based on matching `user_id`, `workout_type`, and similar `date` (within 120s) and `duration` (within 60s), keeping the entry with the lowest ID.
- **Recurring Goal Calculation Fixed (Current Date):**
  - Refactored `WorkoutViewModel.calculateGoalProgress` to correctly handle recurring goals (weekly, monthly, yearly).
  - Dynamically calculates the start/end date interval for the *current* period based on `goal.timeFrame` using `Calendar.current.dateInterval`.
  - Uses the calculated `effectiveStartDate` and `effectiveEndDate` for filtering relevant workouts.
  - Fixed distance unit mismatch: Converts workout distance (meters) to goal unit (miles) *before* summing, ensuring `goal.currentValue` and `goal.targetValue` are compared in the same unit.
  - Added similar logic for duration goals (seconds to hours conversion).
  - Corrected expiration check for recurring goals based on the calculated `effectiveEndDate`.
- **AI Coach UI/UX & Functionality Improvements (Current Date):**
  - **Speech Error Handling:** Fixed issue where "No speech detected" error (Error Code 7 in `SFSpeechErrorDomain`) appeared despite successful transcription; `SpeechManager` now ignores this specific error if a valid final transcription exists.
  - **Response/Audio Sync:** Modified `AICoachViewModel` to delay showing the AI's text response in chat until audio playback begins.
  - **Loading Indicator:** Added `LoadingAnimationView` (animated bars) to `AICoachView`, displayed during GPT text generation and TTS synthesis (`isLoading` state adjusted accordingly).
  - **Voice Selection:** Implemented voice selection feature:
      - Added `TTSVoice` enum in `GPTService.swift` (corrected list based on API feedback, removing "ballad").
      - `AICoachViewModel` saves/loads selection via `UserDefaults`.
      - Added a `Picker` menu (speaker icon) in `AICoachView` header.
  - **Response Truncation:** Modified system prompt in `GPTService.makeAPICallWithHistory` to instruct the AI to prioritize concise, complete responses within the 300-token limit (`max_tokens` kept at 300 per user request).
  - **Response Formatting:** Removed explicit "Activity Roast" and "Performance Feedback" section titles from the system prompt, encouraging a more natural conversational flow.
  - **Audio Playback Fix:** Removed a premature `stopAudio()` call in `AICoachViewModel.submitQuery` that was interrupting playback before it could start.
  - **Speech Error Display:** Prevented the "No speech detected" error text from appearing in the UI by adding a filter in the `AICoachViewModel` error observer to map that specific error description to `nil`.
  - **UI Refinements:**
      - **HomeView Header:** Removed the manual elevation processing trigger (icon and button) from the main title. Renamed the main title to "AI Coach" and made it the new tappable element to launch the modal (removed the separate brain icon button).
      - **AICoachView Header:** Removed the "AI Coach" text and standalone `sparkles` icon. Changed the voice selection menu icon to use the orange `sparkles` icon.
      - **AICoachView Height:** Adjusted the `maxHeight` of the modal to 81% of screen height.
  - **Pause/Resume Timer Logic Fixed (June 2024):**
    - Fixed a bug where pausing an activity would stop the timer UI, but the timer would "jump" forward on resume, including the time spent paused.
    - Added new state variables in `RecordingView` to track total paused time (`pausedTimeAccumulated`) and the start of each pause (`pauseStartTime`).
    - On pause, the app records the time the pause started. On resume, it adds the paused duration to the accumulated total.
    - The timer now displays only the active (non-paused) time, providing an accurate workout duration that matches user expectations.
    - All new state is reset on stop/reset, and this logic is isolated to the timer and UI, with no impact on location tracking or other features.
- **Workout Photo Display Fix (June 2024):**
  - **Issue:** Photos uploaded to a workout (and present in the database) were not always displayed in the activity detail view unless the `photoUrl` property was set on the workout object. This could occur if the backend or upload process did not set this property, even though the photos existed in the database.
  - **Fix:** Updated `WorkoutDetailView.swift` so that the `.onAppear` block always calls `fetchPhotos()` for the workout, regardless of whether `workout.photoUrl` is set. This ensures that any photos associated with the workout are always fetched from the backend and displayed in the detail view.
  - **Why:** Prevents confusion and ensures a consistent user experience by always displaying all photos associated with a workout, matching the backend data. No other code or UI was changed, so this update is safe and does not affect other features.

## New Feature: Save as Route (June 2024)
- **Feature:** Users can now save any completed distance-based workout (run, ride, walk, hike) as a reusable Route.
- **Workflow:**
  - In `WorkoutDetailView`, a "Save as Route" button appears for eligible activities.
  - Tapping the button prompts for a route name and optional description.
  - The app sends a POST request to the new backend endpoint `save_route.php` with the workout ID and user input.
  - The backend copies route data, distance, and start/end coordinates from the workout to the `routes` table.
  - The new route is private by default (`is_public = 0`).
- **Backend:**
  - `save_route.php` validates ownership, extracts route data, and inserts a new row in the `routes` table.
  - Handles both `lat/lon` and `latitude/longitude` keys in route data.
  - Maps workout type to `suitable_for` (e.g., ride → road_biking).
- **Database:**
  - No schema changes required for initial implementation.
  - Route is saved with all relevant fields (title, distance, route_data, etc.).
- **Frontend:**
  - Added state and async function to `WorkoutViewModel` for route saving.
  - Added button and alert UI to `WorkoutDetailView`.
  - Fixed bug: `SaveRouteResponse` now expects `route_id` as `String?` to match backend JSON.
- **Lessons Learned:**
  - Always match backend JSON types with Swift Decodable structs (route_id bug).
  - Place action buttons in the main VStack to ensure visibility.
  - Use computed properties (like `hasRouteData`) for clean conditional UI logic.
- **Next Steps:**
  - Add a view to browse saved routes and compare ride/run times on those routes.
  - Consider adding columns to `routes` for best_time, num_completions, last_completed_at, etc. for future comparison features.

---

**For new contributors:**
- Read this file before making changes.
- Always check for duplicate code/components.
- Test all new features end-to-end (backend + frontend).
- Update this file with all major changes, lessons, and fixes.