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.
@@ -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];
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ad2app-lib",
3
- "version": "1.34.0",
3
+ "version": "1.36.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "commonjs",
@@ -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
+ });
@@ -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'; // closed set, extensible when a new flow ships
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