AstroMatrix Orientation
Internal orientation/ 2026-09-11/ 5 repos · 3 doc trees

Everything, and where it lives

One page to orient from. It collates and reconciles the scattered docs — it introduces no new findings, and every claim points at the doc that owns it.

docs/ROADMAP.md astromatrix-nodejs Read §3 and §6 if you read nothing else.
01

The company, in one paragraph

AstroMatrix is a profitable, deeply-built astrology product — roughly $17–20K/month, 87,467 MAU, 4.6★ — whose Android install base has declined about 35% over four years, steadily. Three years of real product depth work were effective at what they targeted, but no feature ever had user-creation as its explicit job.

Separately, the loyal base that remains is badly monetised (5.37% ever paid, $0.20 ARPMAU) — largely because the paths to pay were structurally broken, not because demand is absent. Those are two distinct problems, a growth-engine failure and a packaging failure. They have separate fixes and should not be collapsed into one narrative.

The single company question this quarter: can AstroMatrix generate users from users?

02

The five lanes

There is no single project here. There are five parallel lanes that each acquired their own tracker — which is the actual source of the “it’s all over the place” feeling.

LaneWhere it livesIts trackerState
A · Product & growth strategyflutter/docs/, nodejs/docs/product/FEATURE-LIST.mdconverged
B · Engineering firesnodejs/docs/SUPPORT-fire-*master fire report~147 issues
C · Website & SEOastromatrix-public/memory: SEO auditaudit done, queue open
D · Paid ads & economicsastromatrix-public/docs/SESSION-HANDOFFday-3 read pending
E · Infra, DR & migrationnodejs/docs/INFRA-*, MIGRATION-*on a 3–6 mo clock
36issues open
54fixed, not yet live
65closed

Lanes A and B are the same product at two altitudes. A fire-report ISSUE number is the engineering half of most roadmap items — roadmap “link the store” is ISSUE-76. C and D are the acquisition side and barely touch A/B. E is a clock: the legacy .NET database goes offline in 3–6 months and the tarot migration is running right now.

03

The roadmap, reconciled

Four documents read like roadmaps. They are not four plans — they are one plan at four altitudes. This is the main thing making the situation feel confusing.

DocumentWhat it actually is
q4-growth-plan-2026-09.mdThe committed bet. One question, one mechanism, one scoreboard.
competitive-position §7The full sequence (Horizon 0/1/2). H1.1–1.6 is the Q4 bet, decomposed.
FEATURE-LIST.mdThe ranked forward list. Collates both repos, flags the numbers that changed.
STRATEGY-brief §5The revenue-unblocking subset — effectively Horizon 0, stated separately.

Read in that order and nothing conflicts. Use FEATURE-LIST as the working list and competitive-position §7 as the sequence.

Horizon 0 This month No new systems. Prerequisite for everything else.

Every item is small and sits on a verified defect. None of it is a bet.

#ItemOwnerStatus
0.1Delete the false paywall bullets — things the free tier already hasISSUE-147in working tree
0.2Raise lifetime to ~$79.99 — do not retire it. Stop discounting it. Raise annual too. 38% of units, 60% of revenuestore consolesdecision, not code
0.3Fix two false store claims — “10 profiles free” is really 5; a 6-month plan that doesn’t existstore-listing-copyblocked on 3 prices
0.4Link the store (1 link, 0.2% reach) and give reports a route (no route at all, 7.8% reach, plateaued)ISSUE-76highest value here
0.5Decide the language gate before localised reports ship ungatedFEATURE-LIST §Cwindow closing — see §6
measured 09-11 Pricing — supersedes “reprice 3–4× or retire”

The last price rise already ran the experiment. Lifetime went $29.99 → $49.99 in Oct 2025. Same month year-over-year, which controls for the Nov–May slump that hit every SKU:

Jun–Augunitsrealized pricerevenue
2025637$27.24$17,355
2026598$48.55$29,062
change−6%+78%+68%

Keep vs retire is settled by arithmetic, not elasticity. Retiring only pays if enough lifetime buyers switch to annual. Break-even switch rate = lifetime net ÷ annual net over its life (annual $39.99 × renewal depth × 0.798 payout):

lifetime priceannual ×1.5×2.04×3.0×4.0
$49.9983%61%42%31%
$59.99never74%50%38%
$79.99never98%67%50%
$99.99nevernever83%63%

The only direct evidence: the +78% rise moved 13% of lifetime-inclined buyers elsewhere — a lower bound, since a price rise is a smaller shock than removal. Raising the price is what settles the keep/retire question — at $79.99, retiring can never pay. Lifetime money also arrives today; annual money arrives over 2+ years, conditional on a renewal depth nobody has measured per SKU.

The genuinely mispriced SKU is annual, not lifetime. At $39.99 it is dominated on both sides: quarterly is $19.99/quarter = $79.96/year, and lifetime is $49.99. Annual is both the cheapest single year and cheaper than two years of quarterly. Quarterly outsells the new annual 480 : 126 — and quarterly is displayOrder: 1, shown first. Raise annual and reorder before adding any new SKU. Do not add a fourth subscription — the ladder is incoherent and a new rung makes it worse.

execution Price fields only — no app release, three consoles

Verified: products_config.json carries no prices; there are no hardcoded prices in any .dart/.ts/.tsx; and the 30% discount works by swapping to a separate product id, not a coupon. So changing two price fields is sufficient.

#Consolefull_purchase2025discount_full_purchase2025
1Google Play$49.99 → $79.99→ $55.99
2App Store Connect$49.99 → $79.99→ $55.99
3Stripe (web — easily missed)$49.99 → $79.99$34.95 → $55.99

⚠ The trap. A Stripe Price amount cannot be edited — you make a new Price and repoint the link. /payments/get lists every active link with no dedupe by shortcode, so leaving the old one active shows two lifetime products at two prices. Deactivate it in the same change.

⚠ The analysis tooling goes quietly wrong. _payments.js’s usdPriceByType() medians price across all history per SKU. After this, full_purchase2025 holds both $49.99 and $79.99 and its median drifts for months — every per-SKU revenue figure stays plausible and becomes wrong. Patch it to bucket by (SKU, month) before changing prices.

Decide what the discounted price really is. Every website CTA now routes through applyDiscount?code=WEBPROMO1, which serves the discount SKU — so web-acquired buyers pay $55.99, not $79.99. That is +12%, not +60%. Today that path is tiny (21 of 1,551 purchases), but the CTA fix was built to grow it. $55.99 is the price most new web users will actually see — choose it deliberately.

Why 0.4 matters more than it looks: every conversion baseline measured before 2026-09-07 was measured against a funnel whose exit did not exist — all 12 upgrade gates dead-ended on an unregistered /premium route. Do not scope pricing against those numbers.

Horizon 1 The quarter The committed bet: relationships as structural distribution.

The loop. Existing user adds a partner → gets a live, current-state relationship reading → sends it → partner opens it with no install required → partner sees content about themselves → creates an account to unlock their side. Deliberately not gated: the first user gets full value alone, which isolates the variable being tested.

The test is not engagement and not shares. It is new users who appeared because existing users brought them.

#ItemNote
1.1Relationship transits — give the relationship matrix the all transits/weekly/monthly lenses the self-matrix already hasA prerequisite, not a parallel idea. Three of the Q4 artifact’s four lines are this. ⚠ The docs call this “not an engine build” — that is wrong, see below
1.2A one-word verdict on every pair, 12 facets underneathWe compute more and conclude less; a user can’t retell what the app said
1.3Relationship types — friend, family, colleague9 of 12 facets are already non-romantic. Re-labelling, not new astrology. Ship after the bet
1.4Invite-from-chart flow155,361 accounts already hold another person’s chart
1.5Shareable pair card — “Us”, not “me”The competitors’ actual unit of growth
1.6Fix the cold-arrival landing first93.2% of share-link arrivals bounce under 10s. Do this before 1.5 sends more traffic at it
1.9Surface the comparison to the 72.2% who never make oneO(n²) — 79,800 pairs at 400 charts. Pre-filter and cap
1.13Per-user event measurementSee §5. Gates grading the entire quarter
correction 09-11 Relationship transits are an engine change

Both FEATURE-LIST §8b.1 and competitive-position 1.1 claim the engine already supports this — “it treats the composite as a natal chart” and “its orb system has a transit slot for synastry/composite.” Both supporting points are misreadings. Verified in the engine source:

  • CalcChart() has exactly two chart slots — dPos and TransitPos. A chart is natal (syn=0), synastry (syn=1), or transit (syn=2).
  • applyComposite() consumes both slots to build the composite — it midpoints person 1 and person 2 into dPos. Line 869 only labels the result like a natal chart.
  • Transits to a composite need three charts at once — person 1, person 2, and the sky now. The two-slot model can’t express that in one call.
  • Orbs[] is [0]=natal, [1]=synastry/composite, [2]=transit — three orb settings for three chart types, not a transit slot for composite.

Three options, not equivalent. (1) Transits to the composite — what the plan implies; needs a third slot or a two-pass API. Real engine work. (2) Davison chart — the midpoint in time and space, so it has a genuine date/time/location and runs through the existing engine as an ordinary natal chart with syn=2 transits, no engine change. Not implemented anywhere. (3) Each partner’s own transits side by side — works today with two existing calls, but answers a different question.

Name which one 1.1 is before estimating it. The asymmetry itself is real: the self matrix has six lenses, the relationship matrix has none.

Instrumentation is a prerequisite, not a follow-on. The Q4 plan says so explicitly, because shipping the loop without it reproduces the exact failure the whole strategy diagnosed: real work shipped with no way to know if it moved anything.

Horizon 2 The year New models, sequenced behind the bet.
  • The Coach / Oracle session layer — full spec already written
  • The Oracle as a shareable weekly card
  • Practitioner marketplace — 4,011 Astrologers at 54.24% conversion; AstroTalk is a $43M comparable, and it’s built on an asset competitors can’t replicate
  • Audio — the stack is shipped; contingent on a hire

The twelve-month concentration four systems, not twenty features

Matrix

The first-level answer everywhere. Raw astrology becomes evidence, not interface.

Relationships

Persistent people, not synastry inputs.

Timeline

The only one of the four with no roadmap entry anywhere.

Memory

~4.5M user-written questions we already hold. The pillar that cannot be bought.

AI sits across these — it is not one of them.

And a fifth thing not to postpone: surfacing. The product is already large. The next win is not building more — it is making users realise what they already have.

Explicitly deprioritized this quarter

More chart types · generic interpretations · broad AI-chat · journaling and meditation expansion · a generic social feed · large UI redesign · extensive ASO iteration · growth-content campaigns · offline/local calculation (real, but a practitioner monetisation play, not this quarter).

04

Numbers — quotable, and retracted

Before quoting any level, run node scripts/_analytics.js verify — about 2.5 minutes against live production. Owner: FINDINGS-2026-09-11.md.

Verified safe to quote

Accounts460,694 · 5.37% ever paid · 3.73% paying now
The one axis that explains revenueCharts held. 1 chart → 1.58% · 2–3 → 5.92% · 4–7 → 13.94% · 8–15 → 34.29% · 16+ → 54.24%. A 34× monotonic spread; behavioural segment collapses to ~1.00× once charts held is controlled for
The two opportunities~11,500 accounts hold 8+ charts and never paid — a conversion play. And 300,710 Solo accounts at 1.58%, where the 1→2 chart step is the real activation event
Scale87,467 MAU (Android 43,983 / iOS 43,128) · $0.20 ARPMAU · ≈$17,195/mo · ads ≈29% of revenue
The declineAndroid active device installs 137,256 → 88,659 (Dec 2022 → Aug 2026). Steady, not accelerating. Android-only — no verified iOS equivalent exists
Blended LTV, all new accounts≈$0.85 per new user ($0.50 IAP + ~$0.32 ads). Correct for what it measures — a blended average over overwhelmingly organic installs. ⚠ Not the paid-ads bar
Paid-ads bar≥ $1.78 net per install, IAP only — measured on the acquired PRIMO10 cohort (643 installs, 46 payments), which converts at 7.15% vs 1.6–1.8% blended. Acquired users convert ~4× better because they clicked an astrology ad on purpose. Before price work-through, renewals or ad revenue. Run scripts/_ads-roi.js, which recomputes it from the ledger every run
Store payout79.8%, not 70%
Payer value is risingavg net $17.34 → $25.10 (+44.7%) as the $49.99 and quarterly SKUs displace legacy ones
Age paysA 65+ user is worth 8.8× an 18–24 user. Ages 45+ are 18.9% of accounts and 32.2% of revenue
Tarot6.26M journal entries, 4.54M carry a question · ~638k completed readings/yr at 67% completion · 8.5% of free-text questions are romance
Discoverabilityreports 7.8% reach (no route at all) · harmonizer 2.8% (1 link) · store 0.2% (1 link)
Relationships155,361 accounts (34.6%) hold another person’s chart · 72.2% of multi-chart accounts never opened the relationship screen · 93.2% of share-link arrivals bounce under 10s
Website~98K monthly users · CTA click rate ~3.2%

Retracted do not quote these again

WrongActual
15.02% ever paid · 448,892 accounts5.37% · 460,694
Solo 35,430 @ 4.69%300,710 @ 1.58%
“Tarot converts worst”False — an aggregation artifact. Tarot converts better at every collection size
Practitioner segment “63% converted, an ARPU play”~11,500 unconverted — a conversion play
Synastry ~23% of chart views6.5%
“316 documents carry user text”4,544,139
Website CTA rate 0.21%~3.2%
“$585K/yr” revenue~$15.5K/mo new + renewals ≈ $20K/mo
GA4’s “50% crash in H2 2024”Measurement artifact — use Play Console device counts
Chani’s “42% retention / 3.1× premium”Fabricated. Never cite
“There is no Meta pixel on the site”It exists — it was firing into an orphaned pixel
Any chart-count threshold above 5Mechanical, not predictive — the free limit is 5 charts

The standing rule behind most of these: a sample covering 16.6% of accounts was quoted as fleet levels. It inflates conversion ~2.8× and undercounts volume 14–44×. Relative lifts in those docs still hold — only levels moved.

05

The one thing blocking everything

Per-user event measurement does not exist — ISSUE-77, ISSUE-142.

Metrics go to Prometheus, an aggregate time-series store. A uid label across 460,694 accounts is a cardinality explosion, not a one-liner. This was mis-priced as trivial and it is design work — a uid-keyed event collection that does not exist yet.

What it blocks: grading the Q4 bet · every gating, capping and pricing question · per-session funnels · experiment assignment.

shipped 09-11 The cheap half is in

app_version and platform now tag every metric at the single choke point every metric passes through. So “did release X improve anything?” is now answerable.

“Did this session go A → B?” is not, and cannot be without an event store. That is the honest boundary.

06

Live state — verified today

The fire docs’ deploy-state claims lag reality in both directions. These three were checked against git directly on 2026-09-11.

already pushed ISSUE-85 — a security fix

The fire doc ranks this “TOP PRIORITY, committed but not pushed.” It is in origin/master (e90aa02). Pushed still isn’t deployed on the Node box — but the doc’s framing is stale and shouldn’t drive the priority order.

decision window closing The AI-language fix is also already pushed

12b9965 is in origin/master, not pending. FEATURE-LIST §C says to decide the language gate “before this is pushed” — that window has closed or is closing. Confirm whether it is deployed, then treat Horizon 0.5 as a live decision rather than a future one.

needs an answer The legacy .NET fixes were reverted, not shipped

In the legacy astromatrix repo, local master is 8 ahead / 2 behind, and four of those commits are reverts. The ISSUE-106 GDPR tarot-deletion fix, the ISSUE-107 fix and the ISSUE-82 Sabian fix were each committed and then reverted.

Verified: RemoveAllData in Website/API/UserController.cs does not touch TarotReadingJournals today. The docs describe these as “fixed, committed, not pushed.” They are not in the working tree and not pushed. Whether the reverts were deliberate needs an answer before anything is re-applied — and the legacy database sunset makes it a dated question.

Uncommitted right now

RepoFilesWhat it is
astromatrix-publicapp/home/terms/
Footer.tsx
style.css
ISSUE-144 — the Terms URL printed in every store description 404s. Page restored, Footer link added. Blocks an ASO submission (Apple 3.1.2). The restored text has no auto-renewal clause — a lawyer’s call, not an audit’s
astromatrix-flutter new-uiproducts_config.json
gated_content_widget.dart
ISSUE-147 / Horizon 0.1 — the false paywall bullets, removed
astromatrix-nodejsdoc edits
+ 4 unpushed commits
ISSUE-143/144 fixes and a navigation-architecture UX audit
  • Branch note: the shipping Flutter branch is new-ui, which is 116 commits ahead of master. Don’t reason about Flutter state from master.
  • Website deploys work again. Push to astromatrix-public master → Gitea Actions → live. Run npm run build locally first and check git log origin/master..HEAD, because the server takes whatever is on origin/master. The “no auto-deploy” note in older docs is stale.
  • Concurrent sessions are editing these docs. docs/README.md changed on disk mid-session. Re-read before editing anything under astromatrix-nodejs/docs/.
07

The doc map

DocOwnsTrust
FINDINGS-2026-09-11.mdVerified fleet numbers and the retraction listread first
product/FEATURE-LIST.mdThe forward list — what to build nextworking list
product/README.mdIndex for the product-design lanecurrent
docs/README.mdIndex for the support / fire / migration lanecurrent
q4-growth-plan-2026-09.mdThe committed quarterly betcurrent
competitive-position-2026-09.mdMarket position, Horizon 0/1/2, the AI-era answer, Memory as the fourth pillarnewest strategy
strategy-reference-2026-09.mdRanked strategy map, visibility map, turnaround research, what NOT to dolevels predate 09-07
built-but-disconnected-2026-09.mdFour assets built and unshipped: sky events, broadcast push, Big Three rarity, astrocartographycode-verified
acquisition-channels-2026-09.mdCreator seeding, PR, store editorial, the paid-UA mathno first-party data
STRATEGY-brief-2026-09-07.mdProduct-consequence view of the active firescurrent
SUPPORT-fire-manager-reportMaster bug tracker — all ~147 numbered ISSUEsauthoritative
SUPPORT-fire-FLUTTER / NODE / PROCESSDerived per-area dev specsderived; FLUTTER self-flags stale
SUPPORT-ux-experience-issues.mdNon-technical UX complaints, counted by headcountcurrent
product/five-user-modes.mdMode briefing pack, code-verifiedlevels superseded
product/app-map-and-changes.mdVerified app map — check here before claiming a screen is unreachablecurrent
user-knowledge-audit-2026-09.mdWhat the product knows about a user; the proposed knowledge modelnew, untracked in git
INFRA-resilience-roadmap.mdDR / failover phases, open security itemscurrent
MIGRATION-*.mdLegacy .NET → Node port3–6 month clock
SESSION-HANDOFF-2026-09-11.mdAds, revenue economics, the website funnelread before the test plan
paid-ads-test-plan-2026-09-09.mdAds working doc4 generations of economics, 3 wrong
08

Traps

Things that keep getting re-derived wrongly. Each one has cost a session already.

  1. Don’t propose a feature without checking whether it already exists. Event-triggered push, UGC/community, rewarded ads, the Guide deep-link and the in-chart South Node were each proposed as new work and each already shipped. Two of two “Bet”-tier items in one doc already existed.
  2. Usage of an undiscoverable state is not a measure of demand. /chartupcoming at 0.2% and Progressed at 1.3% are evidence of no route, not no demand.
  3. Never convert a destination into a state without leaving a signpost for one release. This has produced four confirmed “you deleted it” reports for features that still ship. ~25 of ~49 UI complaints are “I can’t find things”; one named complexity.
  4. “Too complex” and “too sparse” are both right. Six users want more on screen; one articulate user wants less. Solve it with a density mode — never by picking a side.
  5. Never quote a truncated list as complete. Print a count, reconcile it, state N of N. Paginated Meta reads return partial data silently under rate limiting.
  6. Verify writes by re-reading state — not by an exit code or a chained echo.
  7. profiles = accounts (531,848). users = charts/people (1.24M). Not interchangeable, and they have been mixed up in published figures.
  8. Pausing a Meta campaign does not pause its ads. Audit status, never effective_status.
  9. Committed ≠ pushed ≠ deployed ≠ released. For Flutter, add ≠ in a build a real user has.
09

So: what now?

The shortest defensible answer, in order.

  1. Horizon 0.4 — link the store, give reports a route. Small, verified, and everything downstream is unmeasurable without it.
  2. Decide the lifetime SKU (0.2) and the language gate (0.5). Both are decisions rather than builds, and both are load-bearing.
  3. Scope the event store properly. It gates grading the quarter, and it is not the one-liner it was priced as.
  4. Ship Horizon 1.1 — relationship transits. It is the prerequisite half of the committed bet that the Q4 plan assumed already existed.
  5. Leave the ads campaign alone until day 3 gives an honest CPI against the $0.85 bar.
  6. Resolve the legacy .NET reverts before the database sunset makes it urgent.