Astra started as one app: real natal chart, daily horoscope, compatibility. Live on Google Play, queued for App Review. The Play listing went through cleanly. App Review came back with a 4.3(b) — the category is saturated, the build "doesn't sufficiently differentiate from other horoscope apps." Repositioning the same product as a "birth chart tool" reads inside the same category to the same reviewer. Resubmitting unchanged is a slow leak.
The reflex move is to retreat — pull the iOS listing, ship Android, call it a day. We did something else.
Two products, one repo
iOS gets a different app. Astra Oracle: pick a voice — Stoic, Jungian, inner child, future self, villain, tarot — ask one question, read the reply once, on the whole page. No charts, no signs, no daily feed. Lifestyle / reflection, not astrology. The voice is what carries the product, and there are no direct competitors at this angle.
Android keeps the original. Astra: Daily Horoscope & Chart is now live and free on Google Play — real chart, real transits, daily reading in the tone you pick. Play has no 4.3(b) analogue, the category is healthier there, and the existing build is already a strong fit for Android users who want this exact shape.
One Flutter repo. A compile-time constant, PLATFORM_FLAVOR, switches the router and feature set:
--dart-define=PLATFORM_FLAVOR=android→ the horoscope router (splash, onboarding, today, weekly, monthly, compatibility, birth chart, profile). Ships to Play.--dart-define=PLATFORM_FLAVOR=oracle→ the Oracle router (ask, library, settings, six lenses). Ships to TestFlight.
Both routers live in lib/core/routes/; the unused one is tree-shaken out in release mode, so the iOS IPA carries zero horoscope strings and the Android AAB carries zero Oracle code. We verify that with a binary check (scripts/ios_check_oracle_binary.sh) wired into both fastlane lanes — if a v1 horoscope string ever shows up in the shipped App.framework Dart snapshot, the lane fails before upload. That's the safety net against re-triggering 4.3(b) on a future submission.
What's shared
The work that's the same on both platforms keeps living in one place: anonymous Firebase Auth as a quota key, the Cloud Functions LLM proxy, the encrypted Hive storage layer, the ReviewService trigger, the dark cosmic theme, Cormorant Garamond as the hero face. The name "Astra" stays. The brand stays.
What's different is what reaches the user — and what the app pitches itself as on the store.
One backend, two cohorts
Firebase Analytics was finally wired in this week, on both flavors. The wrapper sits in lib/core/analytics.dart and exposes a FirebaseAnalyticsObserver that both GoRouter configs include in their observers list, so screen_view auto-logs across the entire app graph without per-screen plumbing. After Firebase init, we stamp the build flavor as a user property:
await Analytics.instance.bootstrap(userId: auth.currentUser?.uid);
// inside: setUserProperty(name: 'platform_flavor', value: ...)
That gives us one Firebase project, two cohorts (oracle and horoscope), and clean cohort splits in every dashboard. The anonymous UID is the same key the Cloud Functions quota check uses, so analytics events line up with backend quota events without any extra identifier crossing the wire.
Where the two products are right now
- Astra on Google Play — live, free, listing under Astra: Daily Horoscope & Chart. play.google.com/store/apps. Landing: codeensis.com/astra.
- Astra Oracle on TestFlight — first Oracle binary (
1.0.1+6) distributed to internal testers. App Store Connect listing rewritten end-to-end: name, subtitle, promo, keywords (no astrology / horoscope / zodiac / natal), description, five framed Pro Max screenshots, new privacy/marketing/support URLs at/oracle/*. Build sits in "Prepare for Submission" awaitingfastlane release_submit. Landing: codeensis.com/oracle.
What we held back
- Monetization on Oracle. v1 ships free with a 3-questions-per-day quota. No paywall, no subscription. Locking down the product shape and getting through App Review is the gating dependency; pricing comes in 2.1.
- Russian localization on Oracle. en-US only at launch. The six lens prompts need to sound native in each locale (no auto-translation in voices) — that's its own pass, sequenced after the first iOS approval.
- The old chart-tool submission in ASC. The previous build sits in review queue; we'll either explicitly withdraw it or let it expire, depending on which is faster. Doesn't block the Oracle path.
What this lets us do
Two stores, one repo, one team. The iOS surface is positioned where 4.3(b) doesn't fire and there is no obvious competitor. The Android surface is positioned where the original product already fits the market it's in. If either side breaks out we can invest in it without rewriting the other. If neither does, the cost was a router split and a binary gate — not a second codebase.
The default move after a rejection is to rebuild. The better move, when the code is already good, is to re-pitch.