Files
FamilyMealPlanner/CLAUDE.md
T
alexandrev-tibco 75c41b12ec comprobar el esquema de CloudKit antes de publicar
Un campo que esta en el @Model pero no en el esquema desplegado no sincroniza,
y no falla nada: los datos se quedan en el dispositivo que los escribio. Asi se
colaron nueve campos fuera de Production entre la 2.0 y la 2.1.2 sin que nadie
lo notara, incluidas las reglas de dia de las etiquetas y el desayuno/merienda
activados.

scripts/check_cloudkit_schema.py compara las propiedades almacenadas de cada
@Model con los CD_<campo> del esquema real (exportado con cktool), saltando
relaciones, computadas y el sufijo _ckAsset de los binarios.

fastlane submit y release abortan si falta algo; beta solo avisa, porque en
TestFlight es normal que el deploy a Production aun no se haya hecho. La lane
check_schema lo ejecuta suelto.

El deploy a Production sigue siendo manual: cktool no tiene subcomando y Apple
bloquea el endpoint de esquema en ese entorno.

Refs #38

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013su1ttRiMeMYxkZJ1Y3246
2026-09-14 17:23:22 +02:00

6.1 KiB

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:

# after bumping MARKETING_VERSION in the Xcode project to e.g. 1.1.6
git branch -m <old-version> 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.plistCFBundleShortVersionString 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:

plutil -replace CFBundleShortVersionString -string "<version>" MealMood/Resources/Info.plist
plutil -replace CFBundleShortVersionString -string "<version>" MealMoodWidget/Info.plist
plutil -replace CFBundleVersion -string "<next build>" MealMoodWidget/Info.plist

Credentials — all managed via pass (GPG-encrypted, syncs to Gitea)

Never hardcode credentials. Always read from pass:

# 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

Antes de mandar una versión a la App Store: comprobar el esquema de CloudKit

Un campo que está en el @Model pero no en el esquema de CloudKit no sincroniza, y no falla nada: los datos se quedan en el dispositivo que los escribió. Así se colaron nueve campos fuera de Production entre la 2.0 y la 2.1.2 (issue #38) — desayuno/merienda activados, sus horas, el recordatorio, las fotos del planificador y las reglas de día de las etiquetas no viajaban entre dispositivos.

fastlane check_schema                          # o directamente:
python3 scripts/check_cloudkit_schema.py --environment production

fastlane submit y fastlane release ya lo ejecutan y abortan si falta algo; fastlane beta solo avisa (en TestFlight aún puede faltar el deploy).

Si faltan campos: el esquema de development solo se actualiza al ejecutar la app contra ese entorno con iCloud activo, o importándolo a mano:

TOKEN=$(pass show apple/mealmood/cloudkit-token)
TEAM=$(pass show apple/mealmood/developer-team-id)
xcrun cktool export-schema --token "$TOKEN" --team-id "$TEAM" \
  --container-id iCloud.com.alexandrev.mealmood --environment development \
  --output-file /tmp/schema.ckdb
# añadir los CD_<campo> que falten (Bool/Int → INT64, String → STRING,
# Date → TIMESTAMP, Data → CD_<campo>_ckAsset ASSET) y:
xcrun cktool import-schema --token "$TOKEN" --team-id "$TEAM" \
  --container-id iCloud.com.alexandrev.mealmood --environment development \
  --file /tmp/schema.ckdb

El paso a Production no se puede automatizar: cktool no tiene deploy y Apple bloquea el endpoint de esquema en ese entorno. Lo hace Alexandre en CloudKit Console → Schema → Deploy Schema Changes → Deploy to Production.

TestFlight upload

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