# MealMood — Project Context for Claude Code ## App - **Name**: MealMood: Family Meal Planner - **Bundle ID**: com.alexandrevazquez.mealmood - **Platform**: iOS/iPadOS (also runs as iOS app on Mac) - **Current version**: 2.1.1 (dev; 2.1.0 with Apple Watch live in the App Store) - **Main branch**: `1.0.1` (production), feature branches named after versions ### Branch ↔ version convention (IMPORTANT) The active dev branch is named after the **current** `MARKETING_VERSION`, not the one it was created with. Whenever the iOS app version changes, **rename the branch to match**: ```bash # after bumping MARKETING_VERSION in the Xcode project to e.g. 1.1.6 git branch -m 1.1.6 # e.g. git branch -m 1.1.5 1.1.6 ``` Right now: `MARKETING_VERSION = 2.1.1` → active branch is `2.1.1`. Keep them in sync. (The build number bump within the same version does NOT rename the branch — only a version change does.) **`MARKETING_VERSION` in the Xcode project does NOT drive the shipped version.** Both Info.plists hardcode their values, so bumping the project setting alone builds an IPA with the old version and App Store Connect rejects it (`90062: must contain a higher version than that of the previously approved version`). A version bump means editing three places, and `fastlane`'s `increment_build_number` only handles the app's build number: | What | Where | Bumped by fastlane? | |------|-------|---------------------| | App version | `MealMood/Resources/Info.plist` → `CFBundleShortVersionString` | ❌ by hand | | App build | `MealMood/Resources/Info.plist` + pbxproj | ✅ | | Widget version + build | `MealMoodWidget/Info.plist` | ❌ by hand | The widget must match the app or the upload is rejected for a version mismatch. Since `fastlane beta` increments the build as its first step, set the widget to the **next** build number before running it: ```bash plutil -replace CFBundleShortVersionString -string "" MealMood/Resources/Info.plist plutil -replace CFBundleShortVersionString -string "" MealMoodWidget/Info.plist plutil -replace CFBundleVersion -string "" MealMoodWidget/Info.plist ``` ## Credentials — all managed via `pass` (GPG-encrypted, syncs to Gitea) Never hardcode credentials. Always read from `pass`: ```bash # Apple pass show apple/mealmood/apple-id # alexandre.vazquez@gmail.com pass show apple/mealmood/developer-team-id # 2825Q76T7H pass show apple/mealmood/appstore-connect-team-id # 128443966 pass show apple/mealmood/app-store-app-id # 6759255553 pass show apple/mealmood/app-specific-password-fastlane # for fastlane uploads # Firebase pass show firebase/mealmood/api-key pass show firebase/mealmood/project-id pass show firebase/mealmood/google-app-id # AdMob pass show admob/mealmood/app-id pass show admob/mealmood/banner-home-unit-id ``` ### TestFlight upload ```bash export FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD=$(pass show apple/mealmood/app-specific-password-fastlane) fastlane beta ``` ## Stack - SwiftUI + SwiftData (iOS 17+) - Firebase Analytics (`AnalyticsService.swift`) - Google AdMob (banner, free tier only, hidden on Mac) - StoreKit 2 (subscription `com.mealmood.premium.monthly`) - iCloud KV sync (`ICloudSyncService.swift`) — does NOT sync `isPremium` - Widget extension (`MealMoodWidget/`) — requires App Group `group.com.alexandrevazquez.mealmood` - Fastlane for CI/CD ## Premium logic - `PremiumSyncService` is the source of truth for premium state transitions - `isPremium` is determined **only** by StoreKit — never by iCloud sync - `Transaction.updates` listener in `ContentView` catches purchases in real-time - Tests: `MealMoodTests/PremiumSyncServiceTests.swift` ## Key files | File | Purpose | |------|---------| | `MealMood/ContentView.swift` | App root, premium sync, Transaction.updates listener | | `MealMood/Services/PremiumSyncService.swift` | Premium state machine (testable) | | `MealMood/Services/StoreManager.swift` | StoreKit 2 purchase/restore | | `MealMood/Services/ICloudSyncService.swift` | Cross-device sync (dishes, tags, plans — NOT isPremium) | | `MealMood/Views/Home/WeekPlanShareView.swift` | Export styles (default, school, vertical A4) | | `MealMoodWidget/MealMoodWidget.swift` | Home screen widget | | `fastlane/Fastfile` | `beta` and `release` lanes | | `fastlane/metadata/` | App Store metadata in 6 languages |