TMA Analytics — Master Implementation Brief

Everything still to be done for TMA's analytics to work end-to-end: ad → lead → paid → retention — the gate metric is Lead→Paid (Lead → Paid) across both checkout paths: carded trial-start OR direct discounted purchase (corrected 16 Jul — see the green box). Every task says exactly what to do, where, and whether it needs an app-store release. Click any task to open its steps.

This is the "build & wire" list — what Nic builds to SEE and ENABLE the sale. Its companion is the "make & save the sale" list (retention saves · email sends · warm demand · SEO/YouTube): §⑳ of the $15K doc + the growth cockpit ↗. The seam is checkout + measurement (Tasks 1/5/9/8/2/19) — that's where the two lists meet.
Owners (company codes, ONE per task):NIC = paid + wiring + web + backend · AGA = email + WordPress + AC + Intercom + approvals · V = support + social/community + ops + contractor dispatch · RN = anything needing an App/Play-store release · WPDEV = external WordPress dev · FLEET = agents (verification & dashboards: Claude) · auto-synced 12 Aug 2026
15 of the 21 open tasks need no app release. Six need a store release — Tasks 17, 18, 22, 23, 24, 25 (the RN team’s — full detail in their brief: RN Team Brief ↗). The new email deep-link add-on is split: its app-code half rides Task 24 / RN‑6 (release), its OneLink + ESP setup is Task 26 (Nic, no release). Task 5’s release question was checked on 10 Jul: none needed. Start with the priority table below.
Version history — v3.3 back to v2.5
Version 3.3 · 29 Jul 2026 (checkouts-v3 SHIPPED & live-verified — 24 pages up with attribution + GA4 + Stripe Elements; Task 19 closed for the new estate; TWO checkout estates now exist — the quiz paywall is NOT on v3; new Tasks 29/30/31) · v3.2 · 24 Jul 2026 (live-verified pending items — T15 API done; T8/T9 confirmed NOT done: wrong signal / dedup failing; nginx moved to its own brief) · v3.1 (master to-do reorganised: OPEN-by-priority + DONE banked/struck; added nginx UTM fix + gclid→Stripe + trial→paid cohort; paid-scale gate) · v3.0 (live-verified status box: RC→Amplitude connected-but-leaking 3-vs-128 · GTM flag task · social tasks split out) · v2.9 · 21 Jul 2026 (Nic shipped Google Ads Lead + Purchase GTM conversions + a live server-side Stripe analytics webhook — see the ✅ 21 Jul box) · v2.8 · 20 Jul 2026 (Lead→Paid meter now front-and-centre — Nic flagged it was buried; Task 8 status: purchase registers in Ads, next = dedupe + conversion value/ROAS; Task 15 Ads-API access GRANTED) · v2.7 · 14 Jul 2026 (adds Task 27 — email UTM→revenue capture at checkout, Nic/no-release — and the deep-link route map, linked in §7. v2.6: the email deep-linking add-on — AppsFlyer OneLink, folded into RN‑6; and brings the release set into line with RN Team Brief v2.3 by adding RN‑5/6/7 as Tasks 23/24/25) · v2.5: Task 22 RC Paywalls early standalone + Task 21 dunning · supersedes the 6 Jul dev brief (folded in)
This tab is the work. Everything here is open. Anything already shipped has moved to ✅ Done & updates and is struck through there — so if it is not on this tab, you do not need to do it. Click any task to open its step-by-step.
🔒 Gate (Aga, 6 Aug 2026): nothing enters this brief without Aga's approval AND full live verification. Every task currently here carries both.

At a glance — 16 open (10 Nic-side · 6 store-release) — 80/20 re-cut 6 Aug

Analytics and the launch blockers around it, in order. P0 = blocks revenue / a promise already made on a page / seeing a sale at all. P1 = points the money right. P2 = improves what works. REL = needs an App/Play-store release (external RN team). Click any # to jump to that task’s full steps.
Reorganised 24 Jul: open items first (by priority), done items banked & struck through at the bottom. The through-line: the funnel is still partly blind — web trial→paid, gclid→Stripe and BWA/source attribution are DARK, so no big bet (scale paid, pour into BWA, CRO the paywall) can yet be picked on evidence. The top open items light that up.
Attribution / UTM-redirect fixes (nginx Fix 1, quiz-root 301→302, _gl preservation) are a SEPARATE brief → tma-nic-attribution-brief — not duplicated here.

⚡ THE 80/20 — re-cut 6 Aug 2026, every open row re-verified LIVE the same day (Meta Marketing API · Google Ads API v22 · Amplitude · AppsFlyer · AC API · Stripe API · the served quiz bundle · curl on the live pages)

If only four things get done, they are these — in this order. Together ~2.5h of console/build time, ranked by measured $-per-minute against the cash gap. Re-cut 7 Aug PM on live verification.

  1. №1 · R6 — kill Competitive Non-Branded | US (id 24012014523). Re-verified 7 Aug PM: still ENABLED, £206.74/30d, 0 conversions, spent every one of the last 14 days. All 66 keywords are equipment-PURCHASE queries — wrong intent class; keyword surgery cannot save it. ~$260/mo [MEASURED] back for ~5 min. console
  2. №2 · Task 4 — Meta. First UN-PAUSE the 3 OUTCOME_SALES retargeting campaigns (7 of the account’s 8 pixel purchases on 9% of spend — ~5 min), then REBUILD prospecting as OUTCOME_SALES optimised to InitiateCheckout — objectives are immutable, so it is a build, not a toggle (sheet on the card). CAPI gate closed: 201 server Purchase events/28d. console + build, ~90 min
  3. №3 · Task 8 — Google, in the SAFE order: kill the 4 armed double-count Purchase actions + demote add_to_cart/begin_checkout (zero delivery risk, ~15 min). 🔴 HOLD the lead-category demotion until purchase values arrive — 7 of 12 purchases carry £0 value (no tROAS; PMax would stall). The £123k fake value sits on a PAUSED campaign; the live poison is the LEAD category at 76% of bid signal. console
  4. №4 · Task 5 — AppsFlyer, now a 20-MIN AUDIT: the RC→AppsFlyer wiring already exists and fires (af_purchase 61/$4,037 per 30d — the old “21 vs 44” was an Amplitude proxy error, struck). Real residual: 2,384 paid customers → 0 conversions/180d = Task 17 (identity) or traffic quality. dashboard
  5. Task 30 — stamp checkout_version: → moved to Nic's attribution brief 7 Aug PM, folded by Aga into one sitting with NIC-91 (“the attribution stamps”). One home per task — the full re-verified evidence lives there.

The old rider is GONE from this board (7 Aug PM): Task R (RETURN_URL) verified FIXED live — all three v3 pages render https:// — and Task N (noindex hosts) moved to Nic’s attribution brief (NIC-43). One home per task.

Banked by the 6 Aug verify: Task 1 ✅ (8 rc_* events landing, rc_initial_purchase 44/30d — residual $amplitudeUserId folds into Task 17) · Task 34 ✅ (the pipeline's token already carries instagram_basic + instagram_manage_insights and pulled live IG data today — 43,441 followers read back) · the Claude Stripe cohort row ✅ — web trial→paid = 30.0% (24/80 real-email trials, 8 May–23 Jul cohort, 22 phantoms excluded) vs app 62%. The rest of the 80%: 11 is UNBLOCKED and TAKEN BY THE FLEET 7 Aug (Aga-approved; zero Nic) — Intercom turns out to hold the whole funnel state per contact with email; the census is running and segment D went to the Abandonment Fleet · 10 is CLOSED 7 Aug, not merely downgraded — UTM survival verified passing live, the source split already proven, and the one true residual (GA4 user_id) is Task 17’s work · 16 stays blocked on 5+8 (all UACs verified PAUSED) · 26 + the six REL rows are external. The step-by-step blocks below keep their old grouping — the order is this box and the table.

Priority#TaskOwnerWhereRelease?Status
⚡ THE 20% — DO THESE FOUR, IN THIS ORDER (this is where the money is)
⚡ №1R6 ↓Kill the junk campaign — Quiz Funnel | Competitive Non-Branded | US (id 24012014523) RE-VERIFIED LIVE 7 Aug PM via the Ads API: still ENABLED, £206.74/30d · 25 clicks · 0 conversions · spent every one of the last 14 days incl. £7.91 today. All 66 keywords are equipment-PURCHASE queries (bars, machines, gear) — wrong intent class, so the 59 never-served keywords cannot save it. ~$260/mo [MEASURED] for ~5 min. Detail below.NicGoogle Ads console (690-916-9207)🟢 no☐ open — ~5 min, best $/min on the board
⚡ №24 ↓Switch Meta off the Lead objective → Purchase RE-VERIFIED 7 Aug PM: still all OUTCOME_LEADS — and it is a REBUILD (objectives immutable). Cheapest revenue first: un-pause the 3 OUTCOME_SALES retargeting campaigns (7 of 8 purchases on 9%% of spend), then build the InitiateCheckout campaign — full sheet on the cardNicMeta Ads Manager🟢 no☐ open — 20 min
⚡ №38 ↓Google Ads — demote the proxy conversions, keep Purchase primary RE-VERIFIED 7 Aug PM: fake value sits on a PAUSED campaign — live poison is the LEAD category (76%% of bid signal) + 5 primary Purchase actions armed to double-count + 7/12 purchases £0 value (no tROAS). Safe order on the cardNicGoogle Ads console (690-916-9207)🟢 no☐ open — 30 min
⚡ №45 ↓T5 — AppsFlyer: 20-min config audit RE-VERIFIED 7 Aug PM — the wiring ALREADY EXISTS and fires (7 rc_* columns live; af_purchase 61/$4,037 per 30d); the “21 vs 44 under-count” was an Amplitude proxy error, struck. Real finding: 2,384 paid-media customers → 0 conversions/180d (= Task 17 identity or traffic quality). Just audit the mapping + “attributed users only” toggle — steps on the card.NicRC dashboard + AppsFlyer🟢 no☐ open — 20 min audit
🕰 THE 80% — waits, blocked, or external. None of these before the four above.
P216 ↓Rebuild UAC → optimise to the trial event all UACs verified PAUSED 6 AugClaude → NicAds console🟢 no☐ blocked on 5 + 8
P226 ↓Email deep-link infra — OneLink template + ESP wiringNicAppsFlyer + CIO/AC🟢 no☐ open — later
REL17 ↓T4 — canonical user identity (keystone)external RN teamApp code🔴 yes☐ open
REL18 ↓T7 — native product + lifecycle eventsexternal RN teamApp code🔴 yes☐ open
REL22 ↓RevenueCat Paywalls SDK — remote paywall & price controlexternal RN teamApp code🔴 yes☐ open
REL23 ↓RN‑5 — iOS ATE/ATT + AppsFlyer always-initexternal RN teamApp code🔴 yes☐ open
REL24 ↓RN‑6 — install-UTM stitch + ➕ email deep-link add-onexternal RN teamApp code🔴 yes☐ open
REL25 ↓RN‑7 — screen-level trackingexternal RN teamApp code🔴 yes☐ open
P2R23 ↓Work the /register page as CRO — BLOCKED on Aga’s decision 32 moved off the revenue plan 7 Aug — still open, full text belowNic/register/<plan>/ — Estate B🟢 no☐ open — BLOCKED · Aga decides first
P3 · LASTR16 ↓The landing screen — the biggest single loss (ranked LAST, deliberately) moved off the revenue plan 7 Aug — still open, full text belowNicQuiz landing screen🟢 no☐ open — re-ranked LAST

The tasks, in priority order

STEP-BY-STEP DETAIL

the ORDER lives in the ⚡ 80/20 box + the table above — blocks below keep their old grouping
5 T5 — Fix the AppsFlyer conversion events ★ highest leverage broken🟢 no releaseNic · 3–6h
✅ RE-VERIFIED 7 Aug PM — most of this card is REFUTED; the job shrinks from 3–6h to a 20-min audit. The RevenueCat→AppsFlyer integration is already connected and firing (7 rc_* event columns live; af_purchase 61/$4,037 per 30d). 🔴 The “21 vs 44” was an Amplitude proxy error — AppsFlyer’s own report says 61; reading AppsFlyer health out of Amplitude violates platform-own-truth. The real break: RevenueCat’s own attribution shows 2,384 paid-media customers → 0 conversions in 180d (FB 1,334, Google 838) while ASA converts 21% — either identity fragmentation (= Task 17) or traffic quality (= a budget call), and neither is fixed by wiring. Nic’s 20-min audit + the 5-min dashboard read + the new acceptance test: docs/company/ANALYTICS/NIC_EXECUTION_PACK_2026-08-07.md. Android paid 90d: 4,236 installs → 2 purchases vs organic 862 → 27 — confirmed and worse than the card said.
RevenueCat → Integrations → AppsFlyer (server-to-server) · or the app's AppsFlyer SDK
🎯 Why (verified): the ad platforms can only buy more of what they can SEE converting — and today purchases never land on a paid source. Verified 6 Aug (AppsFlyer partners_report, 30d): every paid Android row carries $0.00 revenue (Facebook Ads 199 installs, UAC 232 — all $0) while Organic soaks $4,978; and af_purchase under-counts, 21 vs 44 real rc_initial_purchase.
⚙️ What it enables: per-channel Cost-per-Trial and CAC — the number the $3,000/mo cap decision runs on — and true purchase postbacks for Meta and Google to optimise toward.
🔓 What it unblocks: Task 16 (UAC rebuild is gated on this), Task 4's optimisation event arriving reliably, the revenue plan's “no scale of paid before a real conversion event” hard stop, and the cockpit's DARK CPT tile.
This one task fixes Google, Facebook and Apple Search Ads measurement simultaneously. af_purchase currently fires ~2× against 194 real subscribers (superseded 6 Aug — see Where it stands: it now under-counts, and paid rows carry $0). Nothing about paid can be judged until purchases reconcile ±5% AND attribute to the correct media source.
✅ Checked for you (10 Jul, live via the RevenueCat API): $appsflyerId IS present. It appears on every recently-active customer profile (6/6 with July activity, each also carrying $idfa and af_install_time); it's missing only on dormant profiles last seen months ago — i.e. the current app build already writes it. So this is a dashboard toggle, not a release. No RN-team involvement needed.
  1. RevenueCat → Integrations → AppsFlyer → connect (dev key + app ids). The $appsflyerId attribute is already there, so purchases will post server-side with the correct media source.
  2. Enable the events: af_start_trial, af_purchase, af_subscribe — each carrying revenue + currency.
  3. One nuance: dormant users (last active before ~June) have no $appsflyerId. That's fine — purchases come from active users, so postbacks will attribute correctly going forward.
Done when: AppsFlyer's af_purchase count reconciles to RevenueCat purchases within ±5% over a 7-day window, and purchases appear against the correct media source (not just Organic).
1 Finish the RevenueCat → Amplitude connection — ✅ VERIFIED DONE 6 Aug ✅ done🟢 no releasewas: Nic · 5 min
Where it stands✅ VERIFIED DONE 6 Aug 2026 (Amplitude API, 30d): 8 rc_* events landing with canonical names and real volume — rc_initial_purchase 44, rc_cancellation 49, rc_renewal 18, rc_trial_started 9. The trial denominator is live. The only residual is $amplitudeUserId identity, which is Task 17's keystone anyway — it folds there, and this row is banked. (Was, 24 Jul: 7 rc_, rc_trial_started 3/30d.)
RevenueCat dashboard → project proj4a978c73 → Integrations → Amplitude
Live-checked daily (06:00 T2 auto-check)8 of the 15 lifecycle rc_* events are now in Amplitude with the exact canonical names (incl. rc_renewal and rc_trial_started, so the trial denominator is live). Remaining: enable the last few lifecycle toggles + reconcile ±5% vs RC — then Lead→Trial and Trial→Paid read end to end.
  1. Open the Amplitude integration you already configured.
  2. In the event list, enable every remaining lifecycle event: rc_trial_started, rc_trial_converted, rc_trial_canceled, rc_renewal, rc_uncancellation, rc_non_subscription_purchase, rc_subscription_paused, rc_expiration, rc_billing_issue, rc_product_change, rc_transfer, rc_web_purchase_redeemed, rc_experiment_enrollment.
  3. Keep the rc_ prefix and the exact names above (they're the locked taxonomy — don't use RevenueCat's verbose defaults like rc_renewal_event).
  4. Identity gap found 10 Jul: no customer profile carries $amplitudeUserId. RevenueCat's events will therefore attach to RC's own ids, not to the Amplitude user ids the rest of our product events use — so an rc_initial_purchase can't be joined to that same person's onboarding or workouts. Until Task 17 (T4) ships, set the RevenueCat subscriber attribute $amplitudeUserId so the two systems agree on who the user is.
  5. Once af_purchase is fixed (Task 5), accept Amplitude's merge suggestion for purchase + rc_initial_purchase — but leave af_purchase out of the merged view until then, or it poisons the metric.
Done when: rc_renewal appears in Amplitude within 24h and its volume reconciles to within ±5% of RevenueCat (194 subs, ~6 renewals/day). Claude verifies via the API and reports the counts.

P1 · detail blocks

order per the table above
8 GA4 → Google Ads conversion import ("Task 3" in the 16-Jul message) Lead + Purchase tags shipped 20–21 Jul — verify in UI🟢 no releaseNic · 30 min
✅ RE-VERIFIED 7 Aug PM (Ads API v22) — config confirmed, but the headline framing was WRONG, and the fix now has a safe order. The £123k add_to_cart value sits ENTIRELY on a PAUSED Demand-Gen campaign — it poisons nothing live. The live poison is the LEAD category: 76% of bid-counted conversions (68 of 89). 🔴 New: FIVE enabled+primary web Purchase actions (one live, four armed double-counters incl. a GA4 import) and 7 of 12 purchases carry £0 value — do NOT switch anything to tROAS until the value leg is fixed. Safe order: (1) kill the double-counters + demote add_to_cart/begin_checkout — zero delivery risk; (2) HOLD the SUBMIT_LEAD_FORM demotion until purchase values arrive, or PMax stalls on 6 £0 conversions. Exact click path + ready API mutates (specified, NOT run) + all resource ids: docs/company/ANALYTICS/NIC_EXECUTION_PACK_2026-08-07.md.
Where it stands🔎 RE-VERIFIED 6 Aug 2026 (Google Ads API v22, last 30d): the Purchase tag now WORKS — “Purchase (July 2026) (All Success Pages)” counted 8 purchases at £109 value + 1 Lifetime $297. But the bid diet is still poisoned: 100 Android installs (trailing from PAUSED UACs), 33 “Leads — July 2026 GTM”, 21 generate_lead — and add_to_cart is still a PRIMARY action carrying £123,634 of fake conversion value (562 all-conv), begin_checkout another £108,126. The remaining fix is a 30-min demotion: set add_to_cart / begin_checkout / the Lead actions to SECONDARY (observation), leave Purchase as the one primary. 30d spend context: £929.75 — Competitors-Branded £265, Non-Branded £220, Competitive Non-Branded £199 (24 clicks, 0 conv), PMax £147, Branded £27.
GA4 admin (property 177471782) + Google Ads console (account 690-916-9207)
🎯 Why (verified): Google bids toward its PRIMARY conversion actions. Verified 6 Aug (Ads API v22, 30d): the real Purchase tag WORKS — 9 purchases with value — but add_to_cart is still PRIMARY carrying £123,634 of fake conversion value and begin_checkout another £108k, so the £930/mo Google budget optimises toward people who click, not people who pay.
⚙️ What it enables: ROAS bidding on real money; per-campaign verdicts that mean something — e.g. Competitive Non-Branded's £199/30d → 0 conversions becomes a decidable kill instead of an artefact.
🔓 What it unblocks: Task 16; every keep/kill/scale decision on the Google account; the gclid→Stripe ROI read the plan requires before any cap raise.
✅ UPDATE (Nic, 20 Jul): Purchase conversions now REGISTER in Google Ads — via a separate, INDEPENDENT GTM-triggered conversion tag (NOT the GA4 import below), with dedupe to be fully controlled in GTM. ✅ UPDATE (Nic, 21 Jul): the Purchase tag now sends conversion VALUE + currency (ROAS) + a transaction_id for dedupe, normalised across BOTH the new 2026 checkout and the legacy checkout via Custom-JS; and LEADS now register too, via a separate independent GTM tag fired off generate_lead (verified live on the quiz). Full analysis + proof: docs/company/ANALYTICS/TRACKING-SYSTEM/. ✅ 22 Jul: workspace confirmed published (Nic). Remaining: after checkout testing (Task 19) completes, run one ads-side test to confirm the conversions appear in the Ads UI on a fresh payment. Consequence: Ads is not inheriting GA4's 1.52× double-fire — but the GTM route has its own checklist: ① the tag must send a transaction_id/Order ID (Ads dedupes repeat order-ids; without it a success-page refresh re-fires just like GA4) ② keep ONE primary conversion action — if the GA4 import is ever enabled too, set it to SECONDARY/observation or Ads double-counts at its own level ③ the tag must fire on BOTH Lead→Paid paths (trial-start success AND direct-purchase success, per the green box) ④ conversion VALUE → ROAS — dataLayer push of value + currency + transaction_id on the success pages (Nic: GTM work + success-page rewiring, likely bundled with the checkout-pages testing sprint and with the meter/Task 9 as one job). Original context: Google had never received a purchase signal. That's the whole reason its campaigns bought 1,230 installs and zero attributable subscribers — the algorithm optimised toward the only thing it could see: cheap installs.
  1. (Steps 1–4 = the original GA4-import route — SUPERSEDED by Nic's independent GTM tag, 20 Jul. Keep for reference; if ever enabled alongside the GTM tag, the imported action must be SECONDARY.) GA4 → Admin → Product links → Google Ads links → link account 690-916-9207.
  2. GA4 → Admin → Events → mark purchase as a key event.
  3. Google Ads → Goals → Conversions → Import → Google Analytics 4 → select purchase.
  4. Set it as the campaign's conversion goal. (Do this after Task 9 fixes the purchase event, or you'll import a broken signal.)
Done when: Google Ads shows purchase conversions with revenue/value (via the GTM tag), deduped on transaction_id, covering both paths, matching Stripe ±10% over the same window.
4 Switch Meta campaigns OFF the Lead objective → Purchase blocking all cold spend🟢 no releaseNic · 20 min
✅ RE-VERIFIED 7 Aug PM (Meta API) — still broken, but this card’s fix is not executable as written. All 3 active campaigns still OUTCOME_LEADS. 🔴 Meta objectives cannot be changed after creation — this is a ~60–90 min REBUILD, not a 20-min toggle. 🔴 The card’s suggested events are EMPTY: StartTrial=3, Subscribe=2, fb_mobile_purchase=17 per 28d — unusable; and cold Purchase needs ~£13.8k/mo to exit learning. Optimise the new OUTCOME_SALES campaign to InitiateCheckout (92/30d, fits the envelope at ~£1.2k/mo). The CAPI gate is now CLOSED: pixel 2128008637263218 received 294 Purchase events (201 SERVER) in 28d — the flip is unblocked (dedupe itself still [UNVERIFIED]). Cheap win first: the 3 PAUSED OUTCOME_SALES retargeting campaigns produced 7 of the account’s 8 purchases on 9% of spend — un-pause them. Full build sheet (budgets, adset ids, 4 creatives to duplicate, exclusions incl. the missing customer-exclusion) is ready: docs/company/ANALYTICS/NIC_EXECUTION_PACK_2026-08-07.md. Spend context: Meta is ~£752/mo run-rate now, not £2.7k.
Where it stands🔎 RE-VERIFIED LIVE-BROKEN 6 Aug 2026 (Meta Marketing API): all 3 active campaigns run OUTCOME_LEADSQLG | ADV+ ON | US | Variant 2, MOF - Lead Form, QLG | ADV+ OFF | MC | Control — and their ad sets optimise to LEAD / LEAD_GENERATION. Nothing has moved since this task was written. Gate: flip only after the CAPI purchase verify (one real purchase seen with matching event_id).
Meta Ads Manager (console only — no code)
🎯 Why (verified): Meta buys exactly what its objective names. Verified live 6 Aug: all 3 active campaigns run OUTCOME_LEADS, optimising to LEAD — an algorithm trained on 5,171 Leads vs 172 Purchases, which is why 2,878 installs produced one purchaser. Every pound of Meta spend (~£2.7k/mo) is still buying form-fillers today.
⚙️ What it enables: Meta's budget hunts BUYERS instead of form-fillers — the single biggest lever on paid traffic quality, at zero extra spend.
🔓 What it unblocks: the “No Meta spend — warm OR cold” hard stop; the plan's lever ① (paid working inside the cap, modelled +$3,720/mo at full effect); honest creative verdicts — today creative is blamed for what the objective caused.
The pixel has been trained on 5,171 Lead events against 172 Purchases. Meta did exactly what it was told: it found people who fill in forms. This is why 2,878 app installs from Meta produced one purchaser. No creative can fix a wrong optimisation event.
  1. Every active campaign → objective OUTCOME_SALES (not Leads, not App Installs).
  2. Ad set → optimisation event = Purchase — i.e. the fb_mobile_purchase app event mapped in Task 2 (only once Tasks 2 & 3 are verified — never optimise to an event that isn't arriving). Not Subscribe — see the note below.
  3. Exclude existing subscribers and active app users from all cold audiences.
  4. Meta app-install campaigns stay permanently off. They are not being restructured; they are retired.
⚠ Which event to optimise to (added 16 Jul — read before switching the objective): the RC→Meta mapping in Task 2 sends both Renewal and Trial Converted to Subscribe, so Meta's "Subscribe" now includes renewals (~6/day) and is not a clean new-subscriber count. Optimise the campaign toward fb_mobile_purchase (actual purchases) or StartTrialnever Subscribe — or Meta chases an inflated signal. True new-sub count = reconcile vs Stripe/RC (the capture-all-then-reconcile approach Task 3 locks in). The mapping itself is correct — it's the only one Meta offers; this is only about the objective / optimisation event.
Done when: no active campaign optimises to Lead or App Install, and each campaign's conversion event reads fb_mobile_purchase (Purchase) — never Subscribe.
11 Export each user's “last funnel step” to Customer.io / ActiveCampaign ★ best ROI on this page free revenue🟢 no releaseFLEET · shipped 7 Aug
Where it stands — ~$1–2.6K/mo free retargeting. ✅ SUPERSEDED — SHIPPED BY THE FLEET 7 Aug: AC fields 78/79 (%TMA_FUNNEL_STAGE% + _AT) now exist and stamp nightly from Intercom (com.tma.funnelstage 02:30) — the quiz-tag dependency this row cited was never the substrate, Intercom was, and no Nic time was needed. Stall pools live today: survey_unfinished 4,356 · no_workout 6,775 · no_trial ~1,800; segment D (checkout-abandon) carved out to /abandon-fleet by Aga. This row starts the day the stages stamp; do not double-assign it here.
Backend job → CIO/AC contact attribute (nightly is fine)
🎯 Why (verified): ~500 people/week finish onboarding and never start a workout, ~180/week see the paywall and walk — and we cannot email any of them by stage, because AC has NO funnel-stage attribute (verified 6 Aug: none of the 52 custom fields) and the five stage tags carry 0 contacts.
⚙️ What it enables: four addressable segments emailed at £0 — est. $1,000–2,600/mo recoverable; the free version of the warm-pool retargeting the plan ranks as our best paid audience (~570 paywall-seen + ~450 buy-clicked per 90d).
🔓 What it unblocks: the non-converter email lanes; payer-seeded lookalikes later. ⚠ Starts the day the five stage tags flow — that substrate is with Nic on his own brief; do not double-work it from here.
Right now ~500 people a week sign up, finish onboarding, and never start one of their 3 free workouts. Another ~180/week see the paywall and don't start a trial. We can email them for £0 — but only if each contact carries where they stopped. Estimated $1,000–2,600/month recoverable at zero ad cost.

The four segments to stamp on each user:

SegmentDefinitionWhat they get
A · survey unfinishedsigned up, AppOnboarding_started, no AppOnboarding_completed“Finish setup — 2 minutes”
B · no workoutonboarding complete, no workout_started“Your 3 free workouts are waiting”
C · workout, no trialworkout_started, no trial_started“Here's what week 2 looks like” → free trial
D · paywall, no trialpremium_offer_viewed, no trial_started72h later: $9.99 / 30-day step-down
  1. Compute each user's furthest funnel step (from the backend's own event/state data — no need to query GA4).
  2. Write it to a Customer.io / ActiveCampaign contact attribute, e.g. funnel_stage = survey_unfinished | no_workout | no_trial | paywall_seen, plus last_seen_at.
  3. Refresh nightly.
Done when: the four segments are addressable in CIO/AC and their counts roughly match GA4 (~65 / ~500 / ~90 / ~180 per week).
10 Make the quiz funnel segmentable by traffic source in GA4CLOSED 7 Aug ✅ closed🟢 no app releaseNic · 1–2h
✅ CLOSED 7 Aug 2026 — and here is why the row outlived its own answer. On 6 Aug the ask was downgraded but left OPEN for a residual of three sub-steps, with the dead headline still on top. All three are now answered, so it retires rather than gets carried: (1) UTM survival — VERIFIED PASSING live 7 Aug: quiz.themovementathlete.com/?utm_source=nictest&utm_medium=cpc&utm_campaign=probe 302s to /2026-quiz-checkout-v1/ with all three parameters intact. (2) sessionSource populated — proven 6 Aug by the filtered pull below. (3) GA4 user_id at email capture — the only genuinely open piece, and it is Task 17’s work (canonical user identity, external RN team): the served quiz bundle’s only user_id references are Amplitude SDK internals, and it ships no GA4 measurement ID at all. A row whose headline names a dead ask is a row nobody can read — that is why this one looked open when it was not. Folded into Task 17.

The 6 Aug downgrade, kept for the record🔎 DOWNGRADED 6 Aug: GA4 already splits the quiz host by source — verified with a filtered pull (never a top-N list): 6,840 quiz-host sessions/28d incl. google/cpc 356 + fb/ig paid 601, and generate_lead splits too (372 total, 166 paid-sourced). The paid-vs-organic question is answerable today. Residual is only: UTMs surviving the landing→quiz hop, and user_id at email capture for the web↔app join.
GA4 config / web tagging on the quiz host
🎯 Why (verified): paid landing→quiz-start must be readable separately from organic, or ad creative gets blamed for a landing-page problem. DOWNGRADED 6 Aug: the host-level split already works (verified pull — 6,840 quiz sessions/28d, 372 generate_lead incl. 166 paid-sourced).
⚙️ What it enables: the residual only: UTMs surviving the landing→quiz hop, and user_id at email capture so web and app join later.
🔓 What it unblocks: clean per-source CRO verdicts on the quiz (paid ≥47% start-rate target); feeds the Task 17 identity join.
We must be able to read paid landing → quiz start separately from organic. Today's landing screen loses 51% of visitors — if that stays true for paid traffic, we'd be blaming ad creative for a landing-page problem, and burning the budget diagnosing the wrong thing.
  1. Ensure UTMs survive the landing → quiz navigation (don't strip query params on redirect).
  2. Confirm sessionSource / firstUserSource are populated on the quiz page-view and screen events.
  3. Set GA4 user_id once the email is captured, so web and app join later.
Done when: Claude can pull landing→S01 start-rate split by source (target: paid ≥47%).
R6 Kill the junk traffic — the last live dead placement moved from the revenue plan · 7 Aug🟢 no releaseNic · ~15 min · console
✅ RE-VERIFIED & RE-RANKED №1 — 7 Aug PM, Google Ads API (v21 and v22, identical). Campaign 24012014523 is ENABLED and spent every one of the last 14 days: £75.68/7d · £177.88/14d · £206.74/30d, 0 conversions in every window, £7.91 today. It is 22% of 30-day account spend. Why keyword-level doctrine does not save it: all 66 keywords are equipment-purchase intent (calisthenics bars for sale, street workout equipment…) — buyers of hardware, not app subscriptions; the 3 keywords someone already paused carried 100% of last-7d spend and it is still spending, and the 59 never-served keywords are the same wrong intent class, so “untested ≠ failed” does not apply. Do: pause the ENTIRE campaign in the console (~5 min). Done when: status PAUSED and next-day spend £0 — we re-run the same API read the day you say it is done. Adjacent, for the next pass: Quiz Funnel | Non-Branded | US spent £228.73/30d for 6 conversions — watch it, not kill it yet.
➡️ MOVED HERE 7 Aug 2026 — this was row 6 on the revenue plan. Aga asked for one list per person, so every outstanding Nic row came across with its full text. HALF DONE. Meta Audience Network solved itself ($4.61 of $605.91 = 0.8% of 30-day spend — nothing left to kill). The open half is Google: Quiz Funnel | Competitive Non-Branded | US is still ENABLED and burned £199.28 in 30 days for 24 clicks and 0 conversions.
Google Ads console (690-916-9207)
🔎 RE-VERIFIED 6 AUG — HALF DONE, and the open half MOVED. ✅ Meta Audience Network solved itself: $4.61 of $605.91 = 0.8% of 30-day spend, nothing left to kill. 🔴 The Google junk is still live: “Quiz Funnel | Competitive Non-Branded | US” is ENABLED and burned £199.28 in 30 days for 24 clicks and ZERO conversions — £8.30/click, the most expensive click in the account — and PMax is ENABLED at £146.89. That kill is now the whole task: a 5-minute console job.
🔎 VERIFIED 5 AUG — STILL OPEN. 🔴 = AGA-104 (a)+(b). No completion record for the Competitive Non-Branded pause (still 35% of Google spend at £10.63/click, 0 conv at last live check) and the Audience Network placement lock was never applied — AN stopped on its own and can return untouched.
Google: pause “Competitive Non-Branded” — it is live and taking 35% of Google spend. Meta: untick Audience Network as a permanent lock (it has already stopped on its own).
why this matters + step-by-step →

🔄 CORRECTED 29 Jul 2026 — live-verified against the Google Ads API and Meta Graph API. The earlier version of this task said these placements were “10.1% of all quiz traffic”, that Competitive Non-Branded costs “$14.64 a click”, and that it “has converted nobody in 90 days”. All three were wrong. The two halves of this task turned out to be opposite problems and are now split.

Why it matters — Meta Audience Network: it already stopped, 17 days ago. AN was a three-week burst, not a standing condition. Weekly spend (GBP, account level):

weekAN spendAN clicksAN leadsaccount totalAN %
15 Jun£11.363520£527.892.2%
22 Jun£21.903024£720.713.0%
29 Jun£136.902,5530£1,749.567.8%
06 Jul£35.965040£671.285.4%
13 Jul£0.0100£59.030.0%
20 Jul£0.0700£130.940.1%
27 Jul£0.0400£52.420.1%

90-day AN total is £213.81 of £6,428.79 = 3.3%, not 10.1% — and it is currently 0.1% of spend. Nobody switched it off; Advantage+ reallocated on its own, which means it can come back at any time without anyone touching anything. The original reasoning was still right: AN produced 3,505 clicks for 28 leads while barely registering in GA4 (129 visitors) — most “clicks” never landed, which is the accidental-tap signature. So the fix is still worth making, just as a 5-minute permanent lock, not an emergency.

Why it matters — Google “Competitive Non-Branded”: live, and the opposite story. Status ENABLED right now. Over the last 7 days it is the single biggest Google line item: £101.72 of £287.04 total Google spend (35%) for 9 clicks.

weekspendclicks£/clickconversions
20 Jul£73.565£14.710
27 Jul£32.775£6.550
TOTAL£106.3310£10.630

First impression: 10 July 2026. The campaign is 19 days old, not 90. So “converted nobody in 90 days” was false, and “$14.64 a click” was one week’s figure (w/c 20 Jul), not an average.

🔴 Read this before repeating the argument. “It has converted nobody” is not evidence of anything. On 10 clicks, at our ~3% land→pay rate, you would expect 0.3 sales. Zero is exactly what a perfectly healthy campaign would also show. Kill it on unit economics instead: at £10.63 a click against a $5.02/lead benchmark and a $24/sub floor, it cannot reach a viable CAC at any conversion rate. That argument survives scrutiny; the zero does not.

How to do it

  1. Do this first — Google Ads → pause Quiz Funnel | Competitive Non-Branded | US | Sales OBJ. This is the only live bleed of the two.
  2. Meta Ads Manager → ad sets → Manual placements → untick Audience Network. Not urgent — this is a lock so the late-June spike cannot repeat.
  3. ⚠️ Hold the Display/PMax exclusion. A live GA4 pull contradicts the “zero leads” claim for googleads.g.doubleclick.net — it shows the highest event density of any source. Resolve before cutting.
  4. Record both dates separately — see the correction to task 20 below.

⚠️ Correction to task 20. Task 20 says “wait 14 days after task 6”. The Meta half of the denominator already changed on ~13 July and nobody recorded it. That clock has partly run. Re-baseline from the Google pause date, and treat any Meta-driven change in the rates as having already happened.

✅ Done when: Competitive Non-Branded shows Paused, Audience Network unticked on every live ad set, and both dates recorded separately.

R16 The landing screen — the biggest single loss (ranked LAST, deliberately) moved from the revenue plan · 7 Aug🟢 no releaseNic · re-ranked LAST
✅ RE-VERIFIED 7 Aug PM — still open, ranking LAST confirmed, and the blocker is bigger than this card says. The served build (index-yC00xO94.js) contains nine landing variants (landing_v3_1…9) but variant selection is compile-time (VITE_QUIZ_VARIANT baked in), so every one is dead code and no landing test can currently run at all — a readable A/B needs a per-variant build + router/CDN split that does not exist. ~$270/mo [MODELLED] vs genuine build work; stays last. Promotion trigger, so this is not parked forever: re-rank R16 the week BOTH are true — (a) the attribution stamps have shipped (results become attributable per build), and (b) v1 clears the 21 Aug gate (≥12 sales — at today’s ~8/mo a landing A/B cannot reach a read in any reasonable window; the honest conversion CI is 1.1–4.7%). Until then every hour here buys less than the four cards above.
➡️ MOVED HERE 7 Aug 2026 — this was row 16 on the revenue plan. Aga asked for one list per person, so every outstanding Nic row came across with its full text. 🔴 Aga, 6 Aug: “this is NOT done and we have not done it.” Real — 54.5% of visitors lost, ~2,816 people per 90 days — but stable since ≥9 Jul, so structural rather than a June regression. Standing ruling: it goes LAST. Stable-and-expensive waits behind the leaks that actually moved.
Quiz landing screen
🔴 AGA, 6 AUG: “this is NOT done and we have not done anything about this.” Confirmed — the landing screen has had no work at all. The 5 Aug re-rank stands as sequencing (it is structural, stable since ≥9 Jul, so it goes after the leaks that moved) — but re-ranked is not started, and this row must not read as handled. 2,816 people per 90 days still arrive and never tap once.
🔎 VERIFIED 5 AUG — RE-RANKED. ⚠️ Real but re-ranked: the 54.5% landing loss is stable since ≥9 Jul, i.e. structural, not a June regression. Standing ruling: the landing screen goes LAST — it is stable and expensive; fix the leaks that moved first.
2,816 people per 90 days arrive and never tap once — 54.5%, or 49.8% once bot traffic is stripped. [Estate B — the quiz landing screen]
why this matters + step-by-step →

Why it matters. 🔴 Re-ranked 27 Jul. It was parked at the bottom on the reasoning “stable means hard, and we cannot A/B test”. Half of that no longer holds — bucketing lands with task 1. It is still right to fix the known missing thing (annual) before the unknown creative problem, but this is the largest number in the business and everything downstream multiplies by it. It goes straight after the paywall read is clean — not last.

How to do it

  1. Wait for task 6 (junk out) and task 20 (re-baseline) so you are testing against a stable denominator.
  2. Bucketing from task 1 must be live, or the test is unreadable.
  3. Then treat it as real creative work: a message hypothesis, not a tweak.

✅ Done when: A tested change with a before/after against the 49.8% baseline.

R23 Work the /register page as CRO — BLOCKED on Aga’s decision 32 moved from the revenue plan · 7 Aug🟢 no releaseNic · BLOCKED · Aga decides first
✅ RE-VERIFIED 7 Aug PM — correctly BLOCKED, and one evidence line below is retired. Decision 32 is still unruled (open on Aga’s revenue plan #ck32 and mirrored at the Nic brief’s #register-decision — Aga’s call, ~10 min, and it decides whether this task exists at all). Both /register/ URLs still 200. 🔴 Ignore any “604 buy-clicks → 248 starts” figure in this card — that measurement was made on the OLD /register/ flow and Aga retired it 3 Aug with the on-page-checkout task; the CRO upside is currently unquantifiable until the ruling + a fresh read.
➡️ MOVED HERE 7 Aug 2026 — this was row 23 on the revenue plan. Aga asked for one list per person, so every outstanding Nic row came across with its full text. ⛔ Blocked on decision 32, and the block is real. All five /register/ plan URLs return 200 live (yearly3, yearly_2026, monthly_2026, quarterly_2026_promo, monthly_tripwire_2026) — the page is genuinely still serving buyers. Do not CRO a page that may be retired. Decision 32 stays on Aga’s plan.
/register/<plan>/ — Estate B
🔎 RE-VERIFIED 6 AUG — still blocked on decision 32, and the block is real. All five /register/ plan URLs return 200 live today (yearly3, yearly_2026, monthly_2026, quarterly_2026_promo, monthly_tripwire_2026) — so the page is genuinely still serving buyers. Do not CRO a page that may be retired: rule 32 first.
🔎 VERIFIED 5 AUG — STILL OPEN. ❓ Open, and tangled with task 32 (does /register migrate to v3 or retire?). Decide 32 first.
The last surface before money on the quiz path, never given a conversion pass. [Estate B — /register/<plan>/, NOT checkouts-v3]
why this matters + step-by-step →

🔄 RE-SCOPED 29 Jul 2026. This task used to say “the checkout / register page”, which was ambiguous — and now dangerously so, because there are two checkout estates. It means Estate B only: the six app.themovementathlete.com/register/<plan>/ pages the quiz paywall redirects to. It does not mean the new checkouts-v3 pack, which Nic has just rebuilt.

Why it matters. The last surface before money on the quiz path, never given a conversion pass. 604 buy-clicks produced 248 subscription starts — everything lost between those two numbers is lost here or on the hop to get here.

How to do it

  1. Wait until the paywall read is clean (tasks 1, 6, 20).
  2. Check the migration decision first — if /register is being retired in favour of checkouts-v3, this task disappears rather than gets done. Do not CRO a page that is about to be deleted.
  3. If it stays: run it through the checkout design loop (/checkout-fleet).
  4. Deploy only on a double SHIP verdict.

✅ Done when: a measured change with a before/after — or a written decision that the page is being retired instead.

Stripe cohort pull — web trial→paid rate — ANSWERED 6 Aug: web trial→paid = 30.0%✅ done · number litClaude · Stripe API

The number, so nobody re-asks: web trial→paid = 30.0% — 24 paid of 80 real-email trials, phantom mints excluded (measured 6 Aug from Stripe). App trial→paid = 62% (RevenueCat, low volume). The web funnel converts trials at roughly half the app’s rate — that gap is the CRO target, and every paid-scale model should use 30%, not the app number.

P2 · detail blocks

blocked or low leverage
16Rebuild Google UAC to optimise to the trial event (later)blocked🟢 no releaseClaude spec → Nic
Where it stands — Blocked until 5 + 8
Google Ads console — only after Tasks 5 + 8
🎯 Why (verified): UAC bought 1,230 installs and zero attributable subscribers because installs were the only thing it could see. All UAC campaigns verified PAUSED 6 Aug — correctly parked, not dead.
⚙️ What it enables: a keep/kill decision on evidence: UK/US only, optimise to trial-start, 200-install baseline, scale only if install→trial ≥5%.
🔓 What it unblocks: nothing today — this row EXISTS to stay blocked until Tasks 5 + 8 verify. Restarting it earlier re-buys the same blind installs.
Google UAC isn't "dead" — it was blind. Judgement is deferred until it can see a purchase. Then: UK/US only, optimise to trial (never installs), 200-install baseline before any verdict.
  1. Do not restart UAC until Tasks 5 and 8 are verified.
  2. Rebuild UK/US only, optimisation event = trial start.
  3. Collect a 200-install baseline; scale only if install→trial ≥5%.
Done when: a real install→trial rate exists and the keep/kill decision can be made on data.
26 Email deep-link infra — OneLink template + ESP wiring (the add-on's no-release half) 🟢 no releaseNic · ~½ day
✅ RE-VERIFIED 7 Aug PM — SPLIT this task; Nic’s half is ~5 min then PARKED. The route map exists and is locked (v1.0, in this folder) but no OneLink exists anywhere (all 4,100 AC bodies scanned — only Google’s own signature links), only 18 of 4,100 messages carry an app-store URL (retrofit value ≈ zero), and the value is gated on Task 24 (store release, no date). CIO half premature (0 active campaigns; AC sends for months). Fleet does now: resolve the map’s web fallbacks to real URLs + ship the link-to-web interim + write the OneLink template spec. Nic: confirm OneLink is on the AppsFlyer plan (5 min — if it needs the paid Deep Linking Suite, that is an Aga spend decision), then park until Task 24 has a date. Detail: docs/company/ANALYTICS/NIC_EXECUTION_PACK_2026-08-07.md.
Where it stands — No-release half of the deep-link add-on (feeds Task 24)
AppsFlyer dashboard (OneLink) + Customer.io / ActiveCampaign ESP integration — no app release
🎯 Why (verified): email links into the app currently dump people at the store or a browser page, shedding attribution on the way. This is the no-release half of the deep-link pair (Task 24 is the app half).
⚙️ What it enables: emails that open the app on the right screen with attribution intact, sendable from CIO/AC without their link-wrapping breaking it.
🔓 What it unblocks: Task 24's app-side work (they only function together); email→app conversion measurement. ⚠ Verified 6 Aug: our AppsFlyer plan has NO raw-data reports, so the measurement depth is capped until the plan question is settled — price that in before spending the ½ day.
The dashboard / no-release half of the email deep-link add-on. It produces the OneLink and configures the email tools so wrapped links resolve; the RN team then wires the app to route them (Task 24). One does not work without the other.
  1. Confirm OneLink is on the AppsFlyer plan (most paid tiers include it; if not, add the standalone "Deep Linking Suite"). 5-minute dashboard check.
  2. Build the OneLink template — the smart link carrying deep_link_value + a per-link web fallback URL.
  3. Complete AppsFlyer's ESP integration for Customer.io (and ActiveCampaign) so their link-wrapping doesn't break the deep link. Register the OneLink + ESP click domains — and hand those exact domains to the RN team for Task 24.
  4. Hand off: the route map (deep_link_value → screen) ↗ to the RN team; the OneLink URL pattern to the email team.
Done when: a OneLink exists that (with Task 24 shipped) opens the app to a chosen screen; Customer.io/AC send it without breaking it; and the email team has the link pattern.

🔴 NEEDS A STORE RELEASE

6 open — external RN team, not Nic

These six are the only tasks on this page that need an App/Play-store release. Full detail in the RN Team Brief ↗.

17 T4 — One canonical user identity across all SDKs the keystone 🔴 store releaseexternal RN team · 6–10h
Where it stands — Every server-side integration
App code — iOS + Android (React Native) · external RN team, ships with a store release
🎯 Why (verified): web and app think our users are different people — no shared id joins the quiz, the email, the app session and the payment. Verified: no RC profile carries $amplitudeUserId.
⚙️ What it enables: one person, one journey, across web→app→payment; CAC and payback BY CHANNEL — the two cockpit metrics that stay dark until this ships.
🔓 What it unblocks: cohort LTV by source; every “which channel actually pays back” decision. The keystone REL item — the other five multiply its value.
Every server-side integration on this page attaches events to a user id. If RevenueCat, Amplitude and AppsFlyer each hold a different id, the events land on three different ghosts and no journey can be stitched from ad → trial → subscriber. Everything downstream depends on this.
  1. Pick the canonical id: the backend user id (the one the web quiz will also set as GA4 user_id).
  2. On login and on signup, set it everywhere:
    • RevenueCat: Purchases.logIn(userId)
    • Amplitude: amplitude.setUserId(userId)
    • AppsFlyer: setCustomerUserId(userId)
  3. Set the RevenueCat subscriber attributes $amplitudeUserId and $appsflyerId (from AppsFlyer.getAppsFlyerUID()) so the server-side connectors can attach correctly.
  4. Handle logout / account switching so ids never leak between users.
Done when: one test purchase appears under the same user id in RevenueCat, Amplitude and AppsFlyer.
18 T7 — Native product & lifecycle events (retention) 🔴 store releaseexternal RN team · 8–16h
Where it stands — D1/D7/D30 retention
App code — iOS + Android · external RN team, ships with a store release
🎯 Why (verified): paywall views and trial starts ARE evented in GA4 (premium_offer_viewed 1,273 · trial_started 102, 90d to 20 Jul) and since 7 Aug the three coarse stall-gates are labelled per person in AC — but nothing carries a clean timestamp against a canonical id, so no cohort curve can be drawn.
⚙️ What it enables: the TIMING layer — when they stalled, whether they came back, and what re-engagement did. The stall LOCATION is already answered per person by %TMA_FUNNEL_STAGE% (shipped 7 Aug, no release).
🔓 What it unblocks: in-app conversion optimisation and the retention diagnosis the churn number keeps asking for.
Without these, D1/D7/D30 retention can't be read from real behaviour — and retention is the number that decides both break-even and the company's valuation.
UPDATE 7 Aug 2026 — the per-person half of this shipped, with no release. The task SHRINKS; it does not die. The nightly Intercom→AC pipeline (com.tma.funnelstage, 02:30) stamps %TMA_FUNNEL_STAGE% on every 90d-active app signup already in AC — survey_unfinished 4,356 · no_workout 6,775 · no_trial ~1,800 (checkout 432 = /abandon-fleet; payers excluded) — so “which gate did this person stop at, and how big is each stall pool” is answerable and emailable today (funnel_stage_export.py --dry-run). What it does NOT give is why this task exists: the stamp is a current-state label whose _AT field is the day the job ran, not the day they stalled — so still no D1/D7/D30 curve, no activation timing, no push/review denominator, no canonical-id stitching. What remains in scope: the timestamped event stream (steps 1–3 below). Masters: AUDIENCE_INTEGRITY_MASTER_2026-08-07.md + AUDIENCE_DATA_DICTIONARY_2026-08-07.md.
UPDATE 21 Jul 2026 — this is no longer a total blackout. A behavioural retention read exists today, with no release: the native iOS + Android streams already flow into GA4 property 177471782 (filter platform ∈ (Android, iOS)), giving D7/D28 engagement-retention cohorts by platform, with history. Baseline (pre-promo = the truth): D7 ~20% / D28 ~6%, stable a year (iOS 24%/7% vs Android 16%/5%) (Feb–Apr 2026 baseline, n=1,378, as_of 24 Jul — re-pull before quoting; source: SESSION_MASTER_SUMMARY_2026-07-20_21.md + retention_status.json, NOT the deep-analysis doc, whose 13%/6% is the promo-polluted read). (The May 50%-off promo retained healthier; the 4–12 Jul lifetime promo retained at ~0% and polluted every recent blended number — always report baseline, promo-excluded.) This task is still needed: it upgrades that from messy, un-stitched engagement cohorts to clean, canonical-id Amplitude cohorts — and the paid/subscription retention that decides break-even still comes from RevenueCat — and Task 1 was banked ✅ 6 Aug (rc_initial_purchase 44/30d landing), so that rail is live; the residual id-stitch is Task 17. GA4's native events are also double-tagged + carry an iOS screen_view firehose (part of what proper events here fix). Full verified read: docs/company/ANALYTICS/NATIVE-APP/NATIVE_APP_DEEP_ANALYSIS_2026-07-20.md.
Instrument for TWO distinct retention problems (diagnosis 21 Jul). Retention is not one number — it's two problems wearing each other's clothes, and this task must measure both: (1) Activation / the D1 cliff — the daily curve is a cliff not a slope (D0 100% → D1 ~10% → D7 2%), and ~54% who finish onboarding never start a workout. This is the durable lever and it's a product fix (the "tomorrow's workout is ready" + notification-permission moment at first-workout completion). Instrument: onboarding_completed → first_workout_started → workout_completed, notification_permission_*, and D1/D7 return. (2) Value-perception / subscription churn — 20.5% leave on cost/value, 65% all-time / 71% in 2026 would “pay nothing” (re-derived 20 Jul on 3,984 answered — supersedes the old 64%), but only 6.9% say "lack of usage" while engagement D28 is 6% → most "too expensive" is a usage failure laundered as a price answer. This is the fast lever (email/billing saves — R1, dunning, $9.99 win-back) and reconciles to RevenueCat. Net for Nic: the retention events here should light up the activation funnel + D1 return (problem 1), not just the D30 curve — that's the half that fixes the pipe feeding the churn queue.
  1. Initialise and identify the native Amplitude SDK on both platforms, using the Task 17 id.
  2. Fire the already-named events from the Event Catalog at the right moments (workout started/completed, auth, key product actions). You're not designing events — 53 are already named and dormant.
  3. Also add push + review measurement (new — folded into this task in the RN brief): push_permission_*, push_received/push_opened, app_opened with source, in_app_review_prompted. These make re-engagement measurable and give D1/D7/D30 a denominator.
  4. Defer the onboarding-screen and paywall-screen events until the signup redesign ships — those screens are being rebuilt.
Done when: workout + auth events arrive from real devices carrying the canonical id, and a clean retention curve can be drawn.
22 RevenueCat Paywalls SDK — remote paywall & price control the one that ends the release cycle 🔴 store releaseexternal RN team · 8–16h
Where it stands — Ends the paywall release cycle (Leak #2)
App code — iOS + Android · external RN team · ships FIRST as its own standalone release (ahead of the redesign, to close Leak #2 sooner) · full detail in the RN Team Brief ↗
🎯 Why (verified): every paywall or price change in the app currently needs a store release and days of review.
⚙️ What it enables: remote paywall and price control — test offers and price points in the app WITHOUT a release.
🔓 What it unblocks: in-app pricing experiments; makes the annual-mix push testable on the native side.
Only ~15–20% of people who see the in-app paywall go on to enter a card — the single biggest leak in the funnel. Closing it means iterating on price, copy and layout — but the paywall is hand-coded in the app, so every test needs a full store release today. RevenueCat can serve the paywall remotely (server-driven UI) and assign A/B variants server-side (Experiments). After this one integration, TMA changes prices, copy and layout and runs paywall A/B tests from a dashboard, with no build — permanently. This is the highest-leverage item in this release: every other task makes something visible; this makes the most important screen changeable.
  1. Upgrade the RevenueCat SDK to a Paywalls-capable version; add RevenueCatUI.
  2. Render the paywall from the remote Offering + Paywall config (never hardcoded product ids) so Experiments can assign variants without a build.
  3. Keep firing the existing checkout events (subscription_checkout_screen_opened, payment_initiated, subscription_checkout_confirmed) so the funnel stays continuous.
  4. Confirm in the RN brief's confirm-pass: does the app already render the current Offering? current RC SDK version? (both size the work).
Done when: a paywall design/price change made in the RevenueCat dashboard appears on a device with no new build, and a two-variant Experiment splits traffic. Concerns the in-app paywall only — the web checkout is separate (Task 19).
23 RN‑5 — iOS ATE/ATT + AppsFlyer always-init 🔴 store releaseexternal RN team · ~3h · ships R1 with 22
Where it stands — Meta App-Install attribution (ships R1 with 22)
App code — app/_layout.tsx, services/AppsFlyerService.ts, services/FacebookService.ts · external RN team
🎯 Why (verified): without ATT prompt + always-init, iOS installs attribute to nothing — the highest-LTV platform reads as Organic.
⚙️ What it enables: iOS install and purchase attribution (within ATT consent rates).
🔓 What it unblocks: judging ASA and any iOS paid spend on real numbers — ASA is already our best-attributed paid line (Branded-EU $1,375/30d, verified 6 Aug).
Meta shows no usable App-Install attribution because AppsFlyer is not started unless ATT is granted, and ATE is never set. The fix: always init AppsFlyer (grant AND deny), pass timeToWaitForATTUserAuthorization, and let ATE follow ATT. Small, attribution-critical — ships in Release 1 with Task 22.
Done when: AppsFlyer initialises for grant and deny; Meta receives events with ATE true and false; the App-Installs ATE-volume quality issue clears. Full steps in the RN Team Brief ↗.
24 RN‑6 — install-UTM stitch + ➕ email deep-link add-on 🔴 store releaseexternal RN team · ~4h + ~2.5h add-on
Where it stands — Google→email stitch · email links open the right screen
App code — AppsFlyerService + auth hooks + DeepLinkService · iOS Associated Domains · Android App-Links · external RN team
🎯 Why (verified): a web visitor who installs the app arrives with no memory of the campaign that brought them; email links can't route into the app.
⚙️ What it enables: install-UTM stitching (web session → app install joined) and, with Task 26, email deep-links that land on the right screen.
🔓 What it unblocks: closing the web→app attribution gap that currently swallows every install driven by content or email.
Two things on one release, same OneLink infra. (1) Install-UTM stitch: persist install media_source/UTM and stitch to user_id + email on register, so "which paid install → which email" is answerable. (2) ➕ Email deep-link add-on: make an email link open the app to the right screen (direct) and carry a new user through install to that screen (deferred) — instead of dumping everyone at the store.
  1. Stitch (RN‑6 core): persist the full AppsFlyer conversion payload for all sources; on register/login set setCustomerUserId + RC/AF attributes + POST {user_id, email, appsflyer_id, media_source, utm_*} to the TMA endpoint.
  2. ➕ Deep-link add-on: wire the OneLink deep-link listener → the existing DeepLinkService (today it only console.logs); route on deep_link_value to the mapped screen, handling deferred (first-launch) as well as direct.
  3. ➕ Register domains: add the OneLink subdomain AND the ESP (Customer.io/AC) click domain to iOS Associated Domains + Android App-Links (the ESP wraps links, which breaks a plain Universal Link unless both are registered — this is why it needs a binary).
  4. ➕ Route map: implement TMA's deep_link_value → screen table (the route map ↗ — workout · resume_assessment · upgrade · update_payment · today); unknown → home.
The add-on's no-release half is Task 26 (Nic): confirm the OneLink plan, build the OneLink template, wire the ESP, and hand the RN team the domains + route map. The RN team only wires the app.
Done when: a Google-test install+register produces the stitch; AND a test OneLink email opens the mapped screen with the app installed, and a fresh install from it lands on the mapped screen (deferred). Full steps in the RN Team Brief ↗.
25 RN‑7 — screen-level tracking 🔴 store releaseexternal RN team · ~3h
Where it stands — App opens → page path
App code — Expo Router navigation listener + AppsFlyerService / AnalyticsService · external RN team
🎯 Why (verified): we cannot see which app screens people reach or abandon — the in-app journey between those screens is invisible — since 7 Aug three coarse waypoints (setup done / first workout / first paid) are labelled per person in AC, but nothing between them is.
⚙️ What it enables: screen-level funnels in Amplitude — drop-off located to a screen, not guessed.
🔓 What it unblocks: the app-side equivalent of the quiz drop-off analysis; feeds Task 18's lifecycle picture.
AppsFlyer app-opens have no screen/route, so the funnel can't show an Install → Purchase page path. One navigation listener fires a screen event on every route change (durable screens only; skip the doomed onboarding pages).
Done when: a Home → Workout → Upgrade session produces three distinct screen events, not one app_open. Full steps in the RN Team Brief ↗.
Who owns what — read the owner pill on every task

Nic — paid, wiring, web/checkout, backend. That's almost everything on this page.
Aga — email (ActiveCampaign) and Intercom. Task 20 (Day-5 trial reminder) ✅ done 10 Jul; remaining: the Intercom sends once Task 12's code change lands.
External RN team — only what needs an App Store / Play Store release: Tasks 17, 18, 22, 23, 24, 25, plus the code half of Task 12. Two releases: Task 22 (+ RN‑5 / Task 23) ship first & standalone (close Leak #2 + Meta App-Install attribution sooner); 17/18/24/25/12 ride the redesign. Task 24 (RN‑6) now carries the email deep-link add-on. See the RN Team Brief ↗.
Claude — all dashboards, verification against live APIs, the weekly reconciliation. Zero hours from anyone else.