Store Copy Pre-Flight

Audit · 11 September 2026 · AstroMatrix

Store Copy Pre-Flight

Every claim in the ASO package checked against the shipped Flutter client, the Go ephemeris engine, the live paywall and the live web. The copy craft is sound and all 32 character counts are exact — what stops it are three disclosure problems and a second store-copy package written the same day that says something different. Two items have since been fixed in code, and one of them reversed a finding: the six-languages claim was never the overclaim.

3Blockers open
2Fixed in code
1Conflict
2Factual errors
32Counts exact

Blockers fix before anything is submitted

Fixed · ISSUE-144 §5, §7–§11 · every full description

The Terms of Use link at the foot of all six descriptions is a 404

curl -I http://astromatrix.org/home/terms → 301 → https://… curl -L https://astromatrix.org/home/terms → 404 curl -L https://astromatrix.org/terms → 404 curl -L https://astromatrix.org/Home/Terms → 404 curl -L https://astromatrix.org/privacy → 200 curl -L https://astromatrix.org/home/privacy→ 200

The site is up and the privacy page is fine — only Terms is gone, in every casing and extension I tried. Apple expects a working Terms / EULA link in the metadata of an auto-renewable subscription app (3.1.2), and a reviewer clicking a dead legal line is an avoidable rejection. Lifetime buyers following it today get the 404 too.

Cause: a page lost in the Next.js rewrite. astromatrix.org is served by astromatrix-public, which has app/home/privacy and no app/home/terms — exactly the difference between the 200 and the 404. The page did exist: the legacy .NET site served 325 lines of Terms of Service from Views/Home/Terms.cshtml. It was never carried over.

Fixed app/home/terms/page.tsx restores the legacy text verbatim, mirroring the privacy page; Footer.tsx gains the Terms link it never had. Dev server returns 200 with the full text, tsc and eslint clean, left uncommitted.

Still yours The restored text is dated — Facebook/Twitter sign-in, "Astromatrix, Inc.", California venue, numbering that skips sections 4 and 9, and no auto-renewal clause at all. Restoring what was published is an ops fix; drafting new subscription terms is a lawyer's job, so nothing was rewritten.

Blocker 2 §3 promotional text

PDF export is sold as though it were an app feature

The promo text reads "Now with downloadable PDF reports and complete interpretations in six languages", with no qualifier on either half. PDF export is genuinely Premium-only — it is the one true capability among the app's gates, and three of the ten upgrade prompts in the app are PDF prompts titled "Unlock PDF Reports".

The other package got this right: "read in six languages … unlock every chart type with a single lifetime purchase." This one dropped the qualifier.

Fix Re-attach the purchase to the PDF half. The languages half needs no qualifier — see below.

Reversed · ISSUE-147 §5 "Full interpretations in six languages"

The six-languages claim was never the overclaim — the paywall was

This started as the other half of Blocker 2: both the upgrade dialog and every paid plan sell reading in your language as Premium, so the unqualified store claim looked like an overclaim. Tracing the gate instead of reading the copy reversed it. Language is not gated anywhere:

settings_sections.dart:400 picker, all 7 languages, no entitlement check (the only conditional is a loading spinner) content_gating_service.dart gates saved-count and view-count. Never a language entitlement_resolver.dart no language reference at all routes/component.js (Node) translates on demand, English fallback, for everyone — no payment coupling all 10 showPremiumUpgradeDialog profile limit · saved-report limit · PDF call sites none is a language

Chart types are not gated either — content_gating_service.dart never references ChartType. So both "Reports in your language" and "Every chart type — synastry, composite…" are benefits the free tier already has, which is precisely what rule 4 of the positioning doc forbids: "A benefit the free tier also has teaches users the paywall is noise." That is the same rule that deleted "Created by real astrologers" that morning, from the same file.

Fixed Both bullets removed — from the upgrade dialog and from all four paid plans (products_config.json → 1.2.0), leaving PDF export, unlimited saves and no-ads. flutter analyze shows only the three pre-existing info lints; the product-config tests pass 10/10.

Yours The paid plans now show three bullets instead of five. Whether a fourth gets added is a copy call the positioning doc owns — nothing was invented to fill the gap.

Caveat · ISSUE-146 §5 "not translated menus around English-only interpretations"

One major surface really is English-only — for paying users too

Filed concurrently by another session while this audit was running: /ai/generateDataReport — the Matrix AI report, the app's main AI surface — has no language plumbing on either side, so it comes back in English regardless of the user's setting. A paying Spanish subscriber has been asking about it for roughly three weeks.

So the listing's line is true of the interpretation corpus and not true of that one surface. It is a content bug to fix rather than a qualifier to add to the copy, but it should be fixed before the line is submitted in six languages.

Blocker 3 §5 "More astrology in one place"

Premium is described as PDF plus ad-removal; free limits are never stated

The description's only account of the paywall is "Premium adds downloadable PDF reports and an ad-free experience." The real gates, verified in code:

5 saved people user_service.dart:3186 10 saved charts content_gating_service.dart:98 freeTierSaveLimit 10 saved reports same cap, counted separately reading gate every 10 → 7 → 5 views content_gating_config.dart:4-11 advanced city search lifetime only

The other package states the free tier outright and deliberately reframed Premium away from ad-removal, recording that a wrong free-tier limit on a store page is "a refund and review risk." This package reverts to the framing that was just fixed. The English description is 2,978 characters — 3,440 with Apple's terms block — so there are roughly 560 characters spare to say it properly.

Fix Add the free-tier line and the full Premium list. The room is already there.

Blocker 4 §3 "Apple subscription terms to append"

The Apple subscription block omits every plan's length and price

It covers renewal mechanics, charging and cancellation, but never names what is being sold. Apple's 3.1.2 metadata requirement includes the length and the price of each subscription. The live SKUs are quarterly_subscription, yearly_subscription2025 and full_purchase2025; the other package carries all three with [PRICE] placeholders and a note to pull real values from App Store Connect rather than guess.

Fix Add durations and prices. Pull them from App Store Connect — the figures are not in either repo.

The conflict two packages, same day, different answers

Decide first astromatrix-nodejs/docs/product/store-listing-copy.md

A second store-copy package was written the same day and nothing says which wins

It carries its own verification list, and is governed by the copy rules in positioning-and-paywall-copy.md §4. The two agree on both app names and on the positioning line — and disagree on every field that is actually being changed:

FieldThis packageThe other package
Apple subtitle Birth Chart & Compatibility (27) Your full chart, not your sign (30)
Apple keywords mainstream-led — astrology, zodiac, daily, love, rising, moon practitioner-led — synastry, composite, progressed, draconic, relocation, vedic
Play short desc. Birth charts, daily horoscopes, synastry, composite charts, transits and tarot. Your full birth chart: synastry, composite, transits, tarot. Not just your sign.
Promo text PDF + six languages, unqualified PDF + six languages, tied to the lifetime purchase

These are genuinely different bets — broad-demand acquisition against depth-led differentiation — and §15's test plan only measures one challenger against the live control, so running it as written never compares them to each other.

Fix Pick the winner per field, or run the two short descriptions against each other rather than against the incumbent.

Factual errors wrong about the app, repeated across locales

Wrong §5 and all five localized descriptions

The relationship facets are named wrongly, and the locales don't agree with each other

The copy promises "chemistry, support, connection, growth and challenges" — five. The app has four:

lib/models/matrix_models.dart:500 categories: ['Chemistry', 'Support', 'Connection', 'Evolution']

"Growth & Transformation" is a sub-facet under Evolution; "Challenges" is an aspect trait, not a facet. English, Spanish, Italian, Portuguese and Turkish all say growth + challenges; French alone says évolution — so the one locale that names the real facet is the odd one out.

Fix "chemistry, support, connection and evolution" in all six, or drop the enumeration.

Unshowable §5 · §14 screenshot 5 · §12 CSL 3 screenshot 2

"29 planets, asteroids and chart points" is true of the engine, not of any screen

astromatrix-engine/types.go Planets[] — exactly 29 entries … includes Earth and Pholus lib/core/chart_constraints.dart "Earth is never shown" → hard-coded false "Pholus is always hidden" → hard-coded false ViewConstraints.defaults: Vesta, Eris, Eros, Psyche, Vertex, IC, DC all off by default

So the visible ceiling is 27, and a fresh install shows about 20. The number is defensible as a content claim and the other package uses it too — but a screenshot captioned 29 PLANETS, ASTEROIDS & CHART POINTS cannot be produced honestly: you would enable seven toggles and still be two short.

Fix Keep the claim in prose; re-caption the screenshot to what the frame actually shows.

Sequencing risk true today, contradicted in the field

Time it §15 test sequence · ISSUE-2

"Ad-free" is the promise most likely to be broken while ISSUE-2 is open

Entitlement demotion is still HIGH and unvalidated — the client fix is committed and merged, the bypass-health metric has not moved, and the fire report attributes about 42 of 43 "ads" complaints to this bug rather than to ads. Paying users get silently demoted and start seeing ads.

§15's primary metric (first-time acquirers per visitor) is install-side and unaffected. Its guardrails — rating trend and retained installer rate — are not: they are measuring the entitlement bug. Sending more traffic to a listing whose paid promise is "no ads, ever" while it is live amplifies the existing review pattern.

Fix Run the Play experiments — they're safe. Gate the three keyword-targeted listings on a post-11.0.0 read of _bypass_health.js rate 30.

Worth knowing judgement calls, not errors

Sourcing§1 message order

Three of the six evidence figures have no source I could find

Verified in the internal analytics docs: synastry at 23% of chart views, the add-partner flow predicting a relationship reading at 59.7% against 18.1%, and transits as ~40% of interpretation reading. I could not source "birth charts are 29% of chart views", "transit charts are 21% of chart views" or "relationship interpretations are about 17% of reading". The §1 list also mixes chart-view share with reading share without naming either denominator — which five-user-modes.md calls out by name as the comparison to stop making.

Ordering§5 · §14 screenshots

Tarot is last in the description and absent from all six default screenshots

The competitive work calls it the best-retained feature in the app — 17 visits per account, 15.4% one-visit, the best figure anywhere in the product. It earns a frame more than the sixth slot's generic "Advanced astrology in one app" does.

Mismatch§5 six languages

Six languages is the right call, but the app offers seven

Mongolian carries 9,160 content rows against 18k–125k for the six, and the other package excludes it deliberately for that reason — correct. Worth knowing the picker still offers it (settings_sections.dart:412), so a Mongolian speaker can select a language the listing doesn't claim and the corpus doesn't really cover.

Executable§12 deep-link destinations

The deep-link patterns the CSL section asks for do exist

custom scheme astromatrix.app://facets?Facet=Purpose universal link https://astromatrix.app/facets?Facet=Purpose deep_link_service_mobile.dart:342

Note the host is astromatrix.app, while the listing's support and terms lines use astromatrix.org — different hosts, both real. Confirm Play Console actually exposes a deep-link field on a keyword-targeted custom listing before the plan leans on it.

Verified correct checked against code, engine or live URL

Eight house systems, exactly as listed astrology_constants.dart:141
Sidereal and Vedic options chart_constraints
All nine chart types ship chart_payload.dart:24
Draconic is calculated from the True Node
Solar and lunar returns for any year
Five self facets, named exactly right matrix_models.dart:467
Foundation / Growth / Soul labels match the UI matrix_service.dart:902
Void of course, planetary hours, out of bounds calendars.dart:52
Retrograde calendar and Sabian symbols
Three-card and multi-card tarot spreads tarot_service.dart:372
Astrocartography correctly omitted flag is false
Android ID matches the Play URL cited io.cordova.astromatrix
Both store listing URLs resolve 200

Character counts all 32 exact, all within limits

Every count the package states is correct to the character, including the accented and Turkish fields. Three sit exactly on the limit — Apple keywords at 100, and the Play and CSL 3 short descriptions at 80 — so any later edit to those has to be a like-for-like swap.

Apple name22/30
Apple subtitle27/30
Apple keywords100/100
Apple promo text147/170
Play title28/30
Play short desc.79/80
CSL 1 name25/30
CSL 1 short77/80
CSL 2 name27/30
CSL 2 short79/80
CSL 3 name28/30
CSL 3 short80/80
ES name22/30
ES subtitle29/30
ES keywords96/100
ES short78/80
FR name21/30
FR subtitle28/30
FR keywords99/100
FR short75/80
IT name20/30
IT subtitle27/30
IT keywords97/100
IT short70/80
PT name21/30
PT subtitle29/30
PT keywords99/100
PT short75/80
TR name26/30
TR subtitle27/30
TR keywords94/100
TR short73/80

What changed code edits, all uncommitted

RepoFileChange
astromatrix-public app/home/terms/page.tsx New. Legacy Terms of Service restored verbatim, privacy-page structure
astromatrix-public app/components/Footer.tsx Adds the Terms link the footer never had
astromatrix-flutter gated_content_widget.dart Drops “Reports in your language”; comment records every gate checked
astromatrix-flutter products_config.json Four paid plans lose both false bullets; version → 1.2.0
astromatrix-nodejs SUPPORT-fire-*.md ISSUE-144 and ISSUE-147 filed, indexed and mirrored

Nothing was committed or pushed. ISSUE-144 (Terms URL) and ISSUE-147 (paywall bullets) are filed in SUPPORT-fire-manager-report-2026-08-26.md and mirrored into SUPPORT-fire-FLUTTER.md with ranked actions. My issue was renumbered 145 → 147 mid-session because another session claimed 145 and 146 while this was being written.

Everything above was checked against astromatrix-flutter at 9116d402 on new-ui, astromatrix-engine/types.go, astromatrix-public on Next 16, the product docs in astromatrix-nodejs, and live HTTP requests to astromatrix.org. Prices were not verified — they are in App Store Connect and nowhere in the repos.