ad2app-lib 1.34.0 → 1.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analytics/index.d.ts +28 -1
- package/dist/analytics/index.js +23 -1
- package/package.json +1 -1
- package/src/analytics/index.test.ts +43 -1
- package/src/analytics/index.ts +45 -1
|
@@ -48,6 +48,7 @@ export declare const EVENTS: {
|
|
|
48
48
|
readonly PAYWALL_DISMISSED: "paywall_dismissed";
|
|
49
49
|
readonly ACCESS_DENIED: "access_denied";
|
|
50
50
|
readonly ONBOARDING_STEP_COMPLETED: "onboarding_step_completed";
|
|
51
|
+
readonly ONBOARDING_STEP_VIEWED: "onboarding_step_viewed";
|
|
51
52
|
readonly EXIT_INTENT_SHOWN: "exit_intent_shown";
|
|
52
53
|
readonly EXIT_INTENT_DISMISSED: "exit_intent_dismissed";
|
|
53
54
|
readonly EXIT_INTENT_CAPTURED: "exit_intent_captured";
|
|
@@ -74,6 +75,9 @@ export declare const EVENTS: {
|
|
|
74
75
|
readonly POST_RESCHEDULED: "post_rescheduled";
|
|
75
76
|
readonly ACCOUNT_DISCONNECTED: "account_disconnected";
|
|
76
77
|
readonly NOTIFICATION_PREFERENCES_UPDATED: "notification_preferences_updated";
|
|
78
|
+
readonly NATIVE_SESSION_LAUNCHED: "native_session_launched";
|
|
79
|
+
readonly NATIVE_SESSION_RENEWED: "native_session_renewed";
|
|
80
|
+
readonly NATIVE_SESSION_ENDED: "native_session_ended";
|
|
77
81
|
};
|
|
78
82
|
export type EventName = (typeof EVENTS)[keyof typeof EVENTS];
|
|
79
83
|
/** Which email program a send belongs to (spec 120). Wire values — do not rename. */
|
|
@@ -346,11 +350,16 @@ export interface EventProperties {
|
|
|
346
350
|
app_locale: AppLocale;
|
|
347
351
|
};
|
|
348
352
|
[EVENTS.ONBOARDING_STEP_COMPLETED]: {
|
|
349
|
-
flow: 'complete_profile';
|
|
353
|
+
flow: 'complete_profile' | 'beta_consent';
|
|
350
354
|
step: string;
|
|
351
355
|
step_index: number;
|
|
352
356
|
app_locale: AppLocale;
|
|
353
357
|
};
|
|
358
|
+
[EVENTS.ONBOARDING_STEP_VIEWED]: {
|
|
359
|
+
flow: 'complete_profile' | 'beta_consent';
|
|
360
|
+
step: string;
|
|
361
|
+
app_locale?: AppLocale;
|
|
362
|
+
};
|
|
354
363
|
[EVENTS.EXIT_INTENT_SHOWN]: {
|
|
355
364
|
surface: 'connect_wall';
|
|
356
365
|
dwell_ms?: number;
|
|
@@ -466,6 +475,13 @@ export interface EventProperties {
|
|
|
466
475
|
enabled?: boolean;
|
|
467
476
|
success: boolean;
|
|
468
477
|
};
|
|
478
|
+
[EVENTS.NATIVE_SESSION_LAUNCHED]: {
|
|
479
|
+
outcome: 'restored' | 'none' | 'degraded';
|
|
480
|
+
};
|
|
481
|
+
[EVENTS.NATIVE_SESSION_RENEWED]: Record<string, never>;
|
|
482
|
+
[EVENTS.NATIVE_SESSION_ENDED]: {
|
|
483
|
+
cause: 'rejected' | 'sign_out';
|
|
484
|
+
};
|
|
469
485
|
}
|
|
470
486
|
/** Canonical person property keys (set via identify / $set). */
|
|
471
487
|
export declare const PERSON_PROPS: {
|
|
@@ -476,3 +492,14 @@ export declare const PERSON_PROPS: {
|
|
|
476
492
|
readonly PAYWALL_HITS: "paywall_hits";
|
|
477
493
|
};
|
|
478
494
|
export type PersonPropKey = (typeof PERSON_PROPS)[keyof typeof PERSON_PROPS];
|
|
495
|
+
/**
|
|
496
|
+
* Canonical launch-context SUPER-property keys (159 US2). Registered once per
|
|
497
|
+
* session (like `is_pwa` / `display_mode` / `app_platform` in
|
|
498
|
+
* registerLaunchContext), so they ride every event without a per-event field.
|
|
499
|
+
* `APP_BUILD` is the only one governed here today — the value MUST come from
|
|
500
|
+
* the build itself (contract Rule 4), never a hand-maintained constant.
|
|
501
|
+
*/
|
|
502
|
+
export declare const LAUNCH_CONTEXT_PROPS: {
|
|
503
|
+
readonly APP_BUILD: "app_build";
|
|
504
|
+
};
|
|
505
|
+
export type LaunchContextPropKey = (typeof LAUNCH_CONTEXT_PROPS)[keyof typeof LAUNCH_CONTEXT_PROPS];
|
package/dist/analytics/index.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Do NOT rename events after they ship — historical data does not migrate.
|
|
11
11
|
*/
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
-
exports.PERSON_PROPS = exports.CONNECT_BLOCK_REASONS = exports.ONBOARDING_COMPLETED_AT = exports.EMAIL_PROPS = exports.EVENTS = void 0;
|
|
13
|
+
exports.LAUNCH_CONTEXT_PROPS = exports.PERSON_PROPS = exports.CONNECT_BLOCK_REASONS = exports.ONBOARDING_COMPLETED_AT = exports.EMAIL_PROPS = exports.EVENTS = void 0;
|
|
14
14
|
/** Canonical PostHog event names. */
|
|
15
15
|
exports.EVENTS = {
|
|
16
16
|
// Acquisition (landing)
|
|
@@ -72,6 +72,12 @@ exports.EVENTS = {
|
|
|
72
72
|
// trust — it is part of why a REAL retry defect sat unnoticed in the same list.
|
|
73
73
|
ACCESS_DENIED: 'access_denied', // entitlement 403 at the API choke point (beta wall / subscription)
|
|
74
74
|
ONBOARDING_STEP_COMPLETED: 'onboarding_step_completed', // one step of a multi-step flow (complete-profile)
|
|
75
|
+
// Promoted from a raw string literal at ad2app-frontend's
|
|
76
|
+
// OnboardingFlow.tsx:887 (spec 158, 2026-09-03) — it was already firing in
|
|
77
|
+
// production (15 events, 3 people) ungoverned since spec 095. `flow` is
|
|
78
|
+
// widened alongside ONBOARDING_STEP_COMPLETED below to cover the beta
|
|
79
|
+
// consent step, which is not part of the complete-profile flow.
|
|
80
|
+
ONBOARDING_STEP_VIEWED: 'onboarding_step_viewed', // a step was actually shown (drop-off, not just completion)
|
|
75
81
|
EXIT_INTENT_SHOWN: 'exit_intent_shown', // exit-intent capture modal armed (065, verbatim wire values)
|
|
76
82
|
EXIT_INTENT_DISMISSED: 'exit_intent_dismissed',
|
|
77
83
|
EXIT_INTENT_CAPTURED: 'exit_intent_captured', // email captured into the beta list
|
|
@@ -104,6 +110,12 @@ exports.EVENTS = {
|
|
|
104
110
|
ACCOUNT_DISCONNECTED: 'account_disconnected',
|
|
105
111
|
// Settings.
|
|
106
112
|
NOTIFICATION_PREFERENCES_UPDATED: 'notification_preferences_updated',
|
|
113
|
+
// Native session lifecycle (159) — the iOS wrapper's Keychain-backed session,
|
|
114
|
+
// reported so "does it survive" is a query instead of an inference from login
|
|
115
|
+
// counts. Native only; the web session's posture is unchanged (contract Rule 5).
|
|
116
|
+
NATIVE_SESSION_LAUNCHED: 'native_session_launched',
|
|
117
|
+
NATIVE_SESSION_RENEWED: 'native_session_renewed',
|
|
118
|
+
NATIVE_SESSION_ENDED: 'native_session_ended',
|
|
107
119
|
};
|
|
108
120
|
/**
|
|
109
121
|
* Canonical email event property KEYS (spec 120 T001). Emitters and tests
|
|
@@ -151,3 +163,13 @@ exports.PERSON_PROPS = {
|
|
|
151
163
|
// stored here would rewrite the value on every historical paywall_shown event.
|
|
152
164
|
PAYWALL_HITS: 'paywall_hits',
|
|
153
165
|
};
|
|
166
|
+
/**
|
|
167
|
+
* Canonical launch-context SUPER-property keys (159 US2). Registered once per
|
|
168
|
+
* session (like `is_pwa` / `display_mode` / `app_platform` in
|
|
169
|
+
* registerLaunchContext), so they ride every event without a per-event field.
|
|
170
|
+
* `APP_BUILD` is the only one governed here today — the value MUST come from
|
|
171
|
+
* the build itself (contract Rule 4), never a hand-maintained constant.
|
|
172
|
+
*/
|
|
173
|
+
exports.LAUNCH_CONTEXT_PROPS = {
|
|
174
|
+
APP_BUILD: 'app_build',
|
|
175
|
+
};
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import assert from "node:assert/strict";
|
|
9
9
|
import { test } from "node:test";
|
|
10
10
|
|
|
11
|
-
import { EVENTS, PERSON_PROPS, EMAIL_PROPS } from "./index";
|
|
11
|
+
import { EVENTS, PERSON_PROPS, EMAIL_PROPS, LAUNCH_CONTEXT_PROPS } from "./index";
|
|
12
12
|
import type {
|
|
13
13
|
EventProperties,
|
|
14
14
|
EmailEventProperties,
|
|
@@ -105,6 +105,7 @@ const EVENT_PROPERTY_WITNESS: { [E in keyof EventProperties]: EventProperties[E]
|
|
|
105
105
|
[EVENTS.PAYWALL_DISMISSED]: { surface: "upgrade_modal", trigger: "user_click", hit_number: 1, app_locale: "en" },
|
|
106
106
|
[EVENTS.ACCESS_DENIED]: { reason: "subscription-required", app_locale: "en" },
|
|
107
107
|
[EVENTS.ONBOARDING_STEP_COMPLETED]: { flow: "complete_profile", step: "role", step_index: 1, app_locale: "en" },
|
|
108
|
+
[EVENTS.ONBOARDING_STEP_VIEWED]: { flow: "beta_consent", step: "consent" },
|
|
108
109
|
[EVENTS.EXIT_INTENT_SHOWN]: { surface: "connect_wall" },
|
|
109
110
|
[EVENTS.EXIT_INTENT_DISMISSED]: { surface: "connect_wall" },
|
|
110
111
|
[EVENTS.EXIT_INTENT_CAPTURED]: { surface: "connect_wall" },
|
|
@@ -132,6 +133,10 @@ const EVENT_PROPERTY_WITNESS: { [E in keyof EventProperties]: EventProperties[E]
|
|
|
132
133
|
[EVENTS.POST_RESCHEDULED]: { success: true },
|
|
133
134
|
[EVENTS.ACCOUNT_DISCONNECTED]: { platform: "instagram", success: true },
|
|
134
135
|
[EVENTS.NOTIFICATION_PREFERENCES_UPDATED]: { success: true },
|
|
136
|
+
// 159 native session lifecycle
|
|
137
|
+
[EVENTS.NATIVE_SESSION_LAUNCHED]: { outcome: "restored" },
|
|
138
|
+
[EVENTS.NATIVE_SESSION_RENEWED]: {},
|
|
139
|
+
[EVENTS.NATIVE_SESSION_ENDED]: { cause: "rejected" },
|
|
135
140
|
};
|
|
136
141
|
|
|
137
142
|
test("EVENTS values are 1:1 with EventProperties keys (no missing or typo'd event)", () => {
|
|
@@ -186,6 +191,10 @@ test("080 paywall / onboarding wire names are locked (rename breaks the baseline
|
|
|
186
191
|
assert.equal(EVENTS.ONBOARDING_STEP_COMPLETED, "onboarding_step_completed");
|
|
187
192
|
});
|
|
188
193
|
|
|
194
|
+
test("158: onboarding_step_viewed wire name is locked — it was already live in production as a raw string before this promotion (rename breaks the existing 15 events)", () => {
|
|
195
|
+
assert.equal(EVENTS.ONBOARDING_STEP_VIEWED, "onboarding_step_viewed");
|
|
196
|
+
});
|
|
197
|
+
|
|
189
198
|
test("080 subscription-lifecycle wire names are locked (backend = source of truth)", () => {
|
|
190
199
|
assert.deepEqual(
|
|
191
200
|
[
|
|
@@ -450,3 +459,36 @@ test("120 playbook events carry the edition stamp", () => {
|
|
|
450
459
|
assert.equal(opened.edition_version, "2026.08");
|
|
451
460
|
assert.equal(downloaded.format, "pdf");
|
|
452
461
|
});
|
|
462
|
+
|
|
463
|
+
// 159: the native session lifecycle + build attribution.
|
|
464
|
+
|
|
465
|
+
test("159 native session wire names are locked", () => {
|
|
466
|
+
assert.equal(EVENTS.NATIVE_SESSION_LAUNCHED, "native_session_launched");
|
|
467
|
+
assert.equal(EVENTS.NATIVE_SESSION_RENEWED, "native_session_renewed");
|
|
468
|
+
assert.equal(EVENTS.NATIVE_SESSION_ENDED, "native_session_ended");
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
test("159 native_session_launched carries a closed outcome — restored, none, or degraded", () => {
|
|
472
|
+
const restored: EventProperties[typeof EVENTS.NATIVE_SESSION_LAUNCHED] = { outcome: "restored" };
|
|
473
|
+
const none: EventProperties[typeof EVENTS.NATIVE_SESSION_LAUNCHED] = { outcome: "none" };
|
|
474
|
+
const degraded: EventProperties[typeof EVENTS.NATIVE_SESSION_LAUNCHED] = { outcome: "degraded" };
|
|
475
|
+
assert.equal(restored.outcome, "restored");
|
|
476
|
+
assert.equal(none.outcome, "none");
|
|
477
|
+
assert.equal(degraded.outcome, "degraded");
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
test("159 native_session_ended carries its cause — rejected or sign_out, never bare", () => {
|
|
481
|
+
const rejected: EventProperties[typeof EVENTS.NATIVE_SESSION_ENDED] = { cause: "rejected" };
|
|
482
|
+
const signOut: EventProperties[typeof EVENTS.NATIVE_SESSION_ENDED] = { cause: "sign_out" };
|
|
483
|
+
assert.equal(rejected.cause, "rejected");
|
|
484
|
+
assert.equal(signOut.cause, "sign_out");
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
test("159 native_session_renewed carries no properties (the fact of the renewal is the whole report)", () => {
|
|
488
|
+
const renewed: EventProperties[typeof EVENTS.NATIVE_SESSION_RENEWED] = {};
|
|
489
|
+
assert.deepEqual(renewed, {});
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
test("159 LAUNCH_CONTEXT_PROPS governs the build-attribution key (no raw 'app_build' string at the call site)", () => {
|
|
493
|
+
assert.equal(LAUNCH_CONTEXT_PROPS.APP_BUILD, "app_build");
|
|
494
|
+
});
|
package/src/analytics/index.ts
CHANGED
|
@@ -78,6 +78,12 @@ export const EVENTS = {
|
|
|
78
78
|
// trust — it is part of why a REAL retry defect sat unnoticed in the same list.
|
|
79
79
|
ACCESS_DENIED: 'access_denied', // entitlement 403 at the API choke point (beta wall / subscription)
|
|
80
80
|
ONBOARDING_STEP_COMPLETED: 'onboarding_step_completed', // one step of a multi-step flow (complete-profile)
|
|
81
|
+
// Promoted from a raw string literal at ad2app-frontend's
|
|
82
|
+
// OnboardingFlow.tsx:887 (spec 158, 2026-09-03) — it was already firing in
|
|
83
|
+
// production (15 events, 3 people) ungoverned since spec 095. `flow` is
|
|
84
|
+
// widened alongside ONBOARDING_STEP_COMPLETED below to cover the beta
|
|
85
|
+
// consent step, which is not part of the complete-profile flow.
|
|
86
|
+
ONBOARDING_STEP_VIEWED: 'onboarding_step_viewed', // a step was actually shown (drop-off, not just completion)
|
|
81
87
|
EXIT_INTENT_SHOWN: 'exit_intent_shown', // exit-intent capture modal armed (065, verbatim wire values)
|
|
82
88
|
EXIT_INTENT_DISMISSED: 'exit_intent_dismissed',
|
|
83
89
|
EXIT_INTENT_CAPTURED: 'exit_intent_captured', // email captured into the beta list
|
|
@@ -115,6 +121,13 @@ export const EVENTS = {
|
|
|
115
121
|
|
|
116
122
|
// Settings.
|
|
117
123
|
NOTIFICATION_PREFERENCES_UPDATED: 'notification_preferences_updated',
|
|
124
|
+
|
|
125
|
+
// Native session lifecycle (159) — the iOS wrapper's Keychain-backed session,
|
|
126
|
+
// reported so "does it survive" is a query instead of an inference from login
|
|
127
|
+
// counts. Native only; the web session's posture is unchanged (contract Rule 5).
|
|
128
|
+
NATIVE_SESSION_LAUNCHED: 'native_session_launched',
|
|
129
|
+
NATIVE_SESSION_RENEWED: 'native_session_renewed',
|
|
130
|
+
NATIVE_SESSION_ENDED: 'native_session_ended',
|
|
118
131
|
} as const;
|
|
119
132
|
|
|
120
133
|
export type EventName = (typeof EVENTS)[keyof typeof EVENTS];
|
|
@@ -436,11 +449,19 @@ export interface EventProperties {
|
|
|
436
449
|
app_locale: AppLocale;
|
|
437
450
|
};
|
|
438
451
|
[EVENTS.ONBOARDING_STEP_COMPLETED]: {
|
|
439
|
-
flow: 'complete_profile'; //
|
|
452
|
+
flow: 'complete_profile' | 'beta_consent'; // widened 2026-09-03 (spec 158) for the beta consent step
|
|
440
453
|
step: string; // stable slug derived from the real wizard, not guessed
|
|
441
454
|
step_index: number; // 1-based
|
|
442
455
|
app_locale: AppLocale;
|
|
443
456
|
};
|
|
457
|
+
// Promoted from a raw string (spec 158) — same `flow`/`step` shape as
|
|
458
|
+
// ONBOARDING_STEP_COMPLETED, fired once per step actually SHOWN rather than
|
|
459
|
+
// finished, so drop-off is visible per step, not only per completion.
|
|
460
|
+
[EVENTS.ONBOARDING_STEP_VIEWED]: {
|
|
461
|
+
flow: 'complete_profile' | 'beta_consent';
|
|
462
|
+
step: string;
|
|
463
|
+
app_locale?: AppLocale; // optional: the pre-existing raw call site does not have it yet (T001a repairs the call site, not the type's honesty about what ships today)
|
|
464
|
+
};
|
|
444
465
|
// Exit-intent (065). surface value is VERBATIM from the ExitIntentModal
|
|
445
466
|
// component ('connect_wall'); dwell_ms / page_views / app_locale are optional
|
|
446
467
|
// additions T003 wires (the component does not emit them yet — fidelity gate).
|
|
@@ -486,6 +507,16 @@ export interface EventProperties {
|
|
|
486
507
|
|
|
487
508
|
// Settings — which preference toggled and its new state.
|
|
488
509
|
[EVENTS.NOTIFICATION_PREFERENCES_UPDATED]: { preference?: string; enabled?: boolean; success: boolean };
|
|
510
|
+
|
|
511
|
+
// Native session lifecycle (159, contract session-reports.md). `outcome`
|
|
512
|
+
// covers all three launch reports in one event (restored / none / degraded)
|
|
513
|
+
// rather than three near-duplicate event names — the same pattern as
|
|
514
|
+
// ACCESS_DENIED's `reason`. `cause` on the ending is mandatory (contract
|
|
515
|
+
// Rule 1): an ending without a cause tells a reader nothing a bug and a
|
|
516
|
+
// person didn't already look identical.
|
|
517
|
+
[EVENTS.NATIVE_SESSION_LAUNCHED]: { outcome: 'restored' | 'none' | 'degraded' };
|
|
518
|
+
[EVENTS.NATIVE_SESSION_RENEWED]: Record<string, never>;
|
|
519
|
+
[EVENTS.NATIVE_SESSION_ENDED]: { cause: 'rejected' | 'sign_out' };
|
|
489
520
|
}
|
|
490
521
|
|
|
491
522
|
/** Canonical person property keys (set via identify / $set). */
|
|
@@ -502,6 +533,19 @@ export const PERSON_PROPS = {
|
|
|
502
533
|
|
|
503
534
|
export type PersonPropKey = (typeof PERSON_PROPS)[keyof typeof PERSON_PROPS];
|
|
504
535
|
|
|
536
|
+
/**
|
|
537
|
+
* Canonical launch-context SUPER-property keys (159 US2). Registered once per
|
|
538
|
+
* session (like `is_pwa` / `display_mode` / `app_platform` in
|
|
539
|
+
* registerLaunchContext), so they ride every event without a per-event field.
|
|
540
|
+
* `APP_BUILD` is the only one governed here today — the value MUST come from
|
|
541
|
+
* the build itself (contract Rule 4), never a hand-maintained constant.
|
|
542
|
+
*/
|
|
543
|
+
export const LAUNCH_CONTEXT_PROPS = {
|
|
544
|
+
APP_BUILD: 'app_build',
|
|
545
|
+
} as const;
|
|
546
|
+
|
|
547
|
+
export type LaunchContextPropKey = (typeof LAUNCH_CONTEXT_PROPS)[keyof typeof LAUNCH_CONTEXT_PROPS];
|
|
548
|
+
|
|
505
549
|
/**
|
|
506
550
|
* Compile-time taxonomy contract (AD2-952). These are pure type-level assertions:
|
|
507
551
|
* if EVENTS and EventProperties ever drift, the `tsc --noEmit` CI gate fails the
|