AstroMatrix Quick Wins
Dev task list/2026-09-11/quick items only

Quick wins

Everything here is plausibly under half a day. Anything bigger was deliberately left out — the last section says what, and why.

✓ verified in code or live on 2026-09-11 § from the docs, not re-verified
A

The price change

Lifetime $49.99 → $79.99, discounted lifetime $34.95 → $55.99. No new products and no app release — ✓ products_config.json carries no prices, ✓ no hardcoded prices in any .dart/.ts/.tsx, and ✓ the 30% discount works by swapping to a separate product id, not a coupon.

#WhereChange
A0scripts/_payments.jsDo this first. usdPriceByType() medians price across all history per SKU. After the change full_purchase2025 holds both $49.99 and $79.99 and the median drifts between them for months — every per-SKU revenue figure stays plausible and becomes wrong. Bucket by (SKU, month).
A1Google Play Consolefull_purchase2025 → $79.99 · discount_full_purchase2025 → $55.99. Set regional pricing deliberately, not auto-convert. ✓ Not doable by API — inappproducts 403s with “migrate to the new publishing API” and no v3 replacement resolves. Console UI only.
A2App Store ConnectSame two. ✓ No API path at all — no .p8 key exists.
A3Stripe — lastSame two. ✓ Live state confirmed: $49.99 / $34.95. A Stripe Price amount cannot be edited — create a new Price on the existing Product (so metadata.shortcode carries over), create a new Payment Link, then deactivate the old link.
two traps

Deactivate the old Stripe links. /payments/get (routes/payments.js:1433) lists every active payment link with no dedupe by shortcode — leave the old one up and the app shows two lifetime products at two prices.

Stripe goes last. Doing it before the stores leaves web at $79.99 while both apps still charge $49.99.

✓ Safe: nothing hardcodes a buy.stripe.com URL anywhere — links are fetched live, so replacing them is transparent.

for Mark, not the dev What is the discounted price really?

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

While in the consoles: grab the three real prices. The corrected store copy is blocked on exactly those, and the iOS listing is wrong today — it advertises a 6-month plan that does not exist, and “10 profiles free” when the real limit is 5.

B

Ship what is already fixed

Zero new code. ✓ Verified against git on 2026-09-11.

#RepoAction
B0all — do this firstISSUE-149 — 24 finished Flutter commits have been sitting unreleased since 2026-09-09. Verified three ways: git log 8583ffaf..HEAD = 24 commits, pubspec.yaml still reads 11.0.0+300100090 with no bump since the 09-08 cut, and zero store release emails in 4 days. All pushed to origin/new-ui — nothing is lost, purely a cadence gap. The fixes for ~13 tracked issues are stranded, including ISSUE-129 (Android purchases hitting the payment call with an empty userId — 261 Google Play orders in 30 days landing unattributed). Cut a release. Single highest-value item on this page.
B1astromatrix-publicCommit + push app/home/terms/ and Footer.tsx. The Terms URL printed in every store description 404s, which blocks the ASO submission under Apple guideline 3.1.2. Push auto-deploys — run npm run build locally first and check git log origin/master..HEAD.
B2astromatrix-flutter
(new-ui)
Commit products_config.json + gated_content_widget.dart — the paywall sells two things the free tier already has. Note the shipping branch is new-ui, 116 commits ahead of master.
B3astromatrix-nodejs2 unpushed commits on master.
B4infra, not codeSet the autosupport key in production config and update the scheduled job in the same change, or live autosupport breaks.
before you start Check git, not the docs

Before working any “committed but not deployed” item from the fire docs, check git first. Two such claims were already in origin/master despite the docs saying otherwise.

C

Quick code fixes

now tracked Filed in the fire report

ISSUE-150 (C5), ISSUE-151 (C7) and ISSUE-152 (the pricing work in §A) are now numbered issues in the master fire report and reflected into SUPPORT-fire-FLUTTER.md / SUPPORT-fire-NODE.md. The rest were already tracked — C1 and C3 in SUPPORT-ux-experience-issues.md, C4 as ISSUE-132, C8/C9 in that same doc §8.

⚠ Removed since the first draft: the /matrix-report dead route. It was fixed 2026-09-11 (9b7dd49e) and the app-wide sweep came back clean — zero unregistered navigation targets remain. Do not re-file it.

#WhereItem
C1✓ home_buttons
.dart:149-161
The home button says the wrong word. Home says “Weekly”; reporters search for “transits”. ⚠ The obvious fix — carrying the "Weekly (Transits)" label onto the button — is NOT buildable: home labels are maxLines: 1 + TextOverflow.ellipsis at fontSizeSM, so it renders as “Weekly (Tra…”, and every label runs through getLang() so German and Turkish truncate sooner. Existing labels sit at 6–13 characters — that is the budget. The real fix: merge Weekly + Monthly into ONE “Transits” button (8 chars), opening the Matrix with the Weekly/Monthly/All chips from C3 visible on arrival. Fewer words, the exact word three reporters used, one freed grid slot. Ship with C3.
C3✓ matrix_service
.dart:898
Surface the Matrix lens names as on-screen chips instead of dropdown contents — nobody opens a dropdown to discover a feature. All 7 labels already exist in getCategoryMenuOptions(): All Aspects · Foundation (Birth) · All Transits · Weekly (Transits) · Monthly (Transits) · Growth (Progressed) · Soul (Draconic). Presentation only — no new strings, no astrology.
C4§ ISSUE-132Relationship Matrix silently auto-pairs a second profile and persists it app-wide. Two bail-out guards are written && mounted, so an unmounted widget skips the return as well as the pop. Small and self-contained; not verified on a device.
C5§ notification_
router.dart
ISSUE-150 — notification deep-links dead-end on Home. The router is wrapped in a try/catch that only debugPrints, so any throw silently leaves the user where they were — on a cold launch, the home screen. Fall back to a real destination and record the failure. Reported by a lifetime customer; this is an engagement channel already being paid for.
C6✓ routes/journals
.js:520
The post-reading tarot comment is discarded — writes comment: '', ignoring the request body. One line — but it is deliberate legacy parity, not a bug, so confirm intent first. (The docs cite :451, which is the wrong line.)
C7§ chart_screen.dartISSUE-151 (UNVERIFIED — check the widget first) — the synastry wheel sits 6th in the chart dropdown, behind Progressed at 3rd — a practitioner chart outranking the one chart a normal person came for. Reorder. Not verified.
C8§ astro_helper
.dart:1375
South Node not visible on some surface. It already exists end to end — engine body index 27, glyph present, already a row in the transit aspect table. Scope is only “find which surface drops it”. Narrower than originally filed.
C9§ chart_constraints
.dart
Add a showOuterPlanets flag for Uranus/Neptune/Pluto. 13 per-body visibility flags already exist; classical and outer planets currently share one blunt showPlanets. Leave nodes and angles on the existing flag — traditional practice uses the nodes, so a naive “classical vs post-Saturn” split hides exactly what the requester wants kept.
✗

Deliberately excluded — not quick

ItemWhy not
Event storeDesign work, not a one-liner. Prometheus is aggregate-only; a uid label across 460,694 accounts is a cardinality explosion. A design already exists in user-knowledge-audit-2026-09.md §I3.
Relationship transitsAn engine change, despite the docs saying otherwise. CalcChart() has two chart slots and applyComposite() consumes both. Needs a decision on which of three techniques first.
Link the store / give reports a routeThe highest-value item on the roadmap, but real work — not a quick win.
Raising the annual subscription priceSubscriptions carry Apple/Google consent and notification requirements for existing subscribers. Lifetime is a non-consumable and has none. Separate job.
Anything needing App Store Connect automation✓ No .p8 key exists. Provisioning one (Admin role) unblocks four things at once: Apple price automation, the 62% of revenue never reconciled against Apple’s ledger, iOS review monitoring, and iOS install data.