ad2app-lib 1.37.0 → 1.38.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.
|
@@ -32,6 +32,7 @@ export declare const EVENTS: {
|
|
|
32
32
|
readonly PROFILE_COMPLETED: "profile_completed";
|
|
33
33
|
readonly LOGGED_IN: "logged_in";
|
|
34
34
|
readonly SOCIAL_ACCOUNT_CONNECTED: "social_account_connected";
|
|
35
|
+
readonly SOCIAL_SIGN_IN_FAILED: "social_sign_in_failed";
|
|
35
36
|
readonly COMPOSE_STARTED: "compose_started";
|
|
36
37
|
readonly COMPOSE_STEP_VIEWED: "compose_step_viewed";
|
|
37
38
|
readonly COMPOSE_BLOCKED: "compose_blocked";
|
|
@@ -44,6 +45,9 @@ export declare const EVENTS: {
|
|
|
44
45
|
readonly CHECKOUT_STARTED: "checkout_started";
|
|
45
46
|
readonly SUBSCRIPTION_STARTED: "subscription_started";
|
|
46
47
|
readonly SUBSCRIPTION_CANCELED: "subscription_canceled";
|
|
48
|
+
readonly BILLING_PORTAL_OPENED: "billing_portal_opened";
|
|
49
|
+
readonly SUBSCRIPTION_REACTIVATED: "subscription_reactivated";
|
|
50
|
+
readonly CHECKOUT_CONFIRMATION_TIMED_OUT: "checkout_confirmation_timed_out";
|
|
47
51
|
readonly PAYWALL_SHOWN: "paywall_shown";
|
|
48
52
|
readonly PAYWALL_DISMISSED: "paywall_dismissed";
|
|
49
53
|
readonly ACCESS_DENIED: "access_denied";
|
|
@@ -75,6 +79,12 @@ export declare const EVENTS: {
|
|
|
75
79
|
readonly POST_RESCHEDULED: "post_rescheduled";
|
|
76
80
|
readonly ACCOUNT_DISCONNECTED: "account_disconnected";
|
|
77
81
|
readonly NOTIFICATION_PREFERENCES_UPDATED: "notification_preferences_updated";
|
|
82
|
+
readonly THEME_CHANGED: "theme_changed";
|
|
83
|
+
readonly FEEDBACK_SUBMITTED: "feedback_submitted";
|
|
84
|
+
readonly ACCOUNT_DELETED: "account_deleted";
|
|
85
|
+
readonly LANGUAGE_CHANGED: "language_changed";
|
|
86
|
+
readonly COMMENT_GUARD_MODE_CHANGED: "comment_guard_mode_changed";
|
|
87
|
+
readonly AI_TOOLS_REVOKE_REQUESTED: "ai_tools_revoke_requested";
|
|
78
88
|
readonly NATIVE_SESSION_LAUNCHED: "native_session_launched";
|
|
79
89
|
readonly NATIVE_SESSION_RENEWED: "native_session_renewed";
|
|
80
90
|
readonly NATIVE_SESSION_ENDED: "native_session_ended";
|
|
@@ -165,7 +175,7 @@ export type ComposeStep = 'platforms' | 'content' | 'details' | 'review';
|
|
|
165
175
|
* content, so neither may ride the event: only the class does. Same contract as
|
|
166
176
|
* PublishFailureReason. Do NOT rename after shipping — wire values.
|
|
167
177
|
*/
|
|
168
|
-
export type ComposeBlockReason = 'empty' | 'missing_fields' | 'over_limit' | 'media' | 'channel_blocked' | 'poll';
|
|
178
|
+
export type ComposeBlockReason = 'empty' | 'missing_fields' | 'over_limit' | 'media' | 'channel_blocked' | 'poll' | 'unconnected_platform';
|
|
169
179
|
/**
|
|
170
180
|
* Why a connect attempt ended without a connected account (spec 155 US3).
|
|
171
181
|
*
|
|
@@ -196,9 +206,11 @@ export type ComposeBlockReason = 'empty' | 'missing_fields' | 'over_limit' | 'me
|
|
|
196
206
|
export type OnboardingCompletedAt = 'connect' | 'install' | 'schedule';
|
|
197
207
|
/** The finish lines, in the order the flow can reach them. */
|
|
198
208
|
export declare const ONBOARDING_COMPLETED_AT: readonly ["connect", "install", "schedule"];
|
|
199
|
-
export type ConnectBlockReason = 'paywall' | 'canceled' | 'failed' | 'returned_without_account' | 'personal_profile';
|
|
209
|
+
export type ConnectBlockReason = 'paywall' | 'canceled' | 'failed' | 'returned_without_account' | 'personal_profile' | 'api_failed' | 'timeout' | 'exception';
|
|
200
210
|
/** The reason set, in the order the spec lists it. Iterable for contract tests and dashboards. */
|
|
201
|
-
export declare const CONNECT_BLOCK_REASONS: readonly ["paywall", "canceled", "failed", "returned_without_account", "personal_profile"];
|
|
211
|
+
export declare const CONNECT_BLOCK_REASONS: readonly ["paywall", "canceled", "failed", "returned_without_account", "personal_profile", "api_failed", "timeout", "exception"];
|
|
212
|
+
export type SignInFailureReason = 'canceled' | 'missing_code' | 'invalid_provider' | 'no_email' | 'api_failed' | 'exception';
|
|
213
|
+
export declare const SIGN_IN_FAILURE_REASONS: readonly ["canceled", "missing_code", "invalid_provider", "no_email", "api_failed", "exception"];
|
|
202
214
|
export type PublishFailureReason = 'auth' | 'rate_limit' | 'media' | 'content' | 'network' | 'platform' | 'unknown';
|
|
203
215
|
/** Property shape per event. Keeps emitters honest across repos. */
|
|
204
216
|
export interface EventProperties {
|
|
@@ -261,6 +273,10 @@ export interface EventProperties {
|
|
|
261
273
|
account_count?: number;
|
|
262
274
|
connection_ordinal?: number;
|
|
263
275
|
};
|
|
276
|
+
[EVENTS.SOCIAL_SIGN_IN_FAILED]: {
|
|
277
|
+
platform?: string;
|
|
278
|
+
reason: SignInFailureReason;
|
|
279
|
+
};
|
|
264
280
|
[EVENTS.COMPOSE_STARTED]: {
|
|
265
281
|
source?: 'wizard' | 'quick';
|
|
266
282
|
};
|
|
@@ -324,6 +340,15 @@ export interface EventProperties {
|
|
|
324
340
|
plan: string;
|
|
325
341
|
reason?: string;
|
|
326
342
|
};
|
|
343
|
+
[EVENTS.BILLING_PORTAL_OPENED]: {
|
|
344
|
+
plan: string;
|
|
345
|
+
};
|
|
346
|
+
[EVENTS.SUBSCRIPTION_REACTIVATED]: {
|
|
347
|
+
plan: string;
|
|
348
|
+
};
|
|
349
|
+
[EVENTS.CHECKOUT_CONFIRMATION_TIMED_OUT]: {
|
|
350
|
+
had_session_id: boolean;
|
|
351
|
+
};
|
|
327
352
|
[EVENTS.PAYWALL_SHOWN]: {
|
|
328
353
|
surface: 'upgrade_modal' | 'pricing_page';
|
|
329
354
|
trigger: 'user_click' | 'limit_reached' | 'connect_wall';
|
|
@@ -475,6 +500,22 @@ export interface EventProperties {
|
|
|
475
500
|
enabled?: boolean;
|
|
476
501
|
success: boolean;
|
|
477
502
|
};
|
|
503
|
+
[EVENTS.THEME_CHANGED]: {
|
|
504
|
+
mode: 'light' | 'dark';
|
|
505
|
+
};
|
|
506
|
+
[EVENTS.FEEDBACK_SUBMITTED]: {
|
|
507
|
+
has_screenshot: boolean;
|
|
508
|
+
};
|
|
509
|
+
[EVENTS.ACCOUNT_DELETED]: {
|
|
510
|
+
tier: string;
|
|
511
|
+
};
|
|
512
|
+
[EVENTS.LANGUAGE_CHANGED]: {
|
|
513
|
+
language: 'en' | 'pl';
|
|
514
|
+
};
|
|
515
|
+
[EVENTS.COMMENT_GUARD_MODE_CHANGED]: {
|
|
516
|
+
mode: 'off' | 'flag-only' | 'auto-ignore';
|
|
517
|
+
};
|
|
518
|
+
[EVENTS.AI_TOOLS_REVOKE_REQUESTED]: Record<string, never>;
|
|
478
519
|
[EVENTS.NATIVE_SESSION_LAUNCHED]: {
|
|
479
520
|
outcome: 'restored' | 'none' | 'degraded';
|
|
480
521
|
};
|
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.LAUNCH_CONTEXT_PROPS = 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.SIGN_IN_FAILURE_REASONS = 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)
|
|
@@ -42,6 +42,12 @@ exports.EVENTS = {
|
|
|
42
42
|
PROFILE_COMPLETED: 'profile_completed', // the /complete-profile step (influencers)
|
|
43
43
|
LOGGED_IN: 'logged_in',
|
|
44
44
|
SOCIAL_ACCOUNT_CONNECTED: 'social_account_connected',
|
|
45
|
+
// 164 G007: OAuthCallbackPage's sign-in branch (state !== 'web-connect')
|
|
46
|
+
// had zero tracking on any of its 5 failure outcomes -- LOGGED_IN only
|
|
47
|
+
// ever fires on success (PostHogIdentify.tsx, centrally, off the redux
|
|
48
|
+
// authed state), so failures need their own event rather than an
|
|
49
|
+
// overloaded success/failure LOGGED_IN.
|
|
50
|
+
SOCIAL_SIGN_IN_FAILED: 'social_sign_in_failed',
|
|
45
51
|
COMPOSE_STARTED: 'compose_started',
|
|
46
52
|
// AD2-1371: the composer was a black box between COMPOSE_STARTED and
|
|
47
53
|
// POST_SCHEDULED. Measured 2026-08-28: of seven users who connected an
|
|
@@ -61,6 +67,17 @@ exports.EVENTS = {
|
|
|
61
67
|
CHECKOUT_STARTED: 'checkout_started',
|
|
62
68
|
SUBSCRIPTION_STARTED: 'subscription_started',
|
|
63
69
|
SUBSCRIPTION_CANCELED: 'subscription_canceled',
|
|
70
|
+
// 164 G009 (SubscriptionSection had zero tracking on either action): client
|
|
71
|
+
// intent only — CANCEL_INITIATED above is already backend/webhook-owned
|
|
72
|
+
// (fires off the real `cancel_at_period_end` transition), so cancel itself
|
|
73
|
+
// is deliberately NOT duplicated here.
|
|
74
|
+
BILLING_PORTAL_OPENED: 'billing_portal_opened',
|
|
75
|
+
SUBSCRIPTION_REACTIVATED: 'subscription_reactivated',
|
|
76
|
+
// 164 (ComposePage -> PaymentProcessingPage): the client gave up polling for
|
|
77
|
+
// Stripe/webhook confirmation. Distinct from the backend-owned PAYMENT_FAILED
|
|
78
|
+
// (a real decline) -- this only means "we could not confirm in time", which
|
|
79
|
+
// may still resolve moments later.
|
|
80
|
+
CHECKOUT_CONFIRMATION_TIMED_OUT: 'checkout_confirmation_timed_out',
|
|
64
81
|
// Measurement baseline (080) — paywall / onboarding / exit-intent (frontend-owned)
|
|
65
82
|
PAYWALL_SHOWN: 'paywall_shown', // any monetization surface is shown (upgrade modal / pricing page)
|
|
66
83
|
PAYWALL_DISMISSED: 'paywall_dismissed', // closed without converting
|
|
@@ -110,6 +127,19 @@ exports.EVENTS = {
|
|
|
110
127
|
ACCOUNT_DISCONNECTED: 'account_disconnected',
|
|
111
128
|
// Settings.
|
|
112
129
|
NOTIFICATION_PREFERENCES_UPDATED: 'notification_preferences_updated',
|
|
130
|
+
// 164 G009: SettingsPage had `track`/`EVENTS` imported but never called --
|
|
131
|
+
// theme, feedback, and account-deletion all fired nothing. NOTIFICATION_PREFERENCES_UPDATED
|
|
132
|
+
// above already covers the marketing-emails toggle (its declared `{preference, enabled,
|
|
133
|
+
// success}` shape fits exactly; reused rather than adding a parallel event).
|
|
134
|
+
THEME_CHANGED: 'theme_changed',
|
|
135
|
+
FEEDBACK_SUBMITTED: 'feedback_submitted',
|
|
136
|
+
ACCOUNT_DELETED: 'account_deleted',
|
|
137
|
+
// Shared LanguageSwitcher (SignIn/SignUp/Settings) had the same zero-tracking gap.
|
|
138
|
+
LANGUAGE_CHANGED: 'language_changed',
|
|
139
|
+
// Behind SHOW_COMMENT_GUARD_SECTION / SHOW_AI_TOOLS_SECTION (both default OFF) —
|
|
140
|
+
// no real user can reach either today, but fixed for completeness/G009.
|
|
141
|
+
COMMENT_GUARD_MODE_CHANGED: 'comment_guard_mode_changed',
|
|
142
|
+
AI_TOOLS_REVOKE_REQUESTED: 'ai_tools_revoke_requested',
|
|
113
143
|
// Native session lifecycle (159) — the iOS wrapper's Keychain-backed session,
|
|
114
144
|
// reported so "does it survive" is a query instead of an inference from login
|
|
115
145
|
// counts. Native only; the web session's posture is unchanged (contract Rule 5).
|
|
@@ -151,6 +181,17 @@ exports.CONNECT_BLOCK_REASONS = [
|
|
|
151
181
|
'failed',
|
|
152
182
|
'returned_without_account',
|
|
153
183
|
'personal_profile',
|
|
184
|
+
'api_failed',
|
|
185
|
+
'timeout',
|
|
186
|
+
'exception',
|
|
187
|
+
];
|
|
188
|
+
exports.SIGN_IN_FAILURE_REASONS = [
|
|
189
|
+
'canceled',
|
|
190
|
+
'missing_code',
|
|
191
|
+
'invalid_provider',
|
|
192
|
+
'no_email',
|
|
193
|
+
'api_failed',
|
|
194
|
+
'exception',
|
|
154
195
|
];
|
|
155
196
|
/** Canonical person property keys (set via identify / $set). */
|
|
156
197
|
exports.PERSON_PROPS = {
|
package/package.json
CHANGED
|
@@ -13,13 +13,19 @@ import { test } from 'node:test';
|
|
|
13
13
|
import { CONNECT_BLOCK_REASONS, EVENTS } from './index';
|
|
14
14
|
import type { ConnectBlockReason, EventProperties } from './index';
|
|
15
15
|
|
|
16
|
-
test('the reason set is the
|
|
16
|
+
test('the reason set is the eight causes, as wire values', () => {
|
|
17
|
+
// 164 G001: widened from five to eight — 'api_failed'/'timeout'/'exception'
|
|
18
|
+
// cover connectAccount() itself failing before any redirect ever happens
|
|
19
|
+
// (confirmed live 2026-09-07 as the one previously-untracked branch).
|
|
17
20
|
assert.deepEqual([...CONNECT_BLOCK_REASONS], [
|
|
18
21
|
'paywall',
|
|
19
22
|
'canceled',
|
|
20
23
|
'failed',
|
|
21
24
|
'returned_without_account',
|
|
22
25
|
'personal_profile',
|
|
26
|
+
'api_failed',
|
|
27
|
+
'timeout',
|
|
28
|
+
'exception',
|
|
23
29
|
]);
|
|
24
30
|
});
|
|
25
31
|
|
|
@@ -90,6 +90,8 @@ const EVENT_PROPERTY_WITNESS: { [E in keyof EventProperties]: EventProperties[E]
|
|
|
90
90
|
// 'email' | 'google' union (compile-time, enforced by `tsc --noEmit`).
|
|
91
91
|
[EVENTS.LOGGED_IN]: { method: "apple" },
|
|
92
92
|
[EVENTS.SOCIAL_ACCOUNT_CONNECTED]: { platform: "instagram" },
|
|
93
|
+
// 164 G007
|
|
94
|
+
[EVENTS.SOCIAL_SIGN_IN_FAILED]: { platform: "facebook", reason: "canceled" },
|
|
93
95
|
[EVENTS.COMPOSE_STARTED]: {},
|
|
94
96
|
[EVENTS.COMPOSE_STEP_VIEWED]: { step: "content" },
|
|
95
97
|
[EVENTS.COMPOSE_BLOCKED]: { step: "content", reasons: ["missing_fields"], reason_count: 1 },
|
|
@@ -102,6 +104,11 @@ const EVENT_PROPERTY_WITNESS: { [E in keyof EventProperties]: EventProperties[E]
|
|
|
102
104
|
[EVENTS.CHECKOUT_STARTED]: { plan: "starter" },
|
|
103
105
|
[EVENTS.SUBSCRIPTION_STARTED]: { plan: "starter" },
|
|
104
106
|
[EVENTS.SUBSCRIPTION_CANCELED]: { plan: "starter" },
|
|
107
|
+
// 164 G011
|
|
108
|
+
[EVENTS.BILLING_PORTAL_OPENED]: { plan: "pro" },
|
|
109
|
+
[EVENTS.SUBSCRIPTION_REACTIVATED]: { plan: "pro" },
|
|
110
|
+
// 164 (PaymentProcessingPage)
|
|
111
|
+
[EVENTS.CHECKOUT_CONFIRMATION_TIMED_OUT]: { had_session_id: true },
|
|
105
112
|
// 080 measurement baseline
|
|
106
113
|
[EVENTS.PAYWALL_SHOWN]: { surface: "upgrade_modal", trigger: "user_click", hit_number: 1, app_locale: "en" },
|
|
107
114
|
[EVENTS.PAYWALL_DISMISSED]: { surface: "upgrade_modal", trigger: "user_click", hit_number: 1, app_locale: "en" },
|
|
@@ -139,6 +146,14 @@ const EVENT_PROPERTY_WITNESS: { [E in keyof EventProperties]: EventProperties[E]
|
|
|
139
146
|
[EVENTS.NATIVE_SESSION_LAUNCHED]: { outcome: "restored" },
|
|
140
147
|
[EVENTS.NATIVE_SESSION_RENEWED]: {},
|
|
141
148
|
[EVENTS.NATIVE_SESSION_ENDED]: { cause: "rejected" },
|
|
149
|
+
// 164 G009 — SettingsPage had track/EVENTS imported but never called
|
|
150
|
+
[EVENTS.THEME_CHANGED]: { mode: "dark" },
|
|
151
|
+
[EVENTS.FEEDBACK_SUBMITTED]: { has_screenshot: false },
|
|
152
|
+
[EVENTS.ACCOUNT_DELETED]: { tier: "pro" },
|
|
153
|
+
[EVENTS.LANGUAGE_CHANGED]: { language: "pl" },
|
|
154
|
+
// 164 G012 — both behind default-OFF flags, fixed for completeness
|
|
155
|
+
[EVENTS.COMMENT_GUARD_MODE_CHANGED]: { mode: "flag-only" },
|
|
156
|
+
[EVENTS.AI_TOOLS_REVOKE_REQUESTED]: {},
|
|
142
157
|
};
|
|
143
158
|
|
|
144
159
|
test("EVENTS values are 1:1 with EventProperties keys (no missing or typo'd event)", () => {
|
package/src/analytics/index.ts
CHANGED
|
@@ -46,6 +46,12 @@ export const EVENTS = {
|
|
|
46
46
|
PROFILE_COMPLETED: 'profile_completed', // the /complete-profile step (influencers)
|
|
47
47
|
LOGGED_IN: 'logged_in',
|
|
48
48
|
SOCIAL_ACCOUNT_CONNECTED: 'social_account_connected',
|
|
49
|
+
// 164 G007: OAuthCallbackPage's sign-in branch (state !== 'web-connect')
|
|
50
|
+
// had zero tracking on any of its 5 failure outcomes -- LOGGED_IN only
|
|
51
|
+
// ever fires on success (PostHogIdentify.tsx, centrally, off the redux
|
|
52
|
+
// authed state), so failures need their own event rather than an
|
|
53
|
+
// overloaded success/failure LOGGED_IN.
|
|
54
|
+
SOCIAL_SIGN_IN_FAILED: 'social_sign_in_failed',
|
|
49
55
|
COMPOSE_STARTED: 'compose_started',
|
|
50
56
|
// AD2-1371: the composer was a black box between COMPOSE_STARTED and
|
|
51
57
|
// POST_SCHEDULED. Measured 2026-08-28: of seven users who connected an
|
|
@@ -66,6 +72,17 @@ export const EVENTS = {
|
|
|
66
72
|
CHECKOUT_STARTED: 'checkout_started',
|
|
67
73
|
SUBSCRIPTION_STARTED: 'subscription_started',
|
|
68
74
|
SUBSCRIPTION_CANCELED: 'subscription_canceled',
|
|
75
|
+
// 164 G009 (SubscriptionSection had zero tracking on either action): client
|
|
76
|
+
// intent only — CANCEL_INITIATED above is already backend/webhook-owned
|
|
77
|
+
// (fires off the real `cancel_at_period_end` transition), so cancel itself
|
|
78
|
+
// is deliberately NOT duplicated here.
|
|
79
|
+
BILLING_PORTAL_OPENED: 'billing_portal_opened',
|
|
80
|
+
SUBSCRIPTION_REACTIVATED: 'subscription_reactivated',
|
|
81
|
+
// 164 (ComposePage -> PaymentProcessingPage): the client gave up polling for
|
|
82
|
+
// Stripe/webhook confirmation. Distinct from the backend-owned PAYMENT_FAILED
|
|
83
|
+
// (a real decline) -- this only means "we could not confirm in time", which
|
|
84
|
+
// may still resolve moments later.
|
|
85
|
+
CHECKOUT_CONFIRMATION_TIMED_OUT: 'checkout_confirmation_timed_out',
|
|
69
86
|
|
|
70
87
|
// Measurement baseline (080) — paywall / onboarding / exit-intent (frontend-owned)
|
|
71
88
|
PAYWALL_SHOWN: 'paywall_shown', // any monetization surface is shown (upgrade modal / pricing page)
|
|
@@ -121,6 +138,19 @@ export const EVENTS = {
|
|
|
121
138
|
|
|
122
139
|
// Settings.
|
|
123
140
|
NOTIFICATION_PREFERENCES_UPDATED: 'notification_preferences_updated',
|
|
141
|
+
// 164 G009: SettingsPage had `track`/`EVENTS` imported but never called --
|
|
142
|
+
// theme, feedback, and account-deletion all fired nothing. NOTIFICATION_PREFERENCES_UPDATED
|
|
143
|
+
// above already covers the marketing-emails toggle (its declared `{preference, enabled,
|
|
144
|
+
// success}` shape fits exactly; reused rather than adding a parallel event).
|
|
145
|
+
THEME_CHANGED: 'theme_changed',
|
|
146
|
+
FEEDBACK_SUBMITTED: 'feedback_submitted',
|
|
147
|
+
ACCOUNT_DELETED: 'account_deleted',
|
|
148
|
+
// Shared LanguageSwitcher (SignIn/SignUp/Settings) had the same zero-tracking gap.
|
|
149
|
+
LANGUAGE_CHANGED: 'language_changed',
|
|
150
|
+
// Behind SHOW_COMMENT_GUARD_SECTION / SHOW_AI_TOOLS_SECTION (both default OFF) —
|
|
151
|
+
// no real user can reach either today, but fixed for completeness/G009.
|
|
152
|
+
COMMENT_GUARD_MODE_CHANGED: 'comment_guard_mode_changed',
|
|
153
|
+
AI_TOOLS_REVOKE_REQUESTED: 'ai_tools_revoke_requested',
|
|
124
154
|
|
|
125
155
|
// Native session lifecycle (159) — the iOS wrapper's Keychain-backed session,
|
|
126
156
|
// reported so "does it survive" is a query instead of an inference from login
|
|
@@ -242,7 +272,12 @@ export type ComposeBlockReason =
|
|
|
242
272
|
| 'over_limit' // caption longer than a selected platform allows
|
|
243
273
|
| 'media' // media rejected (format, size, duration, count)
|
|
244
274
|
| 'channel_blocked' // the channel itself refused (e.g. a failed prerequisite read)
|
|
245
|
-
| 'poll'
|
|
275
|
+
| 'poll' // an incomplete or unpostable poll
|
|
276
|
+
// 164 G002: picking an unconnected platform at step 1 (Platforms) redirects
|
|
277
|
+
// straight to /accounts with zero analytics -- same "composer refused to
|
|
278
|
+
// let you proceed" shape as the publish-readiness reasons above, just one
|
|
279
|
+
// step earlier, so it reuses this event/reason set rather than a new event.
|
|
280
|
+
| 'unconnected_platform';
|
|
246
281
|
|
|
247
282
|
/**
|
|
248
283
|
* Why a connect attempt ended without a connected account (spec 155 US3).
|
|
@@ -285,7 +320,14 @@ export type ConnectBlockReason =
|
|
|
285
320
|
| 'canceled'
|
|
286
321
|
| 'failed'
|
|
287
322
|
| 'returned_without_account'
|
|
288
|
-
| 'personal_profile'
|
|
323
|
+
| 'personal_profile'
|
|
324
|
+
// 164 G001: 'failed' above is the POST-redirect case (backend appends
|
|
325
|
+
// ?connect=failed once the provider has already handed control back).
|
|
326
|
+
// These three cover the connectAccount() API call itself never getting a
|
|
327
|
+
// redirect at all — confirmed live 2026-09-07 as the one untracked branch.
|
|
328
|
+
| 'api_failed'
|
|
329
|
+
| 'timeout'
|
|
330
|
+
| 'exception';
|
|
289
331
|
|
|
290
332
|
/** The reason set, in the order the spec lists it. Iterable for contract tests and dashboards. */
|
|
291
333
|
export const CONNECT_BLOCK_REASONS = [
|
|
@@ -294,8 +336,31 @@ export const CONNECT_BLOCK_REASONS = [
|
|
|
294
336
|
'failed',
|
|
295
337
|
'returned_without_account',
|
|
296
338
|
'personal_profile',
|
|
339
|
+
'api_failed',
|
|
340
|
+
'timeout',
|
|
341
|
+
'exception',
|
|
297
342
|
] as const satisfies readonly ConnectBlockReason[];
|
|
298
343
|
|
|
344
|
+
// 164 G007: covers OAuthCallbackPage's social sign-in branch (as opposed to
|
|
345
|
+
// ConnectBlockReason, which covers its account-connect branch) -- distinct
|
|
346
|
+
// enums because the two flows fail for different reasons.
|
|
347
|
+
export type SignInFailureReason =
|
|
348
|
+
| 'canceled' // provider returned ?error= (user denied / backed out)
|
|
349
|
+
| 'missing_code' // callback landed with no ?code= at all
|
|
350
|
+
| 'invalid_provider' // :provider route param missing/unrecognized
|
|
351
|
+
| 'no_email' // provider granted no email (Facebook/TikTok phone-only) -- AD2-1252
|
|
352
|
+
| 'api_failed' // socialSignIn() resolved without an accessToken
|
|
353
|
+
| 'exception'; // socialSignIn() itself threw
|
|
354
|
+
|
|
355
|
+
export const SIGN_IN_FAILURE_REASONS = [
|
|
356
|
+
'canceled',
|
|
357
|
+
'missing_code',
|
|
358
|
+
'invalid_provider',
|
|
359
|
+
'no_email',
|
|
360
|
+
'api_failed',
|
|
361
|
+
'exception',
|
|
362
|
+
] as const satisfies readonly SignInFailureReason[];
|
|
363
|
+
|
|
299
364
|
export type PublishFailureReason =
|
|
300
365
|
| 'auth' // token expired / revoked / missing scope / permission denied
|
|
301
366
|
| 'rate_limit' // provider throttled us (429 / quota)
|
|
@@ -352,6 +417,7 @@ export interface EventProperties {
|
|
|
352
417
|
// 080: 1 = first-ever account, 2 = second… — the "second action" activation hook.
|
|
353
418
|
connection_ordinal?: number;
|
|
354
419
|
};
|
|
420
|
+
[EVENTS.SOCIAL_SIGN_IN_FAILED]: { platform?: string; reason: SignInFailureReason };
|
|
355
421
|
[EVENTS.COMPOSE_STARTED]: { source?: 'wizard' | 'quick' };
|
|
356
422
|
[EVENTS.COMPOSE_STEP_VIEWED]: { step: ComposeStep };
|
|
357
423
|
[EVENTS.COMPOSE_BLOCKED]: {
|
|
@@ -419,6 +485,9 @@ export interface EventProperties {
|
|
|
419
485
|
trigger_source?: TriggerSource; // 080
|
|
420
486
|
};
|
|
421
487
|
[EVENTS.SUBSCRIPTION_CANCELED]: { plan: string; reason?: string };
|
|
488
|
+
[EVENTS.BILLING_PORTAL_OPENED]: { plan: string };
|
|
489
|
+
[EVENTS.SUBSCRIPTION_REACTIVATED]: { plan: string };
|
|
490
|
+
[EVENTS.CHECKOUT_CONFIRMATION_TIMED_OUT]: { had_session_id: boolean };
|
|
422
491
|
|
|
423
492
|
// ── Measurement baseline (080) — new event property shapes ──────────────────
|
|
424
493
|
// Paywall surfaces (frontend). hit_number is the analytical truth (an event
|
|
@@ -511,6 +580,12 @@ export interface EventProperties {
|
|
|
511
580
|
|
|
512
581
|
// Settings — which preference toggled and its new state.
|
|
513
582
|
[EVENTS.NOTIFICATION_PREFERENCES_UPDATED]: { preference?: string; enabled?: boolean; success: boolean };
|
|
583
|
+
[EVENTS.THEME_CHANGED]: { mode: 'light' | 'dark' };
|
|
584
|
+
[EVENTS.FEEDBACK_SUBMITTED]: { has_screenshot: boolean };
|
|
585
|
+
[EVENTS.ACCOUNT_DELETED]: { tier: string };
|
|
586
|
+
[EVENTS.LANGUAGE_CHANGED]: { language: 'en' | 'pl' };
|
|
587
|
+
[EVENTS.COMMENT_GUARD_MODE_CHANGED]: { mode: 'off' | 'flag-only' | 'auto-ignore' };
|
|
588
|
+
[EVENTS.AI_TOOLS_REVOKE_REQUESTED]: Record<string, never>;
|
|
514
589
|
|
|
515
590
|
// Native session lifecycle (159, contract session-reports.md). `outcome`
|
|
516
591
|
// covers all three launch reports in one event (restored / none / degraded)
|