Caluru

Caluru — App Store Release Plan

End-to-end checklist to ship Caluru (com.agarcia.caluru) on the App Store, including RevenueCat subscriptions.

Status snapshot (2026-08-08)

Area Status Notes
Phase 0 pre-flight (camera string, export compliance) Done Fixed in project.pbxproj + Info.plist
RevenueCat + hard paywall in app Done SPM RevenueCat / RevenueCatUI; SubscriptionService; CaluruPaywallView
Release build compiles / automated tests Done See QA_RESULTS.md
Distribution archive + TestFlight smoke Done CAL-63
Paid Apps Agreement + IAP products Done CAL-67
Legal pages / company / URL alignment Done Milestone 2. Legal & company (CAL-68–74)
Manual QA matrix (11 flows) In progress CAL-61
App Store Connect listing In progress CAL-66
Remaining P0/P1 product + launch work Open in Linear See below — Linear is source of truth

Remaining work (Linear → Caluru Release Readiness):

Status Issues
In Progress CAL-61 manual QA · CAL-66 ASC listing · CAL-83 PR unit-test CI
Review P0: CAL-58 usernames · CAL-59 account-delete family · CAL-60 paywall prices · CAL-64 app icon PNG · plus P1 accuracy/legal + P2 UI polish (CAL-71, 75–77, 87–95)
Todo CAL-82 crash reporting · CAL-84 recipe image backfill

Do not treat unchecked boxes in older phase write-ups as current blockers if the matching Linear issue is Done. Re-check Linear before planning.

Related docs: APP_STORE_CONNECT.md · QA_MATRIX.md · QA_RESULTS.md · BACKEND.md


Table of contents

  1. Release phases overview
  2. Phase 0 — Pre-flight fixes
  3. Phase 1 — RevenueCat integration
  4. Phase 2 — Production backend
  5. Phase 3 — Website & legal
  6. Phase 4 — Apple Developer setup
  7. Phase 5 — QA & TestFlight
  8. Phase 6 — App Store Connect listing
  9. Phase 7 — Submit for review
  10. Phase 8 — Launch & post-launch
  11. Master checklist

1. Release phases overview

flowchart TD
    P0[Phase 0: Pre-flight fixes] --> P1[Phase 1: RevenueCat]
    P1 --> P2[Phase 2: Production backend]
    P2 --> P3[Phase 3: Website & legal]
    P3 --> P4[Phase 4: Apple Developer]
    P4 --> P5[Phase 5: QA & TestFlight]
    P5 --> P6[Phase 6: App Store Connect]
    P6 --> P7[Phase 7: Submit for review]
    P7 --> P8[Phase 8: Launch]
Phase Goal Est. effort
0 Fix known config bugs 0.5 day
1 Subscriptions + paywall in app 3–5 days
2 Supabase production ready 0.5–1 day
3 Public legal/support pages 1 day
4 Certificates, IAP products, capabilities 1 day
5 Manual QA on real devices 2–3 days
6 Screenshots, metadata, privacy labels 1–2 days
7 Archive, upload, review submission 0.5 day
8 Monitor, iterate Ongoing

2. Phase 0 — Pre-flight fixes

Status: Done (camera string + ITSAppUsesNonExemptEncryption = NO in Release Debug/Release build settings and Info.plist).

2.1 Camera permission string encoding ✅

INFOPLIST_KEY_NSCameraUsageDescription = "Caluru necesita acceso a la cámara para escanear códigos de barras y fotografiar alimentos.";

2.2 Export compliance ✅

INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO

Only valid while the app uses standard HTTPS/TLS (no custom encryption).

2.3 Release configuration

2.4 Verify automated tests still pass

export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer

xcodebuild test \
  -project Caluru.xcodeproj \
  -scheme Caluru \
  -destination 'platform=iOS Simulator,name=iPhone 17' \
  -only-testing:CaluruTests

xcodebuild archive \
  -project Caluru.xcodeproj \
  -scheme Caluru \
  -destination 'generic/platform=iOS' \
  -archivePath ./build/Caluru.xcarchive

Last recorded automated result: 29/29 PASS (QA_RESULTS.md). Re-run before each submission.

3. Phase 1 — RevenueCat integration

Status: Done in code — SPM packages linked; hard paywall ships. Remaining pricing edge cases tracked in Linear (CAL-60 / related CAL-32).

Caluru uses a hard paywall: after onboarding, MainTabView.canShowMainShell requires Caluru Pro (subscription.status == .pro). Non-subscribers never see Home, Plan, Recipes, Scan, or other main-shell features. The paywall is non-dismissible; Restore Purchases is on the paywall and in Profile → Preferences.

3.1 Free / Pro matrix (ships today)

Feature Free Caluru Pro
Onboarding (profile, targets, first-log nudge) Yes Yes
Main tab shell (Home, Plan, Recipes, Log FAB) No Yes
Daily calorie / macro tracking + food logging No Yes
Barcode scan (Open Food Facts) No Yes
Photo food recognition (GPT-4o Vision) No Yes
Recipe library + cooking mode No Yes
Weekly meal plan + shopping list No Yes
AI nutrition assistant No Yes
Family groups + leaderboard No Yes
Gamification (XP, streaks) No Yes
iCloud sync across devices No Yes
Restore Purchases Paywall only (pre-subscribe) Paywall + Profile → Preferences

Product rule: Free = onboarding only. Everything past onboarding requires an active Caluru Pro entitlement (or Restore Purchases that restores one).

3.2 App Store Connect — subscription products

Prerequisites: Paid Applications Agreement signed in App Store Connect (Agreements, Tax, and Banking).

  1. Go to App Store Connect → Apps → Caluru → Subscriptions.
  2. Create a Subscription Group (e.g. caluru_pro).
  3. Create products (suggested IDs — adjust to your naming convention):
Product ID Type Suggested price (ES) Notes
caluru_pro_monthly Auto-renewable, 1 month €4.99 Primary offer
caluru_pro_yearly Auto-renewable, 1 year €39.99 ~33% savings vs monthly
caluru_pro_yearly_trial Same as yearly + 7-day free trial €39.99 Optional intro offer
  1. Add localised display names and descriptions (es + en).
  2. Set subscription review screenshot (required for first IAP review — can be a paywall screenshot). Use CaluruPaywallScreenshotView in Xcode Previews if needed.
  3. Create Sandbox testers in App Store Connect → Users and Access → Sandbox.
  4. Submit the products with the app version (Guideline 2.1(b)). Creating the SKUs is not enough:
    • Open each product (caluru_pro_monthly, caluru_pro_yearly) → upload the review screenshot → add localized name/description until status is Ready to Submit.
    • On the app version page (1.0), under In-App Purchases and Subscriptions, add both products.
    • Submit the version. Apple reviews IAPs only when they are attached to a binary. Do not leave them in “Missing Metadata” or unattached.

3.3 RevenueCat dashboard

  1. Create account at app.revenuecat.com.
  2. New project → iOS app → Bundle ID com.agarcia.caluru.
  3. Connect App Store Connect:
    • Preferred: App Store Connect API key (Issuer ID, Key ID, .p8 file)
    • Legacy: App-Specific Shared Secret
  4. Create Entitlement: pro (or caluru_pro).
  5. Create Offering: default with packages:
    • $rc_monthly → caluru_pro_monthly
    • $rc_annual → caluru_pro_yearly
  6. Copy the Public API key (iOS) — safe to ship in the app.
  7. (Optional) Enable RevenueCat Paywalls in the dashboard for remote paywall A/B tests without app updates.

3.4 Add RevenueCat SDK (Swift Package Manager)

In Xcode: File → Add Package Dependencies

https://github.com/RevenueCat/purchases-ios

Add products:

Pin to latest stable major version (e.g. 5.x).

3.5 Config & secrets

Add to Config.xcconfig.example (and your local Config.xcconfig):

REVENUECAT_API_KEY = appl_XXXXXXXXXXXXXXXXXXXXXXXX

Add to Info.plist:

<key>REVENUECAT_API_KEY</key>
<string>$(REVENUECAT_API_KEY)</string>

Read in a small config enum (mirror SupabaseConfig pattern in Config.swift).

3.6 New app code (implementation plan)

3.6.1 SubscriptionService.swift

Singleton @Observable service wrapping RevenueCat:

// Caluru/Services/SubscriptionService.swift (new file)

import RevenueCat

@Observable
final class SubscriptionService {
    static let shared = SubscriptionService()

    var isPro: Bool = false
    var currentOffering: Offering?
    var customerInfo: CustomerInfo?

    func configure() {
        Purchases.logLevel = .debug // .warn in Release
        Purchases.configure(withAPIKey: RevenueCatConfig.apiKey)
    }

    /// Call after Sign in with Apple so purchases restore across devices.
    func identify(userId: UUID) async {
        _ = try? await Purchases.shared.logIn(userId.uuidString)
        await refreshCustomerInfo()
    }

    func logOut() async {
        _ = try? await Purchases.shared.logOut()
        await MainActor.run { isPro = false }
    }

    func refreshCustomerInfo() async {
        guard let info = try? await Purchases.shared.customerInfo() else { return }
        await MainActor.run {
            customerInfo = info
            isPro = info.entitlements["pro"]?.isActive == true
        }
    }

    func restorePurchases() async throws {
        let info = try await Purchases.shared.restorePurchases()
        await MainActor.run {
            customerInfo = info
            isPro = info.entitlements["pro"]?.isActive == true
        }
    }
}

Wire in CaluruApp.swift .onAppear / launch:

SubscriptionService.shared.configure()

After successful Apple Sign In (AuthService.handleAppleSignIn), call:

await SubscriptionService.shared.identify(userId: session.user.id)

On sign-out / account deletion:

await SubscriptionService.shared.logOut()

3.6.2 CaluruPaywallView (custom hard paywall)

Ship a non-dismissible custom paywall (interactiveDismissDisabled). Do not offer a close/skip control.

RevenueCatUI.PaywallView(displayCloseButton: true) is not used for the post-onboarding gate.

3.6.3 Feature gating

Primary gate (ships today): MainTabView — canShowMainShell is true only when onboarding is complete and subscription.status == .pro. Otherwise present non-dismissible CaluruPaywallView.

Per-feature checks (assistant, photo scan, family, server openai-proxy 402) remain defense-in-depth; they are not a free tier.

3.6.4 Paywall placement (UX)

Moment Trigger
End of onboarding (after first-log nudge) Hard paywall — non-dismissible; subscribe or Restore Purchases
Cold launch / entitlement lost Same hard paywall if status == .notPro
Profile → Preferences → Caluru Pro Status, renewal date, Restore Purchases (after unlock)

Always show Restore Purchases on the paywall (App Review requirement).

3.6.5 Profile subscription management

Add to AppPreferencesView or ProfileView:

Client-side gates are not enough — users can bypass them. For AI cost control, add a server check in openai-proxy:

  1. RevenueCat Webhooks → Supabase Edge Function revenuecat-webhook updates user_subscriptions table.
  2. openai-proxy checks is_pro before forwarding to OpenAI.
  3. Return 402 Payment Required if not Pro (app shows paywall).
supabase/functions/revenuecat-webhook/index.ts   (new)
supabase/migrations/YYYYMMDD_user_subscriptions.sql   (new)

Webhook events to handle: INITIAL_PURCHASE, RENEWAL, CANCELLATION, EXPIRATION.

Update Localizable.xcstrings / LegalView:

App Store Connect also requires a Terms of Use (EULA) URL for apps with subscriptions.

3.6.8 RevenueCat testing checklist

3.6.9 App Review notes + store metadata (subscription-required)

Apple rejects free-download apps that require a subscription if the App Store description does not say so clearly (Accurate Metadata). Lead the description with paid-access language — see APP_STORE_CONNECT.md.

Include in App Review Information (first line):

IMPORTANT — PAID ACCESS ONLY: Caluru is a free download but requires a paid Caluru Pro subscription (or Restore Purchases) to use the app. No free tier after onboarding. Hard paywall is non-dismissible. Restore Purchases is on the paywall and in Profile → Preferences.


4. Phase 2 — Production backend

Supabase project: opxqcnxiqbitezragpch

4.1 One-time setup

brew install supabase/tap/supabase
supabase login
supabase link --project-ref opxqcnxiqbitezragpch

4.2 Deploy edge functions

supabase functions deploy openai-proxy --project-ref opxqcnxiqbitezragpch
supabase functions deploy delete-account --project-ref opxqcnxiqbitezragpch
# After Phase 1.6.6:
supabase functions deploy revenuecat-webhook --project-ref opxqcnxiqbitezragpch

4.3 Secrets

supabase secrets set OPENAI_API_KEY=sk-... --project-ref opxqcnxiqbitezragpch
supabase secrets set OPENAI_HOURLY_LIMIT=50 --project-ref opxqcnxiqbitezragpch
# After webhook (Authorization header value RevenueCat sends; Bearer prefix optional):
supabase secrets set REVENUECAT_WEBHOOK_SECRET=... --project-ref opxqcnxiqbitezragpch

4.4 Database migrations

supabase db push --project-ref opxqcnxiqbitezragpch

Existing migrations:

4.5 Auth — Sign in with Apple

In Supabase Dashboard → Authentication → Providers → Apple:

In Apple Developer → Identifiers:

4.6 Production verification

# openai-proxy up, auth required
curl -s -o /dev/null -w "%{http_code}" -X POST \
  "https://opxqcnxiqbitezragpch.supabase.co/functions/v1/openai-proxy" \
  -H "Content-Type: application/json" -d '{}'
# Expect: 401

Manual smoke test (signed-in user on device):

  1. Sign in with Apple
  2. Assistant → send message → GPT reply
  3. Scan → photo mode → recognized foods
  4. Profile → Eliminar Cuenta → clean state

App Store Connect requires public HTTPS URLs. In-app legal text alone is not sufficient.

5.1 Pages to host on https://caluru.app

URL Content source
https://caluru.app/privacy legal_privacy_content in Localizable.xcstrings
https://caluru.app/terms legal_terms_content in Localizable.xcstrings
https://caluru.app/support Contact: support@caluru.app, FAQ, subscription help
https://caluru.app Landing page (optional but recommended)

caluru.app did not respond at time of writing — set up DNS + hosting before submission.

5.3 In-app consistency


6. Phase 4 — Apple Developer setup

Team ID: S95DC78UWJ (already in project)

6.1 App ID capabilities

In Apple Developer → Identifiers → com.agarcia.caluru:

6.2 CloudKit

6.3 Certificates & profiles

Xcode automatic signing (already configured) should handle this when you archive with a distribution certificate.

6.4 Archive & upload

  1. Xcode → Product → Archive (Release, Any iOS Device)
  2. Organizer → Distribute App → App Store Connect → Upload
  3. Resolve any validation warnings (missing icons, entitlements, etc.)

7. Phase 5 — QA & TestFlight

Complete the manual matrix in QA_MATRIX.md on a physical iPhone with a Release build.

7.1 Priority flows (P0 — block submission if broken)

# Flow Device
1 Fresh install → full onboarding → hard paywall (no skip) Simulator or device
2 Sign in with Apple → kill app → session restored Physical
10 Account deletion → clean state → re-onboard Physical + test Apple ID
— Sandbox subscription purchase → main shell unlocks Physical
— Restore Purchases on paywall → Pro unlocks Physical
4 Photo food scan (Pro) Physical + auth
5 AI Assistant (Pro) Auth required

7.2 TestFlight

  1. Upload build from Organizer
  2. App Store Connect → TestFlight → Internal testing (up to 100 team members)
  3. External testing (optional) — requires Beta App Review
  4. Fix all P0/P1 bugs before App Store submission

7.3 Document results

Update QA_RESULTS.md with Pass/Fail per row. File Linear/GitHub issues for P0/P1.


8. Phase 6 — App Store Connect listing

Use copy from APP_STORE_CONNECT.md. Description must lead with the paid-subscription requirement (Accurate Metadata).

8.1 Required assets

Asset Spec
App icon Caluru/Assets.xcassets/AppIcons/appstore.png (1024×1024)
iPhone 6.7” screenshots 1290×2796 (required)
iPhone 6.5” screenshots 1284×2778 (required)
iPad screenshots If supporting iPad (TARGETED_DEVICE_FAMILY = 1,2)
Subscription screenshot Paywall UI for IAP review

Branding: use Caluru only — not NutriGuru, GuruDeSalud, or Guru de Salud.

8.2 Metadata

Field Value
App Name Caluru
Subtitle (es) Nutrición, recetas y asistente IA
Bundle ID com.agarcia.caluru
Primary language Spanish (es)
Category Health & Fitness
Privacy Policy URL https://caluru.app/privacy
Terms of Use URL https://caluru.app/terms (required for subscriptions)
Support URL https://caluru.app/support

8.3 App Privacy questionnaire

Declare data collected per PrivacyInfo.xcprivacy and APP_STORE_CONNECT.md:

Partners to name where ASC asks: Apple, Supabase, OpenAI, Open Food Facts, RevenueCat. Tracking = No. Hosted policy must match (§2.6 / §4 / §5 on https://caluru.app/privacy.html).

Tracking: No (NSPrivacyTracking = false)

8.4 Age rating

Answer questionnaire honestly. Nutrition + AI assistant typically rates 4+ or 12+ depending on health/medical framing. Emphasise “educational, not medical advice.”

8.5 Pricing


9. Phase 7 — Submit for review

9.1 Pre-submit checklist

9.2 App Review Information

Field Suggested value
Contact Your name, phone, email
Notes See template below
Sign-in required? Yes for AI proxy / family / account features (Sign in with Apple). Main shell still requires Caluru Pro after onboarding.

Review notes template:

IMPORTANT — PAID ACCESS ONLY: Caluru is a free download but requires a paid Caluru Pro subscription (or Restore Purchases) to use the app. There is no free feature tier after onboarding. The App Store description states this up front.

After onboarding, a non-dismissible hard paywall appears. Home, meal logging, plans, recipes, barcode/photo scan, AI assistant, and family unlock only with an active Caluru Pro entitlement.

Subscriptions: test with a Sandbox Apple ID. Plans are monthly and yearly (yearly may include a free trial when eligible).

Restore Purchases: on the hard paywall (required for returning subscribers) and in Profile → Preferences → Caluru Pro after unlocking.

Sign in with Apple is used for cloud AI, family, and account sync once Pro unlocks the shell.

The app provides educational nutrition information only, not medical advice.

HEALTH SOURCES (Guideline 1.4.1): Calorie and macro targets cite published methods with tappable links. Reviewers can open citations without a subscription:
1. Complete onboarding until the calorie-target screen (“Here’s what your body needs”).
2. Directly under the calorie ring, tap “View sources”, or scroll to the Health sources list and tap any row (opens the paper or database).
3. After unlocking Pro: Profile → Health sources (first settings row), Profile → Legal, Assistant, Scan, and Weight tracking.
Public page: https://caluru.app/sources.html

IN-APP PURCHASES submitted with this binary (Guideline 2.1(b)):
- caluru_pro_monthly (auto-renewable, 1 month)
- caluru_pro_yearly (auto-renewable, 1 year; intro trial when eligible)
Please review these subscriptions together with the app. After onboarding the first screen is the Caluru Pro paywall.

Test flow:
1. Fresh install → complete onboarding
2. Hard paywall appears (cannot dismiss / skip)
3. Tap Restore Purchases (Sandbox Apple ID with prior purchase) OR purchase Caluru Pro
4. Main shell unlocks — exercise logging, scan, assistant as needed
5. Account deletion: Profile → Eliminar Cuenta

9.3 Submit

App Store Connect → version page → In-App Purchases and Subscriptions → add caluru_pro_monthly and caluru_pro_yearly → Add for Review → select build → submit.

Typical review: 24–48 hours. Respond quickly to rejection notes.

9.4 Common rejection reasons for this app type

Reason Prevention
Missing account deletion Already implemented — verify it works
Subscription terms unclear Legal page + paywall fine print
Missing EULA link on product page (3.1.2) Add https://www.apple.com/legal/internet-services/itunes/dev/stdeula/ to the App Description for every localization (see APP_STORE_CONNECT.md); in-app links and Privacy Policy URL are not enough
IAP products not submitted with the binary (2.1(b)) Attach caluru_pro_monthly + caluru_pro_yearly on the version page; upload a paywall screenshot on each product
Health info without citations (1.4.1) Tappable sources on onboarding targets and Profile → Health sources; hosted https://caluru.app/sources.html
AI without disclosure Mention AI in description and review notes
Broken Sign in with Apple Test on physical device before submit
Privacy label mismatch Cross-check with PrivacyInfo.xcprivacy

10. Phase 8 — Launch & post-launch

10.1 Release

10.2 Week 1

10.3 Iteration backlog


11. Master checklist

Source of truth for open work: Linear project Caluru Release Readiness. Checkboxes below mirror code/process state as of 2026-08-08; prefer Linear status when they disagree.

Code & config

Apple & App Store Connect

Backend

QA

Submit / launch ops

Open Review / Todo (do not regress)

Track merge/close in Linear — do not treat as closed until status is Done:


Quick reference commands

# Tests
xcodebuild test -project Caluru.xcodeproj -scheme Caluru \
  -destination 'platform=iOS Simulator,name=iPhone 17'

# Archive
xcodebuild archive -project Caluru.xcodeproj -scheme Caluru \
  -destination 'generic/platform=iOS' \
  -archivePath ./build/Caluru.xcarchive

# Supabase deploy
supabase functions deploy openai-proxy --project-ref opxqcnxiqbitezragpch
supabase functions deploy delete-account --project-ref opxqcnxiqbitezragpch
supabase functions deploy revenuecat-webhook --project-ref opxqcnxiqbitezragpch
supabase db push --project-ref opxqcnxiqbitezragpch

Last updated: 2026-08-08 — align with Linear Caluru Release Readiness before each submit.