Bonds Analytics Catalog

Comprehensive reference for all Mixpanel tracking in the Bonds app. Module: src/utils/analytics.js.

For product: This document lists every event, property, user attribute, and funnel tracked in the app. Use the table of contents to navigate.

For developers: When adding or changing events, update this document in the same PR. See the Developer Checklist at the bottom.


Table of Contents

  1. Conventions
  2. Identity & Properties
  3. Screens
  4. Domain Events
  5. Element Clicked Reference
  6. Funnels
  7. Recommended Mixpanel Reports
  8. Developer Checklist

1. Conventions

Measuring: always filter to US/CA

Any real-user analysis (funnels, retention, drop-off claims) MUST be filtered to mp_country_code = US or CA. The team runs heavy QA testing against production, concentrated in India/Israel, which pollutes global numbers badly enough to invert conclusions (e.g. a "~40% forced-logout within 1 hour" cluster that was entirely India QA traffic). Mixpanel's single-value string filters can't express "in [US, CA]" — either run US and CA separately, or break down by mp_country_code and read the US + CA rows. Production project id: 3256863. No tester-exclusion cohort exists yet; this filter is the stand-in.

Naming Rules

What Convention Example
Event names Title Case, Object-Action, past tense Simulator Session Completed
Property names snake_case chapter_index, skill_level
Screen names lowercase, underscore-separated learn_video, simulator_results

When to Use Which Event Type

Pattern When to use Example
Domain event (own name) Funnel steps, business-critical actions Onboarding Completed, Daily Question Voted
Screen Viewed A distinct screen becomes visible Screen Viewed { screen: 'journey' }
Element Clicked UI interaction (button, tab, card, toggle) Element Clicked { screen: 'profile', element: 'invite_partner' }

Property Value Enums

Property Possible values
platform ios, android, web
user_type anonymous, authenticated
step_type learn, practice, insight, act
mode freeform, journey
input_mode (simulator) voice, text
method (auth) google, apple, email
element_type button, tab, banner, card, toggle, link
delivery (report) clipboard, slack

2. Identity & Properties

2a. Identity Lifecycle

┌─────────────────┐     identifyUser()      ┌──────────────────┐
│    Anonymous     │ ─────────────────────→  │   Authenticated  │
│  (random ID)     │                         │  (Bubble user ID) │
│  user_type:      │                         │  user_type:       │
│   'anonymous'    │     resetIdentity()     │   'authenticated' │
│                  │ ←─────────────────────  │                   │
└─────────────────┘                          └──────────────────┘
Stage Trigger What happens
App opens, no auth initAnalytics() Anonymous distinct_id assigned, super props registered
User authenticates identifyUser(userId, traits) via UserContext identify(userId) merges anonymous→identified, people props set
User logs out resetIdentity() reset() clears identity, back to anonymous with fresh ID

2b. Super Properties

Auto-attached to every event. Set by initAnalytics(), updated by identifyUser().

Property Type Example Description
app_version string v1.0.0-alpha-bc22c80df36e Build version with content hash
platform string ios Detected from window.natively (ios / android / web)
user_type string authenticated anonymous until first identifyUser(), then authenticated

2c. People Properties (User Profile)

Stored on the Mixpanel user profile. Visible in the People section.

Property Type Example Set by When updated
$name string "Sarah" identifyUser() First auth only
$email string "sarah@example.com" identifyUser() First auth only
coins number 150 identifyUser() + setUserProperties() Every user-state update (updateUser — fed by /me applies and the legacy setUserData push)
partner_connected boolean true identifyUser() + setUserProperties() Every user-state update (updateUser)
partner_name_collected boolean true setUserProperties() Every user-state update where partnerName is present
partner_gender string "female" setUserProperties() Every user-state update where partnerGender is present
onboarding_complete boolean true identifyUser() First auth only
signup_date string (ISO) "2026-01-15T10:30:00Z" set_once First auth ever (never overwritten)
initial_source string "Ross2" set_once via identifyUser() First auth ever (never overwritten)
initial_utm_source string "Facebook" set_once via identifyUser() First auth ever (never overwritten)
initial_utm_medium string "Paid" set_once via identifyUser() First auth ever (never overwritten)
initial_utm_campaign string "Default_Campaign" set_once via identifyUser() First auth ever (never overwritten)
initial_through string "partner-x" set_once via identifyUser() First auth ever (never overwritten)
last_source string "Ross2" set via identifyUser() / captureAttribution() Every new campaign click
last_utm_source string "Facebook" set via identifyUser() / captureAttribution() Every new campaign click
last_utm_medium string "Paid" set via identifyUser() / captureAttribution() Every new campaign click
last_utm_campaign string "Default_Campaign" set via identifyUser() / captureAttribution() Every new campaign click
last_through string "partner-x" set via identifyUser() / captureAttribution() Every new campaign click
is_subscribed boolean true setUserProperties() via EntitlementContext Every entitlement refresh (auth, foreground, post-purchase, post-restore)
subscription_type string | null "quarterly" setUserProperties() via EntitlementContext Every entitlement refresh. Resolved from the active entitlement's store product id → RC package id (offerings endpoint) → key of APP_CONFIG.purchases.packages (yearly/quarterly/monthly). null when not subscribed, the offerings map is unavailable, or the package isn't in config
payment_variant string "B" setUserProperties() via <PaymentVariantSeeder> When Bubble reports the variant, or on a fresh Statsig seed
push_enabled boolean true setUserProperties() via notifications.js Authoritative write (true or false) on the OS permission-dialog callback in requestPermission. initNotifications re-asserts it on cold start but only ever upgrades to true — an early-mount getPermissionStatus read is untrustworthy (bridge not ready, same timing class as BNS-1025) and writing a false there would clobber a real grant. Consequence: a Settings-level revoke is not reflected (the property can stay true until a definitive deny path runs); in-session/foreground changes are not tracked.

Cohort tag: the A/B/C payments cohort's source of truth is the Bubble User field payment_variant (Statsig only seeds it once — see docs/statsig-experiments.md). It's written to the Mixpanel user profile as the payment_variant People property (the effective value incl. admin overrides, set by <PaymentVariantSeeder>) — use this for all cohort filtering and breakdowns. It's a People (not super) property because the cohort is sticky and server-owned: a profile property survives reset(), is queryable on the user level, and is still segmentable in event reports, with no need for an at-event snapshot. The raw Statsig bucket is not mirrored to Mixpanel as experiment_payments (kept to one property to avoid confusion); Statsig retains the exposure for experiment analysis on its side.


3. Screens

All values used with Screen Viewed. Fired via screen(name) helper which calls track('Screen Viewed', { screen: name }).

Screen When shown Source Notes
welcome App loads without active session App.jsx
signin User taps "Sign In" App.jsx
signup After onboarding insight, before account creation App.jsx
onboarding_intro One-time intro video before questions App.jsx
onboarding User starts or resumes onboarding App.jsx
onboarding_insight During insight generation/playback App.jsx
journey Journey tab selected MainTabs.jsx
dr-leo Dr Leo tab selected (formerly simulator) MainTabs.jsx Tab id renamed. Deep links: /dr-leo (choice), /dr-leo/ask, /dr-leo/simulator; legacy /simulator opens the simulator directly
dr_leo_choice Dr Leo "How can I help?" choice screen DrLeoChoiceScreen.jsx
ask_landing Ask Anything pre-session intro AskLanding.jsx
ask_session Ask Dr Leo realtime Q&A session (text/voice) AskSession.jsx
ask_answer Ask Dr Leo answer + CTAs AskAnswer.jsx
ask_act Ask act proposal ("Act upon it" from the answer) AskActFlow.jsx
act_acceptance Act acceptance screen ("This is where change happens") + moment drawer AskActFlow.jsx Ask acts only for now; the journey act reaches the same screen via ActStep, which does not report it as a screen
act_check_in Act check-in ("How did it go?") reached from a check-up push or the app-open resurface AskActFlow.jsx Head of its own funnel — the resurface/deep-link entries never pass through ask_act
fun Fun Zone tab selected MainTabs.jsx
profile Profile tab selected MainTabs.jsx
step_review User opens a completed step for review StepReview.jsx Extra props: step_type, chapter
ftue_walkthrough First-time user walkthrough starts MainTabs.jsx
daily_question Daily question overlay opened DailyQuestion.jsx
push_prompt Push notification permission dialog shown MainTabs.jsx
name_prompt Name entry dialog after signup MainTabs.jsx
partner_name_prompt Partner name entry dialog (after user name dialog) MainTabs.jsx
account_info Account info / danger zone screen MainTabs.jsx
share_viewer Shared content viewer (external link) ShareViewer.jsx

4. Domain Events

App Lifecycle

App Opened

Fires once on app launch after analytics initialization.

Property Type Example Required
(none)

Source: App.jsx

App Backgrounded

Fires when the app moves to the background (Natively lifecycle hook).

Property Type Example Required
(none)

Source: App.jsx

App Foregrounded

Fires when the app returns to the foreground (Natively lifecycle hook).

Property Type Example Required
(none)

Source: App.jsx

Logged Out

Fires when user logs out. Identity is reset after this event.

Property Type Example Required
(none)

Source: App.jsx

Session Force Logout

Fires when Bubble force-logs the user out via setLoginState(false) while they were in an authenticated phase (no React-initiated logout pending). Most likely cause is the WebView losing its session cookie (_u2main) across a process restart, which triggers Bubble's "User is logged out" event server-side. Tracked into Mixpanel so we can monitor frequency and surface in retention/funnels alongside other auth events.

Property Type Example Required
phase string "main" yes
source string "bubble" yes

Source: App.jsx (in setLoginState(false) handler when source === 'bubble')

Since 2026-07 (conservative session validation): also fires on the cold-start path, but ONLY after /me (get_user) returned a confirmed-anonymous answer twice — source: "cold_start_me_anonymous", reason: "me_anonymous_confirmed". The old snapshot-mismatch trigger (source: "cold_start_uid_mismatch") was removed; watch this event's volume as the "did we regress auth" tripwire (Sentry alert rule).

Session Validation Outcome

Fires once per cold-start session validation (authenticated session flags present). Replaces the old silent snapshot-mismatch logic with an auditable decision. Spec: docs/superpowers/specs/2026-06-29-conservative-session-validation-design.md.

Property Type Example Required
reason string "proceed" | "account_switch" | "adopt" | "genuine_logout" | "stay_put" | "cross_env" | "fresh" | "env_switch_pending_hold" yes
tier string "production" yes (except cross_env via autoReturnHome and env_switch_pending_hold)
version_key string "(production)" | "/version-73fh1" yes (except cross_env and env_switch_pending_hold)
me_status string "authenticated" | "anonymous" | "indeterminate" | "skipped" yes (except cross_env and env_switch_pending_hold)
resampled boolean false yes (except cross_env and env_switch_pending_hold)
source string "cold_start" | "set_login_state" | "env_switch_prompt" cross_env and env_switch_pending_hold only
has_home boolean true cross_env only

env_switch_pending_hold (2026-07-07): a foreign-version setLoginState(true) arrived while the staging env-switch prompt was awaiting the user's choice — the event is held instead of bouncing (the bounce used to race the prompt); the prompt's own resolution follows as Element Clicked { switch_here | go_home }.

cross_env = landing on any non-HOME version (universal HOME rule — link or bare URL, any tier) → auto-return home, no logout, no wipe. adopt = unanchored version where Bubble confirms a session (migration heal). fresh = quiet welcome, nothing lost. Production should see ~zero; Sentry alert rule on sustained volume (a mis-built push/link reached real users).

Source: App.jsx (checkSession decision flow; autoReturnHome)

Env Switch Prompt Shown

Staging-only QA affordance: shown when a device lands on a foreign staging version whose home is also staging, in place of the silent foreign-landing bounce (design 2026-07-06). Production-home users never see it. Resolves into exactly one Element Clicked { element: 'switch_here' | 'go_home' }; go_home additionally emits Session Validation Outcome { reason: 'cross_env', source: 'env_switch_prompt' }.

Property Type Example Always?
from_version string "/version-aaa" yes (staging home key)
to_version string "/version-73fh1" yes (landed staging key)

Fires when a deeplink's tier differs from the user's active environment — the route is dropped (never honored) and the generic "This link couldn't be opened" notice is shown.

Property Type Example Required
path string "/version-live/" yes
via string "cold_start" | "handleDeepLink" | "handleDeepLink_version_mismatch" yes

Source: index.jsx (cold-start parse + handleDeepLink guard)

Fires when a deep link is processed. Attribution params (if present in the URL) are spread into the event properties.

Property Type Example Required
tab string | null "journey" no
view string | null "chapter_1" no
step_status string | undefined "completed" no
source string "Ross2" no (from attribution)
utm_source string "Facebook" no (from attribution)
utm_medium string "Paid" no (from attribution)
utm_campaign string "Default_Campaign" no (from attribution)
through string "partner-x" no (from attribution)

Source: MainTabs.jsx

Attribution Captured

Fires when a URL with attribution params is opened (cold start or warm start), regardless of whether a deep link route is present. Independent of Deep Link Opened.

Property Type Example Required
source string "Ross2" no
through string "partner-x" no
utm_source string "Facebook" no
utm_medium string "Paid" no
utm_campaign string "Default_Campaign" no

Source: analytics.js via captureAttribution()


Profile Pull (Phase 2 dual-run — shadow telemetry, removed with the element kill)

Session Data Divergence

Pre-APPLY divergence between the /me pull and current state — rollout health metric while the reactive element coexists; after element deletion it measures boot-cache staleness. Expected benign noise: transient coins divergence when a /me lands between an optimistic coin update and the reactive echo — judge by rate and persistence, not single events. Any uid divergence is a P1 signal — investigate immediately.

Property Type Example Required
via string "boot" | "boot_late" | "foreground" | "post_auth" yes
fields string[] ["coins", "email"] yes
count number 1 yes

boot_late = the boot /me resolved after Bubble's page event had already moved the app to main; the session decision is discarded but the authenticated data still applies (pre-fix, this path silently dropped the snapshot — the frozen-balance bug from QA 13-07-26). fun_zone_settled is RETIRED in BNS-1088 slice 2 — the fun-zone creation now charges inside one awaited HTTP workflow and returns the post-deduct balance in the response, so there is nothing left to reconcile: no /me pull, no setUserData push, and no race between them (#427 added the pull, #430 removed it from the delivered path; the migration removed the last of it). Check the Mixpanel boards before dropping the value from any saved report.

Source: App.jsx (session validation incl. late-boot apply, foreground refresh, post-auth checkpoint). The former ActivityView.jsx fun-zone-create-timeout refresh (FunZoneContext.refreshBalance) was removed with fun_zone_settled in BNS-1088 slice 2 — the awaited workflow response is now the only balance writer.


Authentication

Sign In Started

Fires when user initiates sign-in (OAuth or email).

Property Type Example Required
method string "google" / "apple" / "email" yes

Source: SignInScreen.jsx

Sign In Completed

Fires on first successful identity merge (anonymous → authenticated). Reads bonds_auth_method from localStorage. Since BNS-1136 the Sign-In screen tags SSO attempts too, so method reports "google" / "apple" / "email"; "oauth" remains only as the fallback when the tag is missing (e.g. the Android OAuth redirect wiped localStorage). Boards filtering on method = "oauth" should switch to the concrete provider values.

Property Type Example Required
method string "google" / "apple" / "email" / "oauth" (fallback) yes

Source: UserContext.jsx

Sign In Failed

Fires when sign-in fails (wrong password, Bubble error).

Property Type Example Required
error string "wrong_password" / "not_found" yes

Source: App.jsx (via setSignInError)

Sign In Blocked

Fires when sign-in detects a non-existing account (UID comparison or Bubble search).

Property Type Example Required
reason string "account_not_found" yes

Source: App.jsx (via onSigninComplete)


Onboarding

Onboarding Step Completed

Fires after each onboarding question is answered.

Property Type Example Required
step number 2 yes
question_id string "comm_style" yes
type string "single-select" / "slider" / "multi-select" / "open-question" yes
answer_value string "Direct" yes
partner_gender string|null "female" / "male" / "other" / null step 0 only — partner gender follow-up

Source: OnboardingFlow.jsx

Onboarding Completed

Fires when user finishes all onboarding steps.

Property Type Example Required
total_steps number 12 yes
coins number 12 yes
loveLanguage string "Words of Affirmation" no
partnerLoveLanguage string "Physical Touch" no
skippedTiebreaker boolean true no
partner_gender string|null "female" / "male" / "other" / null no

Source: OnboardingFlow.jsx

Onboarding Abandoned

Fires when user exits onboarding via back button on step 0.

Property Type Example Required
(none)

Source: App.jsx

Onboarding Resumed

Fires when onboarding session is resumed, either automatically on app reload (source: 'auto') or when user chooses to continue via the resume dialog (source: 'dialog').

Property Type Example Required
phase string "questions" / "signup" / "insight" yes
step number/string 5 / "signup" / "insight" yes
source string "auto" / "dialog" yes

Source: App.jsx

Onboarding Intro Viewed

Fires when the one-time intro video screen is mounted.

Property Type Example Required
(none)

Source: OnboardingIntro.jsx

Onboarding Intro Completed

Fires when user taps the CTA after the intro video has finished playing.

Property Type Example Required
(none)

Source: OnboardingIntro.jsx

Onboarding Intro Skipped

Fires when user taps the CTA before the intro video finishes (skips remaining video).

Property Type Example Required
(none)

Source: OnboardingIntro.jsx

Onboarding Intro Replayed

Fires when user taps replay to watch the intro video again.

Property Type Example Required
(none)

Source: OnboardingIntro.jsx

Onboarding Intro Backed Out

Fires when user navigates back from the intro screen to welcome.

Property Type Example Required
(none)

Source: OnboardingIntro.jsx

Two Last Questions Shown

Fires when the "Two last questions" popup appears just before the first open question (Groups A & C only — gated by useOnboardingCoinsHidden()). PRD UI Outputs #2. The CTA emits Element Clicked { screen: 'onboarding', element: 'two_last_questions_continue' }.

Property Type Example Required
(none)

Source: OnboardingFlow.jsx

Onboarding Insight Started

Fires when insight generation begins after all questions answered.

Property Type Example Required
(none)

Source: OnboardingInsight.jsx

Onboarding Insight Generated

Fires when the generate_onboarding_insight HTTP call returns a usable insight.

Property Type Example Required
insight_id string "ins_abc123" yes
duration_ms number 4800 yes

Source: OnboardingInsight.jsx

Onboarding Insight Generation Failed

Fires when generation fails: a transport error, the workflow's own quota_exceeded / generation_failed status, or an empty body. The user sees the retry dialog. Replaces the old Onboarding Insight Timed Out (BNS-1088 s3).

Property Type Example Required
reason string "quota_exceeded" yes
duration_ms number 250 yes

reason enum: http_5xx, http_4xx, timeout, network (transport, via failureReason), quota_exceeded, generation_failed (workflow status), empty_content (ok response, no text).

Source: OnboardingInsight.jsx

Onboarding Insight Manual Retry

Fires when the user taps "Try again" on the failure dialog.

Property Type Example Required
(none)

Source: OnboardingInsight.jsx

Onboarding Insight Prewatch Shown

Fires when the before-watching gate appears ("This insight usually uses 3 coins, but this one's on us"). Groups A & C only — the gate exists to frame the coin reward, so Group B (no coin economy) has no gate and auto-plays the insight directly, never firing this event. PRD UI Outputs #4.

Property Type Example Required
(none)

Source: OnboardingInsight.jsx

Onboarding Insight Watch Tapped

Fires when the user taps "Watch" on the before-watching gate, starting insight playback. Groups A & C only (Group B has no gate — see Onboarding Insight Prewatch Shown).

Property Type Example Required
(none)

Source: OnboardingInsight.jsx

Onboarding Insight Playback Completed

Fires when TTS playback finishes and user taps Continue.

Property Type Example Required
(none)

Source: OnboardingInsight.jsx

Onboarding Insight Playback Skipped

Fires when user taps Continue before TTS playback finishes.

Property Type Example Required
(none)

Source: OnboardingInsight.jsx

Onboarding Insight CTA Tapped

Fires when user taps "Create Your Account" after insight playback.

Property Type Example Required
(none)

Source: OnboardingInsight.jsx

Onboarding Insight Timed Out — RETIRED (BNS-1088 slice 3)

Removed when onboarding insight moved to the awaited HTTP call. The old 90s JS2B timeout no longer exists; failures now fire Onboarding Insight Generation Failed (with a reason, including timeout). Kept here as a tombstone — check Mixpanel boards referencing it before final removal.

Onboarding Data Connected

Fires after signup when the onboarding data was successfully committed to Bubble for the new user (the connect_anon_data_to_user HTTP workflow returned 200). Success-only since 2026-07 — previously this also fired on the (since removed) JS2B fallback path, where delivery wasn't confirmed.

Property Type Example Required
coins number 10 yes
answer_count number 10 yes
insight_id string "ins_abc123" yes

Source: App.jsx

Onboarding Data Connect Failed

Compensating negative event: fires when the post-signup connect_anon_data_to_user HTTP call fails after retries (or the surrounding connect block throws). The user is NOT transitioned to the main app; onboarding data stays in storage for recovery. Together with Onboarding Data Connected this resolves the post-signup funnel into connected + failed.

Property Type Example Required
error string "HTTP 500" yes

Source: App.jsx

Onboarding Step Back

Fires when user navigates backward during onboarding.

Property Type Example Required
from_step number 8 yes
to_step number 7 yes

Source: OnboardingFlow.jsx

Onboarding Mic Permission Denied

Fires when microphone permission is denied during onboarding open question.

Property Type Example Required
(none)

Source: OpenQuestion.jsx

Onboarding Mic Unavailable

Fires when microphone/speech recognition is unavailable for a non-permission reason.

Property Type Example Required
reason string "network" / "no_api" yes

Source: OpenQuestion.jsx

Onboarding Speech API Unavailable

Fires when SpeechRecognition API doesn't exist on the device and dialog auto-switches to text mode.

Property Type Example Required
(none)

Source: InsightOtherDialog.jsx

Dictation Stopped On Edit

Fires when the user manually edits the textarea (typing/deleting) while dictation is live, which ends the recognition session (BNS-1057 — an edit is authoritative, so the session terminates rather than reconciling with in-flight speech results). Signals how often users correct mid-dictation and re-tap the mic. Emitted from the shared useSpeechRecognition hook, so it covers every voice surface (Act "other outcome", journey "what's on your mind", insight "other").

Property Type Example Required
(none)

Source: useSpeechRecognition.js


Signup

Sign Up Started

Fires when user initiates sign-up (OAuth or email).

Property Type Example Required
method string "google" / "apple" / "email" yes

Source: SignUpScreen.jsx

Sign Up Completed

Fires on successful signup. Detects email vs OAuth via bonds_auth_method localStorage flag.

Property Type Example Required
method string "email" / "oauth" yes

Source: App.jsx

Sign Up Failed

Fires when sign-up fails (Bubble error).

Property Type Example Required
error string "already_exists" yes

Source: App.jsx (via setSignUpError)

Sign Up Blocked

Fires when sign-up detects an existing account (UID comparison or Bubble search).

Property Type Example Required
reason string "existing_account" yes

Source: App.jsx (via onSignupComplete)


Email Authentication

Email OTP Sent

Fires when an OTP code is successfully sent to the user's email.

Property Type Example Required
flow string "signup" / "forgot" yes

Source: SignUpScreen.jsx, SignInScreen.jsx

Email OTP Verified

Fires when user successfully verifies the OTP code.

Property Type Example Required
flow string "signup" / "forgot" yes

Source: SignUpScreen.jsx, SignInScreen.jsx

Email OTP Failed

Fires when OTP verification fails (wrong or expired code).

Property Type Example Required
flow string "signup" / "forgot" yes
error string "invalid" / "expired" yes

Source: SignUpScreen.jsx, SignInScreen.jsx

Email OTP Resent

Fires when user taps resend code.

Property Type Example Required
flow string "signup" / "forgot" yes

Source: SignUpScreen.jsx, SignInScreen.jsx

Password Reset Completed

Fires after a successful password reset + auto sign-in.

Source: SignInScreen.jsx


Sharing

Share Created

Fires when user creates a shareable link.

Property Type Example Required
content_type string "insight" / "simulator_results" / "activity" yes
content_id string "abc123" yes

Source: InsightPlayback.jsx, SimulatorResults.jsx, ActivityView.jsx

Share Viewed

Fires when someone views a shared link.

Property Type Example Required
content_type string "insight" / "simulator_results" / "activity" yes
content_id string "abc123" yes

Source: ShareViewer.jsx

App Shared

Fires when a user shares the app itself (not a piece of content) — the "Share {app}" CTA opens the OS share sheet with the brand's shareLink (brand.urls.shareLink, default https://<domain>/?source=share_app; white-label tenants override via setAppConfig). When the friend opens the link, the cold-start attribution capture fires Attribution Captured with source: "share_app" — that's how web-side joins from a share are tracked. For store-install attribution, point shareLink at a per-tenant Branch quick link (Branch is already integrated; parseDeepLink unwraps $deeplink_path) whose web fallback is this URL — Branch then reports installs by link, and the deferred deep link restores source=share_app on first open. Fires on initiation: the native share sheet (window.natively.shareText) is fire-and-forget, so share completion isn't observable. Co-fires the Appsflyer af_share event (af_description: "app").

Property Type Example Required
source string "profile" / "chapter_celebration" yes
chapter number 3 no (only from chapter_celebration)

Source: MainTabs.jsx (Profile → Support), ChapterCelebration.jsx — both via shareApp() in utils/deepLink.js

Share Reward Granted

Fires when the user is granted coins (EARN.share, currently 10) for sharing the app, capped to once per day. Granted on share tap — when a share sheet opens (grantShareRewardOncePerDay() in utils/shareReward.js), the surface calls the gift_coins Bubble action and shows the CoinGrant confirmation dialog (rendered behind the native sheet, revealed on dismiss). (Granted on tap, not on app-return: the native share sheet is a modal that often doesn't background the app, and Natively's shareText has no completion callback — a foreground-return gate missed ~80% of shares in QA. The daily cap, persisted via bonds_share_reward_date, bounds the "grants even on cancel" trade-off to once/day.)

Cohort gating: only fires for coin-eligible users — non-premium Groups A & C. Group B (paywall-first) and premium subscribers never see the coin economy, so they get a plain "Share {app}" CTA with no reward and this event never fires for them (App Shared still fires for everyone). Eligibility = useCoinRewardActive() (!isSubscribed && variant !== 'B').

Abuse note (MVP): this does not verify the friend installed — and since it grants on tap, cancelling the share sheet still grants. Accepted for MVP; watched via the ratio of this event to App Shared. Closing the loop on the invitee joining is the deferred proper fix.

Property Type Example Required
source string "profile" / "chapter_celebration" yes
amount number 10 yes

Source: ShareAppRow.jsx (Profile) and ChapterCelebration.jsx via grantShareRewardOncePerDay() in utils/shareReward.js


Account

Account Deleted

Fires when user confirms account deletion.

Source: MainTabs.jsx (AccountInfoPage)

Onboarding Question Skipped

Fires when user skips an open-ended onboarding question.

Property Type Example Required
question string "What do you wish your partner would do more of" yes

Source: OpenQuestion.jsx


Journey

Chapter numbering convention. Two property names coexist for historical reasons, both 1-based:

They agree numerically. When building a funnel across both groups, Mixpanel's "formula" column can alias one to the other.

dynamic segmentation. Every chapter-scoped event carries a boolean dynamic property (Phase 2 dynamic chapters vs predefined). Use it to split funnels and retention by chapter kind. Predefined chapters report dynamic: false; LLM-generated 3-node chapters report dynamic: true. For Simulator Session * events the property is present only when mode === 'journey'.

Chapter Started

Fires when the user pays the chapter cost (10 coins per the Coin Economy price card) via the coin gate and a new chapter begins. Not tied to a specific step — mirrors Chapter Completed.

Property Type Example Required
chapter number 0 yes
chapter_title string "Active Listening" yes
coin_cost number 10 yes
dynamic boolean false yes

Source: JourneyPath.jsx

Chapter Start Failed

Fires when the chapter_started HTTP workflow fails after all central retries and the optimistic start is reverted (BNS-1112). Chapter Started fires optimistically before the HTTP call, and the next Start tap after a revert fires it again — subtract this event to de-duplicate the start funnel. Rare by design (transient failures are retried with backoff).

Property Type Example Required
chapter number 0 yes
chapter_title string "Active Listening" yes
dynamic boolean false yes

Source: JourneyPath.jsx

Journey Step Started

Fires when a journey step begins (learn, practice, insight, or act).

Property Type Example Required
step_type string "learn" yes
chapter number 0 yes
chapter_title string "Active Listening" yes
dynamic boolean false yes

Source: ChapterFlow.jsx

Journey Step Completed

Fires when a journey step finishes successfully.

Property Type Example Required
step_type string "practice" yes
chapter number 0 yes
coins number 3 yes
replay boolean false yes
dynamic boolean false yes

Source: ChapterFlow.jsx

Journey Step Abandoned

Fires when user exits a journey step without completing it.

Property Type Example Required
step_type string "learn" yes
chapter number 0 yes
dynamic boolean false yes

Source: ChapterFlow.jsx

Journey Video Watched

Fires when the learn video finishes playing.

Property Type Example Required
chapter number 0 yes
dynamic boolean false yes

Source: LearnStep.jsx

Journey Practice Started

Fires when user starts a practice simulator session from the journey.

Property Type Example Required
chapter number 0 yes
dynamic boolean false yes

Source: PracticeStep.jsx

Journey Practice Completed

Fires when practice simulator session ends with a score.

Property Type Example Required
chapter number 0 yes
score number 4 yes
coins number 3 yes
attempt number 1 yes
dynamic boolean false yes

Source: PracticeStep.jsx

Chapter Completed

Fires when all steps in a chapter are done.

Property Type Example Required
chapter number 0 yes
chapter_title string "Active Listening" yes
dynamic boolean false yes

Source: ChapterFlow.jsx

Chapter Start Tapped

Fires when the user taps the external "START CHAPTER · N COINS" button on the journey map. This button replaces the in-bubble START for the first step of a chapter (PRD Phase 1, page 2).

Property Type Example Required
screen string "journey_map" yes
chapter_number number 1 yes
via string "external_cta" yes
step_type string "learn" yes
dynamic boolean false yes

Source: JourneyScreen.jsx

Chapter Step Start Tapped

Fires when the user taps the in-bubble START button on a non-first step (Practice/Insight/Act) on the journey map.

Property Type Example Required
screen string "journey_map" yes
chapter_number number 1 yes
via string "bubble" yes
step_type string "practice" / "insight" / "act" yes
dynamic boolean false yes

Source: JourneyScreen.jsx

Chapter Paused State Shown

Fires when the paused view is rendered the day after a chapter is completed (PRD Phase 1, page 4). Deduped by completed → next pair so a single paused session fires the event once.

Property Type Example Required
screen string "chapter_paused" yes
next_chapter_number number 2 yes
completed_chapter_number number 1 yes
dynamic boolean false yes

Source: JourneyScreen.jsx

First Step Spotlight Shown

BNS-1077 orientation beat. Fires when the first-step spotlight (dim map + glowing cutout around the chapter's first step circle + explainer bubble) appears — beat 1 of the standalone Ch1 confirmation overlay, shown once after the post-FTUE dialog chain.

Property Type Example Required Notes
screen string "first_step_spotlight" yes
chapter_number number 1 yes
step_type string "practice" | "learn" yes dynamic → practice, predefined → learn
dynamic boolean true yes

The user's tap on Next fires Element Clicked { screen: "first_step_spotlight", element_type: "button", element: "next", dynamic }.

Source: MainTabs.jsx

First Step Spotlight Skipped

Compensating event: the spotlight auto-skipped straight to the Start-Chapter sheet because the target circle couldn't be measured. Spotlight Shown funnels resolve into Element Clicked (next) + this event.

Property Type Example Required Notes
reason string "target_not_found" yes
dynamic boolean true yes

Source: MainTabs.jsx

Chapter Confirmation Shown

Fires when a predefined confirmation card is shown. Two sources — when building the Confirmation → Start → Complete funnel, union screen ∈ { ch1_predefined_confirmation, chapter_predefined_confirmation }:

Property Type Example Required Notes
screen string "ch1_predefined_confirmation" | "chapter_predefined_confirmation" yes
via string "standalone_overlay" Ch1 only
chapter_number number 1, 2, 3 yes
chapter_id string "1776754765374x…" Ch2/Ch3 Bubble chapter id
chapter_title string "Slowing the Reaction" Ch2/Ch3
dynamic boolean false yes chapter kind (Phase 2)

Source: MainTabs.jsx (Ch1), JourneyPath.jsx (Ch2 / Ch3).

Chapter Confirmation Accepted

Fires when the user taps "Start Chapter" on the predefined confirmation card (Ch1 FTUE or Ch2/Ch3 standalone sheet).

Property Type Example Required Notes
screen string "ch1_predefined_confirmation" | "chapter_predefined_confirmation" yes
chapter_number number 1, 2, 3 yes
chapter_id string "1776754765374x…" Ch2/Ch3
chapter_title string "Slowing the Reaction" Ch2/Ch3
via string "start_chapter" yes
dynamic boolean false yes chapter kind (Phase 2)

Source: MainTabs.jsx (Ch1 standalone overlay), JourneyPath.jsx (Ch2 / Ch3). The DrLeoWalkthrough.jsx last-slide path is unreachable in production.

Chapter Confirmation Dismissed

Fires when the user taps "No thanks, later" on the predefined confirmation card.

Property Type Example Required Notes
screen string "ch1_predefined_confirmation" | "chapter_predefined_confirmation" yes
chapter_number number 1, 2, 3 yes
chapter_id string "1776754765374x…" Ch2/Ch3
chapter_title string "Slowing the Reaction" Ch2/Ch3
via string "no_thanks_later" yes
dynamic boolean false yes chapter kind (Phase 2)

Source: MainTabs.jsx (Ch1 standalone overlay), JourneyPath.jsx (Ch2 / Ch3). The DrLeoWalkthrough.jsx last-slide path is unreachable in production.

FTUE First Slide Shown

Fires when the FTUE first slide ("Generating your chapter…") is shown (PRD Phase 1, page 5).

Property Type Example Required
screen string "ftue_first_slide" yes
step number 0 yes
is_replay boolean false yes

Source: DrLeoWalkthrough.jsx

FTUE Skipped

Fires when the user taps Skip on any FTUE step. Per PRD §6, Skip now jumps to the last FTUE step instead of closing the walkthrough.

Property Type Example Required
screen string "ftue_first_slide" | "ftue_walkthrough" yes
step number 0 yes
is_replay boolean false yes

Source: DrLeoWalkthrough.jsx

Chapter Menu Opened

Fires on the OPEN transition of the chapter-list dropdown (chevron tap when the menu was closed). Does not fire on close — use the companion Element Clicked { element: 'chapter_menu_chevron' } event for bi-directional tap counts.

Property Type Example Required Notes
screen string "journey_map" yes
chapter_number number 2 yes focal chapter at the moment of tap (1-based)
chapter_id string "1776754765374x…" yes Bubble chapter id
dynamic boolean false yes focal chapter's kind

Source: JourneyScreen.jsx

Chapter Menu Chapter Selected

Fires when the user picks a chapter from the chapter-list dropdown. Funnels dropdown engagement → scroll navigation.

Property Type Example Required Notes
screen string "journey_map" yes
from_chapter_number number 1 yes 1-based; the user's active chapter at tap time
to_chapter_number number 3 yes 1-based; the chapter they picked
to_chapter_id string "1776754765374x…" yes
to_chapter_title string "Slowing the Reaction" yes
dynamic boolean false yes target chapter's kind

Source: JourneyPath.jsx


Insight

Insight Flow Started

Fires when an insight generation flow begins.

Property Type Example Required
type string "learn" / "activity" / "journey" yes
learn_type_id string "lt_01" when type=learn
activity_id string "act_01" when type=activity
chapter number 0 when type=journey
replay boolean false when type=journey
dynamic boolean false when type=journey

Source: InsightFlow.jsx · InsightStep.jsx

Insight Question Answered

Fires per insight question response.

Property Type Example Required
answer string "yes" / "no" / custom text yes

Source: InsightFlow.jsx · InsightStep.jsx

Insight Generated

Fires when insight generation succeeds. For type: 'learn' it fires when generation is triggered (legacy JS2B path). For type: 'journey' (BNS-1119, HTTP path) it fires when the synchronous generate_journey_insight call returns the insight, with the round-trip duration.

Property Type Example Required
type string "learn" / "journey" yes
learn_type_id string "lt_01" when type=learn
chapter number 0 when type=journey
dynamic boolean false when type=journey
duration_ms number 3100 when type=journey

Source: InsightFlow.jsx (learn) · InsightStep.jsx (journey)

Insight Generation Failed

Fires when the journey insight HTTP generation fails after its transport retry (BNS-1119 — replaces Insight Generation Timed Out for the journey surface). The user sees the Retry UI.

Property Type Example Required
type string "journey" yes
chapter number 0 yes
dynamic boolean false yes
reason string "timeout" / "http_5xx" / "http_4xx" / "network" yes
duration_ms number 51000 yes

Source: InsightStep.jsx

Insight Generation Manual Retry

Fires when the user taps Retry on the journey insight failure screen.

Property Type Example Required
chapter number 0 yes
dynamic boolean false yes
mode string "generate" / "replay" yes

Source: InsightStep.jsx

Insight Replay Loaded

Fires when opening a completed chapter's insight (replay) and the get_journey_insight read returns the stored insight.

Property Type Example Required
chapter number 0 yes
dynamic boolean false yes
duration_ms number 800 yes

Source: InsightStep.jsx

Insight Replay Load Failed

Fires when opening a completed chapter's insight (replay) and the get_journey_insight read fails or finds no stored insight.

Property Type Example Required
chapter number 0 yes
dynamic boolean false yes
reason string "not_found" / "timeout" / "http_5xx" / "http_4xx" / "network" yes

Source: InsightStep.jsx

Insight Generation Timed Out

Fires when insight generation exceeds the timeout. Journey surface removed in BNS-1119 (replaced by Insight Generation Failed); Activity surface removed in BNS-1088 slice 2 (replaced by Activity Create Failed — the 90s poll timer it fired from is deleted). From that release this event only fires for the Tab/Learn surfaces.

Property Type Example Required
type string "learn" (was also "activity" before BNS-1088 slice 2) yes
learn_type_id string "lt_01" when type=learn (always sent, may be null)
activity_id string RETIRED (was: when type=activity). The activity surface moved to Activity Create Failed in BNS-1088 slice 2; type is now learn-only, so this property no longer fires.

Source: InsightFlow.jsx (learn/activity)

Insight Playback Completed

Fires when TTS playback finishes and user taps Continue.

Property Type Example Required
(none)

Source: InsightFlow.jsx · InsightStep.jsx

Insight Playback Skipped

Fires when user taps Continue before TTS playback finishes.

Property Type Example Required
(none)

Source: InsightFlow.jsx · InsightStep.jsx

Insight Flow Closed

Fires when user exits the insight flow without completing playback.

Property Type Example Required
(none)

Source: InsightFlow.jsx


Simulator

Element Clickedsimulator / toggle / input_mode / mode (voice/text) / SimulatorSection. BNS-1071: the voice/text landing toggle persists the user's choice as their default next session (one shared preference across Ask, Practice and the Simulator). Fired only on an explicit toggle — never on the forced voice→text fallback, a "Try in simulator" seed, or a re-tap of the already-selected pill.

Mid-session breadcrumbs & drop position (BNS-1142). The session-boundary events say THAT a user dropped, not WHERE — and a user who backgrounds/kills the app emits nothing at all, so drop position is recovered from the last breadcrumb seen before the session went silent (Simulator Clarification Asked/Submitted, Simulator Turn Completed). All simulator events carry seconds_since_start (whole seconds since the session's Simulator Session Started). Recovery/exit events additionally carry the shared position props:

Simulator Session Started

Fires when a simulator session begins (freeform or journey practice).

Property Type Example Required
mode string "journey" / "freeform" yes
input_mode string "voice" / "text" yes
seconds_since_start number 0 (always — funnel anchor) yes
source string "ask" only when the freeform session was pre-seeded by Ask Dr Leo's "Try in simulator" (omitted for plain Tab sessions) — enables the Ask → practice funnel
chapter_id string "ch_01" when mode=journey
dynamic boolean false when mode=journey

Source: SimulatorSection.jsx

Simulator Clarification Asked

Fires each time Dr Leo finishes delivering a clarifying question in Stage 1 (dynamic chapters and freeform/Tab sessions only — predefined chapters have no clarification phase). The count includes Leo's opening utterance, so question_number reads as "coach turn N". This is the key drop-localization breadcrumb for the dynamic-chapter mid-session churn analysis: Started with no Clarification Asked = died before Leo ever spoke (setup/mic/connection); last event = Asked {n} with no answer = quit while question n was on screen.

Property Type Example Required
question_number number 2 yes
seconds_since_start number 41 yes
mode string "journey" / "freeform" yes
input_mode string "voice" / "text" yes
source string "ask" when seeded from Ask "Try in simulator"
chapter_id string "ch_01" when mode=journey
dynamic boolean true when mode=journey (in practice always true — predefined never fires it)

Source: SimulatorSession.jsx (bookkeeping in simulatorAnalytics.js)

Simulator Clarification Submitted

Fires when the user's answer to a clarifying question is finalized (voice transcription final or text send). question_number is the answer's own counter (answer N), pairing with Simulator Clarification Asked for engagement depth.

Same properties as Simulator Clarification Asked.

Source: SimulatorSession.jsx

Simulator Turn Completed

Fires each time a Stage 2 conversation turn finishes (partner utterance finalized / user reply transcribed or sent). Partner turns are odd (1, 3), user turns even (2, 4) — partner speaks first, two exchanges per session.

Property Type Example Required
turn_number number 3 yes
speaker string "partner" / "user" yes
seconds_since_start number 95 yes
mode string "journey" / "freeform" yes
input_mode string "voice" / "text" yes
source string "ask" when seeded from Ask "Try in simulator"
chapter_id string "ch_01" when mode=journey
dynamic boolean false when mode=journey

Source: SimulatorSession.jsx

Simulator Session Completed

Fires when AI evaluation is received and results are shown.

Property Type Example Required
score number 4 yes
skill_level string "Finding Your Groove" yes
mode string "journey" / "freeform" yes
input_mode string "voice" / "text" yes
seconds_since_start number 142 yes
clarifying_questions number 3 (0 for predefined) yes
source string "ask" when seeded from Ask "Try in simulator" (see Simulator Session Started) — the completion end of the Ask → practice funnel
chapter_id string "ch_01" when mode=journey
dynamic boolean false when mode=journey

Source: SimulatorSection.jsx

Simulator Session Closed

Fires when user manually ends a session (via close dialog). Only covers in-app quits — a user who backgrounds/kills the app emits nothing; recover those drops from the last breadcrumb (see section note).

Property Type Example Required
mode string "journey" / "freeform" yes
input_mode string "voice" / "text" yes
source string "ask" when seeded from Ask "Try in simulator" (see Simulator Session Started)
chapter_id string "ch_01" when mode=journey
dynamic boolean false when mode=journey
position props phase / clarifying_questions / clarifying_answers / turn_number / seconds_since_start (see section note) yes

Source: SimulatorSection.jsx

Simulator Session Retried

Fires when user retries the session from the results screen.

Property Type Example Required
mode string "journey" / "freeform" yes
input_mode string "voice" / "text" yes
seconds_since_start number 160 (session clock keeps running across the retry) yes
chapter_id string "ch_01" when mode=journey
dynamic boolean false when mode=journey

Source: SimulatorSection.jsx

Simulator Session Error

Fires on unrecoverable session error. For the JSON-extraction boundaries (Stage 1, Stage 3), this only fires after parse retries are exhausted — see Simulator Issue Retry / Simulator Eval Retry below for the per-attempt signal.

Property Type Example Required
code string "TIMEOUT" / "EVAL_PARSE_FAILED" / "ISSUE_PARSE_FAILED" / "UNKNOWN" yes
message string "Connection lost" yes
mode string "journey" / "freeform" yes
input_mode string "voice" / "text" yes
chapter_id string "ch_01" when mode=journey
dynamic boolean false when mode=journey
position props phase / clarifying_questions / clarifying_answers / turn_number / seconds_since_start (see section note) yes

Source: SimulatorSection.jsx

Simulator Mic Permission Denied

Fires when the mic-permission dialog (MicPermissionDialogs) is shown — either a live PERMISSION_DENIED from the realtime session or the predefined-chapter Start-practice preflight detecting an already-denied mic. Negative-signal counterpart to Simulator Mic Permission Granted; separates mic-friction drops from clarifying-question churn.

Property Type Example Required
mode string "journey" / "freeform" yes
input_mode string "voice" yes
chapter_id / dynamic / source as on Simulator Session Started conditional
position props phase ("intro" on the predefined preflight) etc. (see section note) yes

Source: SimulatorSession.jsx

Simulator Mic Permission Granted

Fires when the user returns from Settings and the visibility probe confirms mic access; the session silently restarts at the stage it was in. A Stage-1 restart tears down the coach session and re-asks from question 1 (clarification counters reset — see section note); a Stage-2 restart re-enters with a fresh conversation (turn numbering resets).

Property Type Example Required
mode string "journey" / "freeform" yes
input_mode string "voice" (still voice at fire time — the grant keeps voice) yes
chapter_id / dynamic as on Simulator Session Started when mode=journey
position props see section note yes

Source: SimulatorSection.jsx

Simulator Switch To Text

Fires when the user picks the text fallback from the mic-trouble dialogs; the session restarts stage-aware on the WebSocket transport (same Stage-1/Stage-2 reset semantics as Simulator Mic Permission Granted above).

Property Type Example Required
mode string "journey" / "freeform" yes
input_mode string "voice" (fires before the flip to text) yes
chapter_id / dynamic as on Simulator Session Started when mode=journey
position props see section note yes

Source: SimulatorSection.jsx

Simulator Eval Retry

Fires per attempt when the Stage 3 evaluation JSON fails to parse and we re-fire the silent response.create on the same session. After today's tool-calling change this should be near-zero in production — the model is structurally forced into schema-valid JSON. Kept as belt-and-suspenders.

Property Type Example Required
attempt number 1 yes
max_retries number 2 yes
raw_length number 412 yes
raw_text_hash string "a3f1b29c" (FNV-1a fingerprint of raw response, no PII) yes
mode string "journey" / "freeform" yes
chapter_id string "ch_01" when mode=journey
dynamic boolean false when mode=journey

Source: SimulatorSection.jsx

Simulator Eval Retry Recovered

Fires once when a Stage 3 eval succeeds after at least one retry. Lets analytics distinguish first-try clean sessions from "drift caught and recovered" sessions, both of which otherwise look identical at Simulator Session Completed.

Property Type Example Required
attempts_total number 2 (1 retry + final success = 2) yes
mode string "journey" / "freeform" yes
chapter_id string "ch_01" when mode=journey
dynamic boolean false when mode=journey

Source: SimulatorSection.jsx

Simulator Issue Retry

Stage 1 (issue-extraction) counterpart to Simulator Eval Retry. Same shape, same near-zero post-tool-call expectation.

Property Type Example Required
attempt number 1 yes
max_retries number 2 yes
raw_length number 188 yes
raw_text_hash string "7e22c014" yes
mode string "journey" / "freeform" yes
chapter_id string "ch_01" when mode=journey
dynamic boolean false when mode=journey

Source: SimulatorSection.jsx

Simulator Issue Retry Recovered

Stage 1 counterpart to Simulator Eval Retry Recovered.

Property Type Example Required
attempts_total number 2 (1 retry + final success = 2) yes
mode string "journey" / "freeform" yes
chapter_id string "ch_01" when mode=journey
dynamic boolean false when mode=journey

Source: SimulatorSection.jsx

Coins Deducted

Fires when coins are spent — to unlock stage 2 of a freeform simulator session, or on the first paid turn of an Ask Dr Leo session.

Property Type Example Required
amount number 8 yes
context string "simulator" | "ask" yes
via string "journey_map" ask only (BNS-1067)

via (journey_map | step_end | dr_leo_tab | deep_link) is sent on the context: 'ask' deduct so paywall attribution separates the ask entry points; the context: 'simulator' deduct does not carry it.

Source: SimulatorSection.jsx (simulator) · AskSession.jsx (ask)

Coins Gifted

Fires when the user receives a one-time coin gift (e.g. from the "Not Enough Coins" dialog).

Property Type Example Required
amount number 3 yes
previousBalance number 0 yes

Source: NotEnoughCoinsDialog.jsx


Activities

Activity Create Started

Fires when user initiates creating a new activity.

Property Type Example Required
act_type_id string "1712x459…" (Bubble unique id) yes
act_type_name string "Funky Date" no (absent if the type list hasn't loaded yet, e.g. deep-link cold start)

Source: ActivityView.jsx

Activity Created

Fires when generate_activity_insight returns an activity — the moment the user is charged. Pairs with Activity Create Started to give the fun-zone creation funnel: every Started resolves into exactly one Activity Created or one Activity Create Failed (unless the user leaves mid-flight, which fires neither — the client bails before the billable call). Added in BNS-1088 slice 2.

Property Type Example Required
act_type_id string "1710515984265x220966944209895420" (Bubble unique id) yes
act_type_name string "Funky date" no (absent if the type list hasn't loaded yet)
duration_ms number 3120 (typically ~3000-5000) yes

Source: ActivityView.jsx

Activity Create Failed

Fires when the creation call fails, or when the server rejects it for balance. Replaces the fun-zone use of Insight Generation Timed Out (the 90s poll timer that this migration deleted). Added in BNS-1088 slice 2.

Property Type Example Required
act_type_id string "1710515984265x220966944209895420" yes
act_type_name string "Funky date" no
reason string "http_5xx" | "http_4xx" | "timeout" | "network" | "insufficient_coins" | "empty_content" yes
duration_ms number 25010 yes

insufficient_coins is the server's balance gate and means no charge and no activity — the client already gates on SPEND.funZoneAct before opening the view, so it only fires when the client's balance was stale. empty_content means the workflow charged and returned ok but the act parsed to zero steps — the (already applied) balance stands, but the unusable activity is not saved to history and the user gets the error/retry screen. The other four come from the shared failureReason enum (src/utils/failureReason.js), identical across every migrated LLM surface so the funnels stay comparable.

Source: ActivityView.jsx

Activity View Completed

Fires when user finishes viewing/listening to an activity.

Property Type Example Required
activity_id string "1712x460…" (Bubble unique id) yes
title string "A picnic under the stars" no (absent if the history record has no title)
replay boolean false yes

Source: ActivityView.jsx

Activity View Closed

Fires when user closes an activity without completing it.

Property Type Example Required
(none)

Source: ActivityView.jsx


Daily Question

Daily Question Viewed

Fires when the daily question appears on screen.

Property Type Example Required
question_id string "dq_20260318" yes
category string "intimacy" yes

Source: DailyQuestion.jsx

Daily Question Voted

Fires when user selects an answer.

Property Type Example Required
question_id string "dq_20260318" yes
index number 1 yes

Source: DailyQuestion.jsx


Partner

Partner Connected

Fires when a partner is connected for the first time (partner_connected transitions false → true).

Property Type Example Required
(none)

Source: UserContext.jsx


Payments

All payment events run on the React side via the Natively RevenueCat bridge and a direct RC REST read for entitlement state — Bubble is uninvolved.

Removed: Paywall Viewed / Paywall Dismissed — these only fired from RC's hosted paywall (EntitlementContext.showPaywall()), which has been removed. The app uses a single custom paywall (PlansScreen), tracked via Plans Screen Shown (see below).

Purchase Started

Fires when buy(packageId) is called.

Purchase Completed

Fires when the purchasePackage callback returns status: 'SUCCESS'.

Purchase Failed

Fires when the purchasePackage callback returns a non-SUCCESS status (or never fires — a null bridge response after the 10s timeout).

Genuine failures (not user cancellations) also emit a log.error → Sentry Issue; cancellations emit log.warn (queryable log, no Issue).

Purchase Pending

Fires when buy() can't confirm the outcome (callback returned no usable signal and the entitlement hasn't flipped within buy()'s window) — the purchase may still have succeeded. The recovery watcher keeps polling the entitlement.

Purchase Confirmed Late

Fires when the recovery watcher confirms a pending purchase via the RevenueCat entitlement flip (after buy() already returned). Routes into the normal celebration. This is a success that the old code would have logged as Purchase Failed.

Purchase Unconfirmed

Fires once when a pending purchase is still unconfirmed after the active confirm window (~5 min) — the Refresh/Restore give-up affordance is shown.

Purchase Restore Tapped

Fires when the user taps Restore Purchases.

Purchase Restore Completed

Fires after the post-restore entitlement refresh.

Plans screen events

Custom in-app paywall at PlansScreen.jsx. Pushed from Profile → Subscription.

Purchase Started/Purchase Completed/Purchase Failed/Purchase Restore Tapped/Purchase Restore Completed already fire from EntitlementContext.buy/restore; the plans screen passes source: 'plans_screen' so the funnel can be segmented by entry point.

Entitlement Changed

Fires whenever isSubscribed flips on EntitlementContext (renewal, expiry, cross-device sync).

Also: is_subscribed is set as a Mixpanel People property every time EntitlementContext resolves, so the latest known state lives on the user profile for funnel segmentation.

Cohort coin-gate events

Fired by the shared useCoinGate hook (src/hooks/useCoinGate.js), which funnels every coin-spending action (chapter start, simulator session, activity creation, etc.) through one cohort-aware path. Replaces the legacy local if (coins < N) checks scattered across feature files.

Cohort ('A' | 'B' | 'C') is not encoded on these events — it lives on the Mixpanel user profile as the payment_variant People property (the effective value incl. admin overrides, set by <PaymentVariantSeeder>). Segment/break down any coin-gate event by payment_variant for cohort funnels. (The raw Statsig bucket is intentionally not mirrored to Mixpanel — Statsig keeps the exposure for experiment analysis.)

What these events add instead is gate_outcomewhich branch the gate took, orthogonal to cohort:

Premium-user surfaces

Subscription lifecycle screens

People + super properties

subscription_type is pushed as a Mixpanel People property by EntitlementContext on every entitlement refresh — RevenueCat is the source of truth (yearly/quarterly/monthly, null when not subscribed), set alongside is_subscribed. It is not sourced from Bubble/UserContext (that field was never reliably pushed).

The cohort variant is carried by a single People (user-profile) property:

Use payment_variant for all cohort-segmented funnel analysis — break events down by it in reports; it reflects the experience the user actually got. The raw Statsig assignment is not mirrored to Mixpanel as experiment_payments (the payments experiment is suppressed in attachMixpanelMirror, src/utils/experiments.js, to keep one cohort property); use Statsig's own exposure data for randomized (intent-to-treat) experiment analysis.


System

Report Submitted

Fires when a bug report is sent (via clipboard copy or Slack relay).

Property Type Example Required
platform string "ios" yes
has_logs boolean true yes
log_count number 42 yes
delivery string "slack" / "clipboard" yes

Source: logCapture.js

Profile Photo Updated

Fires when user crops and saves a new profile photo.

Property Type Example Required
screen string "profile" yes

Source: MainTabs.jsx

FTUE Walkthrough Completed

Fires when the user finishes all steps of the first-time user experience walkthrough.

Property Type Example Required
steps_seen number 5 yes
is_replay boolean true yes

Source: DrLeoWalkthrough.jsx

TTS Error Occurred

Fires when TTS streaming fails (network error, proxy down, etc.). Centralized in useTTS hook — all TTS consumers get tracking automatically.

Property Type Example Required
backend string "mse" / "pcm" yes
error_message string "Failed to fetch" yes
screen string "insight_playback" / "activity_view" / "simulator_results" yes (via caller context)

Source: useTTS.js

Support Unread Badge Shown

Fires when the Intercom unread badge appears (count rises from 0 to n) — a red dot on the profile nav tab plus a count pill on the Profile → Support "Share feedback" row. Fires once per appearance (rising edge), not on every count change; the badge clears itself when the user opens the messenger (Intercom marks the conversation read). Only possible after Intercom boots (logged-in, JWT delivered).

Property Type Example Required
count number 2 yes

Source: MainTabs.jsx (count from useIntercomUnread / utils/intercom.js)

Push Notification Prompt

Push Prompt Shown

Fires when the pre-permission dialog appears.

Property Type Example Required
trigger string "post_signup" / "session_start" / "reinstall" / "act_reminder" yes
mode string "first_time" / "settings_redirect" no (only present for act_reminder trigger)

Source: MainTabs.jsx, useNotificationGate.jsx

Push Prompt Accepted

Fires when user taps "Enable notifications" in the pre-permission dialog. Tracked BEFORE the native OS prompt.

Property Type Example Required
trigger string "post_signup" / "session_start" / "reinstall" / "act_reminder" yes
mode string "first_time" / "settings_redirect" no (only present for act_reminder trigger)

Source: MainTabs.jsx, useNotificationGate.jsx

Push Prompt Dismissed

Fires when user taps "Not now" in the pre-permission dialog.

Property Type Example Required
trigger string "post_signup" / "session_start" / "reinstall" / "act_reminder" yes

Source: MainTabs.jsx, useNotificationGate.jsx

Push Settings Redirect Shown

Fires when the "previously denied" dialog appears in Profile.

Source: MainTabs.jsx

Push Settings Redirect Accepted

Fires when user taps "Open Settings" from the profile notification dialog.

Source: MainTabs.jsx

Store Reviews (rating prompt)

The custom Like/Dislike pre-prompt that gates the native OS store-review sheet. Screen: rating_prompt. See the plan: docs/superpowers/plans/2026-06-29-store-reviews-rating-prompt.md.

Rating Prompt Shown

Fires when the pre-prompt is shown (after the gate passes and the home surface settled).

Property Type Example Required
prompt_ordinal number 1 (first) or 2 (second)
trigger text chapters | single_steps | sessions | both (both only on the 2nd prompt when both engagement deltas fire)
chapters_finished number 2
single_step_count number 3
session_count number 4

Source: useRatingPrompt.js

Native Store Review Requested

Fires when the user taps "Rate Bonds in the store" and the native review sheet is invoked. Note: fire-and-forget — the OS decides whether to display it, and a submitted review is not detectable.

Property Type Example Required
prompt_ordinal number 1

Source: useRatingPrompt.jsappReview.js

Rating Feedback Opened

Fires when the user taps "Not yet" → "Please tell us how we can do better" and the feedback flow opens.

Property Type Example Required
prompt_ordinal number 1

Source: useRatingPrompt.jsopenFeedback

Rating Prompt Dismissed

Fires when the pre-prompt closes without a response — either the user taps the X / backdrop (reason: 'user'), or the prompt auto-hides because the user left the settled home surface while it was visible (reason: 'left_surface', BNS-1115). Every Rating Prompt Shown therefore resolves into a response event or exactly one Dismissed (modulo app kills).

Property Type Example Required
prompt_ordinal number 1
reason text user | left_surface — X/backdrop vs auto-hide on leaving home
from_step text ask | loved | notYet — which step the X was tapped on (isolates the "tapped 👍 but didn't rate" cohort) only when reason: 'user'

Source: useRatingPrompt.js


5. Element Clicked Reference

All Element Clicked events. Properties always include screen, element_type, and element. Extra context properties listed in the last column.

Welcome & Auth

Screen Type Element Extra Source
welcome button lets_go WelcomeScreen
welcome button sign_in WelcomeScreen
signin button back SignInScreen
signin button go_to_signup SignInScreen
signin link continue_with_email SignInScreen
signin link forgot_password SignInScreen
signup button back SignUpScreen
signup button go_to_signin SignUpScreen
signup link continue_with_email SignUpScreen
signup link go_to_signin SignUpScreen
env_switch_prompt button switch_here / go_home App.jsx (EnvSwitchPrompt, staging QA only)

FTUE Walkthrough

Screen Type Element Extra Source
ftue_walkthrough button next step DrLeoWalkthrough
ftue_walkthrough button skip step DrLeoWalkthrough
ftue_walkthrough button back step DrLeoWalkthrough
Screen Type Element Extra Source
main tab journey / dr-leo / fun / profile MainTabs
{activeTab} button back MainTabs (stack)

Dr Leo

Screen Type Element Extra Source
dr_leo_choice button ask_anything DrLeoChoiceScreen
dr_leo_choice button conversation_simulator DrLeoChoiceScreen
dr_leo_choice link history_link DrLeoChoiceScreen
ask_landing button ask_start mode AskLanding
ask_landing toggle input_mode mode (voice/text) AskFlow (BNS-1071 — persists the choice as the default next session)
ask_session chip suggested_question AskSession (tap a suggested-question chip)
ask_answer button act_upon_it / try_in_simulator / follow_up_question / ask_close / ask_share / ask_play / ask_pause / ask_replay / ask_mute muted (on ask_mute) AskAnswer
ask_act button act_accept / act_decline / act_back / retry_generation via (all but retry_generation) AskActFlow
act_acceptance button ok_open_drawer / choose_moment source (ask) AskActFlow — both CTAs open the same moment drawer; neither commits
act_acceptance button moment_today / moment_tomorrow / moment_in_two_days / moment_skip source (ask), when AskActFlow — the commit resolve point. moment_skip sends when: 'ok'
act_acceptance sheet moment_dismissed source (ask) AskActFlow — backdrop/drag dismiss. Not a commit, unlike moment_skip
act_acceptance button acceptance_back source (ask), via AskActFlow — the header back control while on the acceptance screen. Attributed here, NOT to ask_act, so the proposal funnel isn't polluted and the acceptance funnel has an abandonment leg
how_did_it_go button tried_meaningful / tried_uncomfortable / hesitated / no_moment / other source (ask) AskActFlow — the five outcome pills. other advances to the text screen and reports on submit, not here
other_input button submit_other source (ask) AskActFlow — submits the free-text reflection AND the other outcome together
other_input button mic_start / mic_stop source (ask), via AskActFlow — the mic toggle on the reflection screen. Recording auto-starts on entry, so mic_stop is the common first press. Journey acts fire the same pair scoped by chapter
other_input button other_back source (ask), via AskActFlow — backing out of the free-text screen without submitting. Act Outcome Reported { outcome: 'other' } already fired on the pill but the server write is deferred to submit, so this is the leg that resolves the other count into submitted + abandoned
act_not_done button set_new_reminder / stop_notifications source (ask) AskActFlow
act_not_done button reschedule_today / reschedule_tomorrow / reschedule_in_two_days source (ask) AskActFlow — the moment drawer reopened in reschedule mode. Skip is a no-op here (there is no "reschedule to no time")
act_not_done sheet moment_dismissed source (ask) AskActFlow — dismissing the reschedule drawer changes nothing
act_check_in button check_in_done source (ask), via AskActFlow — the shared exit from the performed / thank-you / not-done screens
act_check_in button check_in_back source (ask), via AskActFlow — the shell header's back/close control on the outcome-pill screen, fired alongside Act Check-In Dismissed. Every phase past the pills ships its own header (and its own check_in_done), so the shell header is not rendered there at all
journey_map button ask_hand_reveal chapter_id AskHand on the journey map — intro (first-exposure) tap 1 only, reveals the "Tap to ask" label. Also settles the intro animation for the rest of the session. A settled hand skips this and opens on one tap (BNS-1147).
journey_map button ask_hand_open step, chapter_id AskHand on the journey map — opens the Ask intro screen (tap 2 on a first-exposure hand, or tap 1 on a settled hand — BNS-1147). Between chapters (the paused / generic open, where the ask isn't tied to a chapter) step and chapter_id are OMITTED — so the event isn't falsely attributed to a chapter.
learn_video / insight_playback / simulator_results button ask_hand_reveal step, chapter_id AskHand at the end of a chapter step — intro (first-exposure) tap 1 only (a settled step-end hand opens on one tap — BNS-1147).
learn_video / insight_playback / simulator_results button ask_hand_open step, chapter_id AskHand at the end of a chapter step — opens the Ask intro screen (tap 2 first-exposure, or tap 1 settled).

Ask hand (BNS-1067 — Ask Dr Leo PART 2)

The floating Ask affordance, on the journey map and at the end of the Learn, Insight and Practice steps. It is a two-tap control on first exposure only: the intro (unseen) hand reveals the "Tap to ask" label on tap 1 and opens on tap 2, whereas a settled hand (already exposed this session) opens on a single tap and never re-shows the explanation (BNS-1147). So the reveal leg is present only for the first-exposure funnel:

A user who sees the hand and never opens it (ignores it, or reveals the label and walks away) fires Ask Hand Dismissed, so Ask Hand Shown fully resolves into opens + walk-aways — the negative branch is an explicit event, not just a derived gap.

Event Properties Fires when Source
Ask Hand Shown surface (journey_map / step_end), phase (intro / settled), step, chapter_id The hand mounts on a surface. phase: 'intro' means the user is seeing the first-run animation for this session; settled means they've already tapped it this session. AskHand via JourneyScreen (map) / StepEndAskHand (step ends)
Ask Hand Dismissed surface (journey_map / step_end), reason (navigated_away / step_advanced) The user leaves without ever opening Ask. step_end (step_advanced): fires once when the step-end hand unmounts un-opened (Continue/Done or leaving the step). journey_map (navigated_away): fires once per map visit — on leaving the map (tab switch / opening a chapter) if the hand was shown that visit but never opened. Scrolling between chapters is engagement, not a dismissal, so the map hand does NOT fire it per chapter even though Ask Hand Shown re-fires per chapter on scroll. StepEndAskHand (via AskHand unmount) / JourneyScreen (map-visit level)

Property notes:

No Act step end — by design. The PRD excludes it ("we have critical actions we don't want to compete with"), compensating with the map hand afterwards. So surface: 'step_end' will never carry step: 'act'; an act-related ask arrives as surface: 'journey_map', step: 'act'. A funnel that expects four step ends is mis-specified.

One intro per SESSION, not per surface and not per chapter (BNS-1147, product 2026-07-27 — this superseded the per-chapter scope shipped for BNS-1067). The "already tapped" flag lives in sessionStorage and is shared across all four surfaces, so a reveal at the end of Learn makes the map hand — and every later chapter's hand — report phase: 'settled'. Expect at most one phase: 'intro' per app session, and a fresh one after a relaunch.

Board warning: any funnel or breakdown that assumed one intro per chapter will see the intro count drop sharply from this release, with the difference landing in settled. The property values are unchanged; only how often each occurs is.

via on the Ask events (BNS-1067)

Every Ask domain event below carries via — where the ask was opened from:

via Means
dr_leo_tab The Dr Leo tab's "How can I help?" choice screen (the original entry).
journey_map The Ask hand on the journey map. Carries chapter + step context.
step_end The Ask hand at the end of Learn / Insight / Practice. Carries chapter + step context.
deep_link A bare ?dl=/dr-leo/ask link — seeds the Ask screen with no context.

Use via, not source. source is already taken on the Ask↔simulator funnel and means the opposite direction (source: "ask" on Simulator Session Started marks Ask → simulator outbound, see the Simulator section). Reusing it for inbound entry would corrupt that funnel.

via is on all ask events including the negative ones (Ask Session Error, Ask Voice Fallback, Ask Session Abandoned) — otherwise a contextual ask's failure paths would be unattributable and the per-entry funnel couldn't resolve.

An ask reached by tapping through the choice screen reports dr_leo_tab even if that tab was originally opened from the journey: walking back to the choice screen clears the context, so the ask genuinely is no longer about that chapter.

Ask Dr Leo (domain events)

Event Properties Fires when Source
Ask Session Started mode (text/voice), is_follow_up, via A new ask session connects AskSession.jsx
Ask Answer Received mode, clarifying_turns, via Leo delivers the marked answer AskSession.jsx
Ask Act Generated via, duration_ms generate_ask_act returned an act. Replay-reuse, NOT strict idempotency: normal re-entry (decline then tap again) finds the stored completed Act (the workflow gates on non-empty text, so an act blanked by a failed generation is not replayed) and returns it without billing, so the event fires again with a short duration_ms (reused <1.5s vs fresh ≈4-5s). It is a search-then-create workflow with no uniqueness constraint or lock, so concurrent retries (a transport timeout racing the original) can still create a second Act and bill a second generation — the design is duplicate-tolerant, not duplicate-proof. Don't treat repeat events as guaranteed-free. AskActFlow.jsx
Ask Act Generation Failed via, reason (http_5xx | http_4xx | timeout | network — the shared utils/failureReason enum, same as every other LLM surface), duration_ms Generation failed and the retry screen is showing. Pairs with Element Clicked { element: 'retry_generation' }. AskActFlow.jsx
Ask Act Screen Viewed via The act proposal actually painted (NOT the loader). Funnel head for accept-vs-decline. AskActFlow.jsx
Ask Act Declined via User tapped "I'm not ready yet". The negative branch that lets Ask Act Screen Viewed resolve fully into accept + decline + back. AskActFlow.jsx
Act Acceptance Screen Viewed source (ask), via The acceptance screen painted after "I'll give it a try". Separates "accepted the act" from "picked a moment" — the drop-off between the two is otherwise invisible. AskActFlow.jsx
Act Commit Failed source (ask), when, reason (shared utils/failureReason enum) act_commit did not return 200. Compensating event for the optimistic-looking flow: the user stays on the acceptance screen, an inline notice appears, and the CTAs act as a live retry. AskActFlow.jsx
Act Acceptance Abandoned source (ask), via User left the acceptance screen via the header back control without picking a moment or skipping. The third leg that makes Act Acceptance Screen Viewed fully resolvable into committed + failed + abandoned. AskActFlow.jsx
Act Check-In Viewed source (ask), via (deep_link | app_open) The "How did it go?" screen painted. Funnel head for the check-in leg. AskActFlow.jsx
Act Check-In Dismissed source (ask), via User left "How did it go?" via the header back control without picking an outcome — the check-in funnel's negative signal, so Act Check-In Viewed = Outcome Reported + Dismissed (+ rare app kills). Fires only from the outcome-pill screen: past it the loop is already closed server-side, so a back-out there is a plain exit and reports the check_in_back click alone. Journey acts fire the same event with chapter. AskActFlow.jsx (ask) / ActStep.jsx (journey)
Act Check-In Surface Aborted reason (tab_changed), dropped, tab The app-open queue was built but abandoned before its entries surfaced — the user left the journey tab during the get_due_acts round-trip or the deferred push. Nothing is stamped, so every dropped entry is still due on the next launch. This is what makes Act Check-In Surfaced resolvable: count - dropped = Act Check-In Viewed{via: app_open}. deferred is NOT in this equation — count is already the post-cap queue size, and the deferred entries sit outside it (subtracting them undercounts the check-ins that were actually seen). Without this event, aborted queues read as lost events. MainTabs.jsx
Act Check-In Prefetch Failed source (journey), via (app_open), reason A journey act was about to surface on app open and its chapter body could not be prefetched. The check-in still shows (ChapterFlow falls back to its own loader), so without this event the symptom is a check-in that renders slowly or blank with no way to count it. Ask acts carry their text inline and never hit this path, hence source: journey only. MainTabs.jsx
Act Check-In Unavailable source (ask), via (deep_link | app_open), reason (fetch_failed, else absent) via: 'app_open' is always reason: 'fetch_failed' — the app-open gate's get_due_acts call failed and the queue degraded to journey-only; it is what separates "nobody had a due ask act" from "the endpoint was down", which Act Check-In Surfaced alone cannot. via: 'deep_link': a check-up push was tapped but the act is no longer due — already reported, or the reminder was cancelled — or the get_due_acts lookup failed. The user lands on the Dr Leo choice screen. Without this, a tapped push that goes nowhere is invisible; it is the denominator for "did the check-up actually deliver a check-in?". DrLeoSection.jsx
Act Outcome Reported source (ask), outcome, via Fires for ALL five pills including other, so the funnel covers every option the user actually picked — even though other's server write is deferred to submit. AskActFlow.jsx
Act Outcome Report Failed source (ask), outcome, reason, via act_report_outcome did not return 200. The screen has already advanced (reporting is a record, not a gate), but the act stays due and WILL resurface — so this is not cosmetic, it's the signal that a reported outcome was lost. AskActFlow.jsx
Act Other Submitted source (ask), via, length, input_mode (text/voice) Free-text/voice reflection submitted. length only (never the text) — the reflection is personal content and does not belong in analytics; the journey twin logs response raw under the BNS-1129 exception, the ask side deliberately does not. input_mode is text if the user typed at any point, else voice. AskActFlow.jsx
Act Reminder Rescheduled source (ask), when, via User picked a new moment from the not-done screen. Journey acts fire the same event with chapter. AskActFlow.jsx
Act Reminder Reschedule Failed source (ask), when, reason, via act_update_reminder did not return 200 on the reschedule path (the cancel path fires Act Notifications Stop Failed). Compensating event: without it a failed reschedule looks identical to a successful one — the drawer closes either way. The user stays on the not-done screen with an inline notice, so the moment CTAs act as a live retry. AskActFlow.jsx
Act Notifications Stopped source (ask), via User chose to stop check-up notifications for this act, and the cancel landed. AskActFlow.jsx
Act Notifications Stop Failed source (ask), reason, via The stop-notifications call did not return 200. Its own event rather than a when: 'cancel' variant of Act Reminder Reschedule Failed — overloading that one leaves Act Notifications Stopped with no resolvable negative branch unless every query remembers to split on when. The flow now stays open with an inline notice: the push is still armed server-side, so closing would tell the user notifications are off when they are not. AskActFlow.jsx
Ask Session Completed mode, via Same beat as Answer Received (answer landed) AskSession.jsx
Ask Session Error error_code, refunded, via Realtime/transport error AskSession.jsx
Ask Session Abandoned mode, is_follow_up, via, phase (connecting/live/composing) User closes the live session via the header X before an answer lands. The answered / error phases have their own terminal events (Ask Answer Received / Ask Session Error) and do not fire this. Closes the previously-invisible abandonment leg (started − answered − errored was only an implied count). AskSession.jsx
Ask Voice Fallback reason (mic_error/permission_denied), is_follow_up, via A voice session hits a mic error or permission denial and the user taps Switch to Text. Measures the voice→text fallback rate (relevant while Text input mode is new). AskSession.jsx
Coins Deducted amount, context, via (ask only) Shared event across features. Canonical context values: 'ask' (amount 6, AskSession) · 'simulator' (amount 8, SimulatorSection). First question/turn of a paid ask fires context: 'ask' with via (journey_map | step_end | dr_leo_tab | deep_link, BNS-1067) so paywall attribution separates the ask entry points. NOT fired for follow-ups or Try-in-simulator — both are free (product decision 2026-06-18), so try_in_simulator / follow_up_question clicks log Element Clicked but no Coins Deducted (the simulator's context: 'simulator' deduct is also skipped on the seeded path). AskSession.jsx · SimulatorSection.jsx

Act (domain events)

Closing-the-loop telemetry for the Act step (commit → check-up reminder → outcome). The Element Clicked rows for these screens are in the Act table below; this table is the domain-event funnel. chapter is the chapter's 1-based index; dynamic is the chapter kind (Phase 2). (Documented retroactively — these shipped in #338 but weren't in the catalog.)

Event Properties Fires when Source
Act Insight Generated chapter, dynamic, duration_ms BNS-1088: the HTTP act generation (generate_journey_act_insight, stateless Responses API) resolved — fires just before the ready phase paints. duration_ms spans the whole attempt incl. the user-context fetch. ActStep.jsx
Act Insight Generation Failed chapter, dynamic, reason (timeout/network/http_4xx/http_5xx), duration_ms BNS-1088: the generation call failed after its transport retry — the loader parks on the retry screen. The retry tap is the existing Element Clicked { screen: 'act', element: 'retry' }. Replaces the old silent 90s JS2B poll timeout (which only logged, never tracked — no event to retire). ActStep.jsx
Act Screen Viewed chapter, dynamic, reused (bool) An act card paints (ready phase). reused: true (BNS-1088) = a previously generated act was re-shown from the chapter payload (generate → back → re-enter no longer re-bills a generation); false = a fresh generation just landed (pairs 1:1 with Act Insight Generated). ActStep.jsx
Act Committed chapter (journey only) | source (ask), when (today/tomorrow/in_two_days/ok), reminder_set (bool) User picks a moment (or Skip/OK) and commits to the act. reminder_set is true only for a specific moment. Journey acts carry chapter and no source; ask acts carry source: 'ask' and no chapter — split the funnel on whichever is present. Ask acts fire this only after act_commit returns 200; a failed commit fires Act Commit Failed instead and the user stays on the acceptance screen. ActStep.jsx (journey) / AskActFlow.jsx (ask)
Act Check-In Surfaced count, journey_count, ask_count, deferred The app-open gate (BNS-1113) finds due, unreported acts on a fresh launch and starts auto-opening "How did it go?" — fires once per launch with the number of acts queued (each act then fires its own Act Check-In Viewed with via: 'app_open'). Throttled: an act surfaced today isn't re-queued until the next day. Since BNS-1089 the queue merges journey acts with ask acts (get_due_acts), FIFO by commitment time; journey_count/ask_count split count by source, and deferred is how many the per-open cap (2) held back — those are not dropped, they re-queue on the next open. A get_due_acts failure degrades to journey-only (ask_count: 0), so a run of zero ask_count is a signal worth checking against the endpoint's error rate. MainTabs.jsx
Act Check-In Viewed chapter, dynamic, via (app_open/deep_link/in_app) User lands on "How did it go?" to report on a committed act — the return leg of the funnel. via: 'app_open' = auto-surfaced by the app-open gate (BNS-1113); 'deep_link' = arrived from the check-up push notification (deep link sets initialPhase); 'in_app' = tapped the Act node on the journey map. Fires once per mount (ref-guarded so an other_input → back → how_did_it_go bounce doesn't double-count). ActStep.jsx
Act Details Viewed chapter, dynamic, via User taps the underlined act title on "How did it go?" and the read-only act page paints (Slack 10-07-26 — the 2-word title alone doesn't remind users what they committed to). Kept separate from Act Screen Viewed so the pre-commit suggestion funnel stays clean. via mirrors the check-in entry. ActStep.jsx
Act Outcome Reported chapter, outcome (tried_meaningful/tried_uncomfortable/hesitated/no_moment/other), via User taps an outcome pill. Always fires (incl. other, which then also fires Act Other Submitted on its own submit). via mirrors the check-in entry, so outcomes can be split by reminder-driven vs organic without a join. ActStep.jsx
Act Check-In Dismissed chapter, dynamic, via User leaves "How did it go?" via the header ✕/back without reporting — the funnel's negative signal. Viewed = Outcome Reported + Dismissed (+ rare app kills). Split by via: 'app_open' to measure how often the auto-surfaced check-in is waved away. ActStep.jsx
Act Performed chapter, coins (7) Outcome is tried_meaningful or tried_uncomfortable (the +coins branches). coins is single-sourced via ACT_REWARD_COINS and mirrors the server grant. ActStep.jsx
Act Other Submitted chapter, input_mode (text/voice), response (raw free-text answer) User submits a free-text/voice reflection on the "Other" path. response is the raw user text (BNS-1129 — product wants to read the answers in Mixpanel); this is a deliberate, single exception to the repo's length/hash-only convention for free-text, so it must not be hashed. ActStep.jsx
Act Reminder Rescheduled chapter, when User sets a new check-up reminder from the act-not-done screen (Bubble action reschedule_reminder) ActStep.jsx
Act Notifications Stopped chapter User taps "Stop notifications" on the act-not-done screen ActStep.jsx

Deferred — days_since_commit (reminder-latency). Act Check-In Viewed / Act Outcome Reported would ideally carry days_since_commit (latency from Act Committed), but there is no client-durable commit timestamp that survives the cold-start return a days-later check-up triggers. Required to enable: add a server committed_date to the act and surface it on the chapter's reactive payload, then set days_since_commit = round((Date.now() − Date.parse(committed_date)) / 86_400_000) on the check-in event. reschedule_count is already derivable in Mixpanel by counting Act Reminder Rescheduled per user+chapter before the outcome — no denormalized property needed.

Onboarding

Screen Type Element Extra Source
onboarding button back step OnboardingFlow
onboarding button error_go_back OnboardingFlow
onboarding button retry_fetch OnboardingFlow
onboarding_open button mic OpenQuestion
onboarding_open button type OpenQuestion
onboarding_open button skip OpenQuestion
onboarding_open button share_anyway OpenQuestion

Journey

Screen Type Element Extra Source
journey button chapter_menu JourneyPath
journey button chapter_select chapter JourneyPath
journey button node node_id, node_type JourneyPath
journey button start_lesson node_id, node_type JourneyPath
journey button review_step node_id, node_type JourneyPath
journey button repractice_step node_id, node_type JourneyPath
journey button replay_insight node_id, node_type JourneyPath
journey button milestone_review node_id JourneyPath
journey button select_journey HomeSection
journey button send / chat / notifications / previous_journey / next_journey / change_topic / conversation_coach / practical_actions / ask_question HomeSection

Learn Video

Screen Type Element Extra Source
learn_video button play LearnStep
learn_video button pause LearnStep
learn_video button speed speed LearnStep
learn_video button rewind LearnStep
learn_video button mute / unmute LearnStep
learn_video button replay LearnStep
learn_video button skip_to_questions / continue_to_questions LearnStep

Practice

Screen Type Element Extra Source
practice button start_practice chapter PracticeStep
practice toggle input_mode mode (voice/text) PracticeStep (BNS-1071)

Act

Screen Type Element Extra Source
act button accept chapter ActStep
act button decline chapter ActStep
act button retry chapter ActStep
how_did_it_go link view_act chapter ActStep
act_review button back chapter ActStep
act_reminder button reminder_tomorrow chapter ReminderSheet
act_reminder button reminder_3_days chapter ReminderSheet
act_reminder button reminder_7_days chapter ReminderSheet
act_reminder button skip_reminder chapter ReminderSheet

Chapter Celebration

Screen Type Element Extra Source
chapter_celebration button continue chapter ChapterCelebration

Step Review

Screen Type Element Extra Source
step_review button rewatch_video StepReview
step_review button close_video StepReview
step_review button play / pause / replay StepReview
step_review button rewind StepReview
step_review button speed speed StepReview
step_review button mute / unmute StepReview

Insight Questions

Screen Type Element Extra Source
insight_questions button back InsightQuestions
insight_questions button close InsightQuestions
insight_questions button mic InsightQuestions
insight_questions button type InsightQuestions
insight_questions button yes / no InsightQuestions
insight_questions button nudge_continue InsightQuestions
insight_questions button nudge_custom InsightQuestions

Insight Other Dialog

Screen Type Element Extra Source
insight_other button mic_start / mic_stop InsightOtherDialog
insight_other button share InsightOtherDialog
insight_other button dialog_closed had_text InsightOtherDialog
insight_other input typing_started InsightOtherDialog
insight_other button open_settings InsightOtherDialog

Insight Playback

Screen Type Element Extra Source
insight_playback button play / pause InsightPlayback
insight_playback button speed speed InsightPlayback
insight_playback button replay InsightPlayback
insight_playback button mute / unmute InsightPlayback
insight_playback button continue InsightPlayback

Fun Zone

Screen Type Element Extra Source
fun banner daily_question FunZoneSection
fun card activity_type type_id, type_name FunZoneSection
fun card history_item section: 'activity', item_id, title FunZoneSection

Activity View

Screen Type Element Extra Source
activity_view button close ActivityView
activity_view button listen ActivityView
activity_view button play / pause ActivityView
activity_view button speed speed ActivityView
activity_view button replay ActivityView
activity_view button retry ActivityView
activity_view button continue ActivityView

Type Detail

Screen Type Element Extra Source
type_detail button create_new section, type_id, type_name TypeDetailScreen
type_detail card history_item section, item_id, title TypeDetailScreen

Simulator Results

Screen Type Element Extra Source
simulator_results button play / pause SimulatorResults
simulator_results button speed speed SimulatorResults
simulator_results button replay SimulatorResults
simulator_results button retry SimulatorResults
simulator_results button done SimulatorResults

Profile & Settings

Screen Type Element Extra Source
profile button upload_photo MainTabs
profile button save_name MainTabs
profile button share_feedback unread_count (number — Intercom unread messages at tap time; > 0 means the user tapped through the unread badge) FeedbackRow
profile button how_bonds_works MainTabs
profile button account_info MainTabs
profile toggle dark_mode preference ThemeContext

Account Info

Screen Type Element Extra Source
account_info button logout MainTabs
account_info button delete_account MainTabs

Name Prompt

Screen Type Element Extra Source
name_prompt button submit_name MainTabs
name_prompt button prefer_not_to_say MainTabs

Partner Name Prompt

Screen Type Element Extra Source
partner_name_prompt button submit_partner_name MainTabs
partner_name_prompt button skip_partner_name MainTabs

Profile Save (Account Info)

Screen Type Element Extra Source
profile button save_partner_name MainTabs (AccountInfoPage)

Daily Question

Screen Type Element Extra Source
daily_question button close DailyQuestion

Share Viewer

Screen Type Element Extra Source
share_viewer button get_bonds ShareCTA

Push Notifications

Event Properties When Source
Push Permission Requested Before native OS prompt is shown notifications.js
Push Permission Responded granted (bool) After user responds to OS prompt or settings redirect notifications.js
Push Player ID Obtained First time player ID is fetched in a session notifications.js

Push Permission Responded is gated on the native OS-dialog callback (no timeout), so a slow approval is no longer mis-logged as a denial (BNS-1025); if the callback is never delivered, no outcome event fires (honest "unknown"). The same outcome also writes the push_enabled People property, which additionally re-syncs on every cold start — see the People Properties table.

Store Reviews (rating prompt)

Screen Type Element Extra Source
rating_prompt button loved useRatingPrompt (👍 step 1)
rating_prompt button not_yet useRatingPrompt (👎 step 1)

The terminal CTAs ("Rate Bonds in the store", "Please tell us…") and the X-dismiss fire dedicated domain events (Native Store Review Requested, Rating Feedback Opened, Rating Prompt Dismissed), not Element Clicked — see Domain Events › Store Reviews.


6. Funnels

Onboarding → Insight → Signup Funnel

1. Screen Viewed { screen: 'welcome' }
2. Element Clicked { element: 'lets_go' }
3. Onboarding Intro Viewed              ← one-time only
4. Onboarding Intro Completed           ← if video finished
   OR Onboarding Intro Skipped          ← if CTA tapped before video ends
5. Screen Viewed { screen: 'onboarding' }
6. Onboarding Step Completed  ← repeats per step
7. Onboarding Completed
8. Onboarding Insight Started
9. Onboarding Insight Generated
10. Onboarding Insight Playback Completed / Skipped
11. Onboarding Insight CTA Tapped
12. Screen Viewed { screen: 'signup' }
13. Sign Up Started { method }
14. Sign Up Completed
15. Onboarding Data Connected
16. Screen Viewed { screen: 'ftue_walkthrough' }
17. FTUE Walkthrough Completed

Drop-off: Onboarding Abandoned (back on step 0)
          Onboarding Intro Backed Out (back from intro to welcome)
Resume:   Onboarding Resumed { phase, step, source }
Replay:   Onboarding Intro Replayed (user replays intro video)
Gen fail: Onboarding Insight Generation Failed { reason, duration_ms } → Onboarding Insight Manual Retry
Connect:  Onboarding Data Connect Failed { error } (no transition; data kept for recovery)
Back:     Onboarding Step Back { from_step, to_step }
Mic err:  Onboarding Mic Permission Denied / Onboarding Mic Unavailable
No API:   Onboarding Speech API Unavailable
Skip:     Element Clicked { screen: 'ftue_walkthrough', element: 'skip' }

Authentication Funnel

1. Element Clicked { element: 'sign_in' }
2. Screen Viewed { screen: 'signin' }
3. Sign In Started { method }
4. Sign In Completed { method }

Journey Chapter Funnel

Each chapter follows this step sequence:

0. Chapter Started { chapter, chapter_title, coin_cost: 10 }  ← fires on coin-gate confirm

1. Journey Step Started { step_type: 'learn' }
2. Journey Video Watched
3. Journey Step Completed { step_type: 'learn' }

4. Journey Step Started { step_type: 'practice' }
5. Journey Practice Started
6. Simulator Session Started { mode: 'journey' }
6a.   Simulator Clarification Asked { question_number }      ← dynamic only, repeats
6b.   Simulator Clarification Submitted { question_number }  ← dynamic only, repeats
6c.   Simulator Turn Completed { turn_number: 1..4, speaker }  ← repeats
7. Simulator Session Completed
8. Journey Practice Completed
9. Journey Step Completed { step_type: 'practice' }

Mid-session drop position = last 6a/6b/6c breadcrumb seen (app-kills emit no
Closed event); in-app quits also carry phase/turn on Simulator Session Closed.

10. Journey Step Started { step_type: 'insight' }
11. Insight Flow Started { type: 'journey' }
12. Insight Question Answered  ← repeats
13. Insight Generated { type: 'journey' }   ← or Insight Generation Failed → Insight Generation Manual Retry
14. Insight Playback Completed / Skipped
15. Journey Step Completed { step_type: 'insight' }

16. Journey Step Started { step_type: 'act' }
17. Element Clicked { element: 'accept' or 'decline' }
18. Journey Step Completed { step_type: 'act' }

19. Chapter Completed

Drop-off at any stage: Journey Step Abandoned { step_type }

Simulator Funnel (Freeform)

1. Screen Viewed { screen: 'simulator' }
2. Simulator Session Started { mode: 'freeform' }
2a.   Simulator Clarification Asked / Submitted { question_number }  ← repeats
3. Coins Deducted { amount, context: 'simulator' }  ← fires at stage 2 start
3a.   Simulator Turn Completed { turn_number: 1..4, speaker }  ← repeats
4. Simulator Session Completed { score, skill_level }

Alt exits:
  - Simulator Session Closed (user quits mid-session)
  - Simulator Session Error { code, message }

Loop: Simulator Session Retried → back to step 3

Insight Funnel (Standalone)

1. Insight Flow Started { type: 'learn' or 'activity' }
2. Insight Question Answered  ← repeats
3. Insight Generated
4. Insight Playback Completed / Skipped

Timeout: Insight Generation Timed Out
Drop-off: Insight Flow Closed

Daily Question Funnel

1. Daily Question Viewed { question_id, category }
2. Daily Question Voted { question_id, index }

Partner Connection

1. Partner Connected

Note: Partner invitation is handled in Bubble, not tracked with Element Clicked in the React UI.

Push Notification Permission Funnel

Triggers: post_signup (MainTabs startup), session_start (7-day re-prompt), reinstall (fresh install), act_reminder (journey act reminder gate)

1. Push Prompt Shown
2. Push Prompt Accepted (vs Push Prompt Dismissed = drop-off)
3. Push Permission Requested
4. Push Permission Responded { granted: true }
5. Push Player ID Obtained

Report Event(s) Insight
DAU / WAU / MAU App Opened Daily/weekly/monthly active users
Session Duration App OpenedApp Backgrounded Average time in app per session
Retention Cohort by signup_date, retained via App Opened Day 1/7/30 retention
Onboarding Conversion Onboarding funnel above Step-by-step drop-off rates
Journey Progress Chapter StartedChapter Completed by chapter Chapter start/complete rates
Simulator Engagement Simulator Session StartedCompleted Completion rate, avg score by mode
Feature Adoption % of authenticated users who triggered each feature start Which features resonate
Coin Economy Coins Deducted vs coins earned (Journey, Onboarding) Earn/spend balance
Daily Question Engagement Daily Question ViewedVoted Participation rate
Partner Adoption Partner Connected / total authenticated users Invite conversion

8. Developer Checklist

When adding or changing analytics tracking:

Code patterns

Track a UI interaction:

import { track } from '../utils/analytics';

track('Element Clicked', {
    screen: 'screen_name',
    element_type: 'button',  // button | tab | banner | card | toggle | link
    element: 'element_name',
    // ...extra context
});

Track a domain event:

track('Feature Action', { property: value });
// e.g. track('Partner Connected');

Track a screen view:

import { screen } from '../utils/analytics';

screen('screen_name');