@wireai/activation 0.13.0 → 0.13.2
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/AGENTS.md +3 -1
- package/CHANGELOG.md +116 -3
- package/README.md +82 -0
- package/dist/analytics/index.d.mts +2 -2
- package/dist/analytics/index.d.ts +2 -2
- package/dist/analytics/index.js +61 -2
- package/dist/analytics/index.js.map +1 -1
- package/dist/analytics/index.mjs +61 -2
- package/dist/analytics/index.mjs.map +1 -1
- package/dist/{currentSession-_GynvhzT.d.mts → currentSession-ClkLjcJ0.d.mts} +298 -13
- package/dist/{currentSession-D7zabMXK.d.ts → currentSession-DOVZEWJl.d.ts} +298 -13
- package/dist/index.d.mts +195 -4
- package/dist/index.d.ts +195 -4
- package/dist/index.js +909 -340
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +693 -145
- package/dist/index.mjs.map +1 -1
- package/dist/questionnaire/index.js.map +1 -1
- package/dist/questionnaire/index.mjs.map +1 -1
- package/dist/reviews/index.js +12 -1
- package/dist/reviews/index.js.map +1 -1
- package/dist/reviews/index.mjs +12 -1
- package/dist/reviews/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/OnboardingFlow.tsx +131 -0
- package/src/WireOnboarding.tsx +63 -2
- package/src/analytics/analyticsEvent.ts +16 -1
- package/src/analytics/eventQueue.ts +2 -0
- package/src/analytics/reportClientEvent.ts +68 -12
- package/src/cards/PermissionCard.tsx +438 -0
- package/src/cards/index.ts +7 -0
- package/src/illustrations/defaultIllustrations.tsx +44 -3
- package/src/index.ts +38 -0
- package/src/permissions/index.ts +64 -0
- package/src/permissions/permissionCopy.ts +87 -0
- package/src/permissions/permissionEvents.ts +76 -0
- package/src/permissions/permissionMemory.ts +88 -0
- package/src/permissions/placement.ts +88 -0
- package/src/permissions/types.ts +131 -0
- package/src/session/persistedSession.ts +10 -3
- package/src/types.ts +66 -4
|
@@ -153,15 +153,24 @@ type ClientEvent = {
|
|
|
153
153
|
*/
|
|
154
154
|
user_id?: string;
|
|
155
155
|
/**
|
|
156
|
-
* Client-stamped
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
156
|
+
* Client-stamped timestamp of when the event was ENQUEUED on the device. Optional and ADDITIVE.
|
|
157
|
+
*
|
|
158
|
+
* TWO REPRESENTATIONS, on purpose:
|
|
159
|
+
* - INTERNAL (epoch-ms `number`): what the offline queue stamps at enqueue time (see
|
|
160
|
+
* `createEventQueue`), so two otherwise byte-identical events fired seconds apart (a genuine
|
|
161
|
+
* repeat, e.g. the user taps "share" twice) are NOT collapsed by the queue's identical-JSON
|
|
162
|
+
* de-dup — while two truly simultaneous re-enqueues of the same instant (a redundant
|
|
163
|
+
* re-render) still share a `ts` and collapse. The de-dup signature depends on this number.
|
|
164
|
+
* - WIRE (ISO8601 UTC `string`): what actually leaves the device. {@link buildEventsRequest}
|
|
165
|
+
* converts the number on its way out, because the server declares `ts: str | None` and
|
|
166
|
+
* pydantic v2 does NOT coerce a number into it — a numeric `ts` made the server answer HTTP
|
|
167
|
+
* 200 with `{written: 0, skipped: N, errors: [{field: "ts", reason: "validation_error"}]}`,
|
|
168
|
+
* silently discarding EVERY `app_event` through 0.13.0.
|
|
169
|
+
*
|
|
170
|
+
* A caller-set ISO string is passed through as-is. Never a wall-clock the server trusts (it
|
|
171
|
+
* derives its own receive time); an old/strict server that does not model it ignores the field.
|
|
163
172
|
*/
|
|
164
|
-
ts?: number;
|
|
173
|
+
ts?: number | string;
|
|
165
174
|
};
|
|
166
175
|
/** Where to POST. Derived from `WireOnboardingConfig` (`serverUrl` + `apiKey`). */
|
|
167
176
|
type ClientEventTarget = {
|
|
@@ -206,6 +215,222 @@ declare const reportClientEventsAwait: (target: ClientEventTarget | undefined, e
|
|
|
206
215
|
/** Convenience single-event wrapper around {@link reportClientEventsAwait}. */
|
|
207
216
|
declare const reportClientEventAwait: (target: ClientEventTarget | undefined, event: ClientEvent) => Promise<boolean>;
|
|
208
217
|
|
|
218
|
+
/**
|
|
219
|
+
* permissionCopy - the rationale a priming screen shows, and the kit-quality defaults behind it.
|
|
220
|
+
*
|
|
221
|
+
* Same job the `copy` prop does for the loaders: the kit ships English that is good enough to
|
|
222
|
+
* ship as-is, and a host overrides any single line without having to restate the rest.
|
|
223
|
+
*
|
|
224
|
+
* PURE, no React, no React Native - so the resolution is unit-testable and the same function can
|
|
225
|
+
* later resolve copy a server sent.
|
|
226
|
+
*/
|
|
227
|
+
|
|
228
|
+
/** Every string a permission screen can render. Both states (the ask and the blocked route). */
|
|
229
|
+
type PermissionScreenCopy = {
|
|
230
|
+
/** Headline for the ask. */
|
|
231
|
+
title: string;
|
|
232
|
+
/** The rationale. The one thing that decides whether the primary gets tapped. */
|
|
233
|
+
message: string;
|
|
234
|
+
/** Primary button. Tapping it is the ONLY thing that can open the OS dialog. */
|
|
235
|
+
primaryLabel: string;
|
|
236
|
+
/** Secondary button. Advances the flow and does NOT spend the one native prompt. */
|
|
237
|
+
secondaryLabel: string;
|
|
238
|
+
/** Headline once the OS says the permission is permanently refused. */
|
|
239
|
+
blockedTitle: string;
|
|
240
|
+
/** Rationale for the blocked state, where the only remaining route is the settings app. */
|
|
241
|
+
blockedMessage: string;
|
|
242
|
+
/** Primary button on the blocked route. */
|
|
243
|
+
settingsLabel: string;
|
|
244
|
+
/** Primary button when there is nothing left to ask for (already granted, or no `request`). */
|
|
245
|
+
continueLabel: string;
|
|
246
|
+
};
|
|
247
|
+
/**
|
|
248
|
+
* The notification default. Written to the same bar as the kit's other shipped copy: it names the
|
|
249
|
+
* benefit in the user's terms and promises a limit, because "we would like to send you
|
|
250
|
+
* notifications" is exactly the sentence that spends the one iOS prompt on a no.
|
|
251
|
+
*/
|
|
252
|
+
declare const NOTIFICATIONS_PERMISSION_COPY: PermissionScreenCopy;
|
|
253
|
+
/** The fallback for any permission the kit ships no copy for. A host overriding `copy` replaces it. */
|
|
254
|
+
declare const GENERIC_PERMISSION_COPY: PermissionScreenCopy;
|
|
255
|
+
/** The kit's shipped defaults, by permission. Anything not listed falls back to the generic set. */
|
|
256
|
+
declare const DEFAULT_PERMISSION_COPY: Record<string, PermissionScreenCopy>;
|
|
257
|
+
/**
|
|
258
|
+
* The kit default for `permission`, with the host's overrides applied on top.
|
|
259
|
+
*
|
|
260
|
+
* An override key whose value is `undefined` is IGNORED rather than allowed to blank the default:
|
|
261
|
+
* hosts build this object from their i18n layer, and a missing translation resolving to `undefined`
|
|
262
|
+
* would otherwise render an empty button.
|
|
263
|
+
*/
|
|
264
|
+
declare const resolvePermissionCopy: (permission: WirePermissionKind, overrides?: Partial<PermissionScreenCopy>) => PermissionScreenCopy;
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Public types for the injectable mid-flow permission screens.
|
|
268
|
+
*
|
|
269
|
+
* THE ONE RULE THIS WHOLE MODULE EXISTS TO ENFORCE: the OS permission dialog is reached from the
|
|
270
|
+
* user's explicit primary tap and from nowhere else. iOS grants an app exactly ONE native
|
|
271
|
+
* notification prompt for its entire lifetime, so firing it on mount (the shape almost every app
|
|
272
|
+
* ships) spends the single ask on a user who has not been told why. A priming screen spends a
|
|
273
|
+
* cheap in-app screen first and only forwards the ones who said yes.
|
|
274
|
+
*
|
|
275
|
+
* DEPENDENCY-FREE, the RevenueCat-bridge idiom: the kit imports NO native permission module. The
|
|
276
|
+
* host passes `request` (and optionally `getStatus` / `openSettings`) in, exactly the way it hands
|
|
277
|
+
* the RevenueCat bridge real `CustomerInfo` objects. Wiring `expo-notifications` is five lines in
|
|
278
|
+
* the host and zero dependencies here.
|
|
279
|
+
*
|
|
280
|
+
* A PERMISSION SCREEN IS NOT A QUESTION. It mints no `key` and no `slot_id`, sends nothing to the
|
|
281
|
+
* backend, and never enters the thread, so `deriveAnswers` / `readProgress` and every completion
|
|
282
|
+
* semantic are byte-identical whether or not one is configured. Completion NEVER blocks on a grant.
|
|
283
|
+
*/
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Which OS permission a screen primes. `notifications` is the one the kit ships copy for; any other
|
|
287
|
+
* string is accepted (the host supplies the copy) so a host can prime tracking, health or location
|
|
288
|
+
* without waiting on a kit release.
|
|
289
|
+
*/
|
|
290
|
+
type WirePermissionKind = "notifications" | (string & {});
|
|
291
|
+
/**
|
|
292
|
+
* What the host's `request` / `getStatus` answers.
|
|
293
|
+
* - `granted`: the user allowed it.
|
|
294
|
+
* - `denied`: not allowed right now, but the OS would still show a dialog if asked again
|
|
295
|
+
* (Android, or an iOS provisional state).
|
|
296
|
+
* - `blocked`: permanently refused. Asking again shows NOTHING, so the only route left is Settings.
|
|
297
|
+
*/
|
|
298
|
+
type WirePermissionStatus = "granted" | "denied" | "blocked";
|
|
299
|
+
/** How a permission screen ENDED. `skipped` is the secondary tap, which never burns the OS prompt. */
|
|
300
|
+
type WirePermissionOutcome = WirePermissionStatus | "skipped";
|
|
301
|
+
/**
|
|
302
|
+
* The moments a permission screen reports. One stage, one canonical event name (see
|
|
303
|
+
* `permissionEventName`), so the funnel reads the same across every tenant.
|
|
304
|
+
* - `shown`: the primer screen became visible. The denominator.
|
|
305
|
+
* - `accepted`: the user tapped the primary, so the OS dialog is ABOUT to open. The gap between
|
|
306
|
+
* `shown` and `accepted` is the only number that tells a host whether its rationale copy works.
|
|
307
|
+
* - `granted` / `denied`: what the OS answered (a `blocked` answer reports as `denied` and carries
|
|
308
|
+
* `status: "blocked"`, so the two are one funnel step and still distinguishable).
|
|
309
|
+
* - `skipped`: the secondary tap. The prompt was NOT spent.
|
|
310
|
+
* - `settings`: the user was redirected to the OS settings page (the `blocked` route).
|
|
311
|
+
*/
|
|
312
|
+
type PermissionStage = "shown" | "accepted" | "granted" | "denied" | "skipped" | "settings";
|
|
313
|
+
/**
|
|
314
|
+
* WHERE a screen sits in the server-driven stream. The flow length is decided by the backend and
|
|
315
|
+
* varies per user, so every position is resolved against the card the flow is ABOUT to render:
|
|
316
|
+
* - `"start"`: before the first card.
|
|
317
|
+
* - `{ afterCard: n }`: after `n` cards have been shown (`{ afterCard: 2 }` sits between card 2
|
|
318
|
+
* and card 3). An `n` past the end of a shorter-than-expected stream degrades to `"beforeEnd"`
|
|
319
|
+
* rather than silently never showing.
|
|
320
|
+
* - `"beforeEnd"`: immediately before the terminal recap. The default.
|
|
321
|
+
*/
|
|
322
|
+
type PermissionPlacement = "start" | "beforeEnd" | {
|
|
323
|
+
afterCard: number;
|
|
324
|
+
};
|
|
325
|
+
/**
|
|
326
|
+
* One injectable permission screen.
|
|
327
|
+
*
|
|
328
|
+
* ```tsx
|
|
329
|
+
* import * as Notifications from "expo-notifications";
|
|
330
|
+
*
|
|
331
|
+
* <WireOnboarding
|
|
332
|
+
* permissionScreens={[
|
|
333
|
+
* {
|
|
334
|
+
* permission: "notifications",
|
|
335
|
+
* placement: "beforeEnd",
|
|
336
|
+
* request: async () => {
|
|
337
|
+
* const { status, canAskAgain } = await Notifications.requestPermissionsAsync();
|
|
338
|
+
* return status === "granted" ? "granted" : canAskAgain ? "denied" : "blocked";
|
|
339
|
+
* },
|
|
340
|
+
* },
|
|
341
|
+
* ]}
|
|
342
|
+
* />
|
|
343
|
+
* ```
|
|
344
|
+
*/
|
|
345
|
+
type PermissionScreenConfig = {
|
|
346
|
+
/** Which permission this screen primes. Drives the default copy and every event's `permission`. */
|
|
347
|
+
permission: WirePermissionKind;
|
|
348
|
+
/**
|
|
349
|
+
* Stable id for the once-only / resume record. Defaults to `<permission>:<index in the array>`,
|
|
350
|
+
* which is right until you reorder the array; set it explicitly if you configure two screens for
|
|
351
|
+
* the same permission or intend to reorder them between releases.
|
|
352
|
+
*/
|
|
353
|
+
id?: string;
|
|
354
|
+
/** Where the screen sits in the stream. Default `"beforeEnd"`. */
|
|
355
|
+
placement?: PermissionPlacement;
|
|
356
|
+
/**
|
|
357
|
+
* THE ONLY FUNCTION THE KIT CALLS THAT CAN OPEN AN OS DIALOG, and it is called from the primary
|
|
358
|
+
* press handler alone. Never from mount, never from an effect, never from a status probe.
|
|
359
|
+
*/
|
|
360
|
+
request: () => Promise<WirePermissionStatus>;
|
|
361
|
+
/**
|
|
362
|
+
* Optional NON-PROMPTING status read (`Notifications.getPermissionsAsync()`), used only to pick
|
|
363
|
+
* which primary action the screen offers: a `blocked` user gets "Open settings" instead of an
|
|
364
|
+
* "Enable" button that would open nothing, and an already-granted user gets a plain Continue.
|
|
365
|
+
* Leave it out and the screen simply always offers the ask.
|
|
366
|
+
*/
|
|
367
|
+
getStatus?: () => Promise<WirePermissionStatus>;
|
|
368
|
+
/** Open the OS settings page (`Linking.openSettings()`). Only reachable on the `blocked` route. */
|
|
369
|
+
openSettings?: () => void | Promise<void>;
|
|
370
|
+
/** Rationale copy overrides. Anything left out keeps the kit default for this permission. */
|
|
371
|
+
copy?: Partial<PermissionScreenCopy>;
|
|
372
|
+
/**
|
|
373
|
+
* Name of a host illustration (the existing `illustrations` registry). Defaults to the
|
|
374
|
+
* permission name, so registering `illustrations={{ notifications: <MyBell/> }}` is enough. The
|
|
375
|
+
* kit ships a dependency-free default so the screen is never a blank box.
|
|
376
|
+
*/
|
|
377
|
+
illustration?: string;
|
|
378
|
+
/**
|
|
379
|
+
* Fired once, with the outcome this screen produced. This is the seam for scheduling a local
|
|
380
|
+
* notification the moment a grant lands; the kit deliberately schedules nothing itself. It can
|
|
381
|
+
* never break the flow: a throw here is caught and the flow continues.
|
|
382
|
+
*/
|
|
383
|
+
onResult?: (permission: WirePermissionKind, outcome: WirePermissionOutcome) => void;
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* permissionEvents - the canonical Wire names for a permission-priming funnel.
|
|
388
|
+
*
|
|
389
|
+
* Same job `purchaseEvents.ts` does for the subscription funnel and `analyticsEvent.ts` does for
|
|
390
|
+
* the onboarding funnel: every app was naming these itself (`push_permission`, `NOTIF_PROMPT`,
|
|
391
|
+
* `notifications_allowed`), so the same funnel read differently per tenant and no cross-app report
|
|
392
|
+
* was possible. These are the ONE set of names.
|
|
393
|
+
*
|
|
394
|
+
* They are `app_event` `question_key` values on the wire, exactly like `WIRE_PURCHASE_EVENTS`, so
|
|
395
|
+
* they are ALSO the exact strings a review / questionnaire firing trigger matches on. Never rename
|
|
396
|
+
* one: a rename silently unfires every trigger configured against the old string.
|
|
397
|
+
*
|
|
398
|
+
* PURE + dependency-free: no React, no React Native, no transport, no imports outside the type
|
|
399
|
+
* declarations - so an analytics-only bundle can carry the names without carrying a screen.
|
|
400
|
+
*/
|
|
401
|
+
|
|
402
|
+
declare const WIRE_PERMISSION_EVENTS: {
|
|
403
|
+
/** The priming screen became visible. The denominator for every rate below. */
|
|
404
|
+
readonly screenShown: "wire_permission_screen_shown";
|
|
405
|
+
/** The user tapped the primary, so the OS dialog is about to open. The rationale worked. */
|
|
406
|
+
readonly primerAccepted: "wire_permission_primer_accepted";
|
|
407
|
+
/** The OS granted it. */
|
|
408
|
+
readonly granted: "wire_permission_granted";
|
|
409
|
+
/** The OS refused it (a permanently blocked answer reports here too, with `status: "blocked"`). */
|
|
410
|
+
readonly denied: "wire_permission_denied";
|
|
411
|
+
/** The user took the secondary. The one native prompt was NOT spent. */
|
|
412
|
+
readonly skipped: "wire_permission_skipped";
|
|
413
|
+
/** A blocked user was redirected to the OS settings page. */
|
|
414
|
+
readonly settingsOpened: "wire_permission_settings_opened";
|
|
415
|
+
};
|
|
416
|
+
type WirePermissionEventName = (typeof WIRE_PERMISSION_EVENTS)[keyof typeof WIRE_PERMISSION_EVENTS];
|
|
417
|
+
/** The canonical event name for a stage. Exhaustive over the union (a new stage is a compile error). */
|
|
418
|
+
declare const permissionEventName: (stage: PermissionStage) => WirePermissionEventName;
|
|
419
|
+
/**
|
|
420
|
+
* The small, non-PII props that ride a permission event. `permission` is always present so one
|
|
421
|
+
* funnel can be sliced per permission; `status` only appears when the OS actually answered, which
|
|
422
|
+
* is what keeps a `blocked` refusal distinguishable from a plain `denied` without a second event.
|
|
423
|
+
*/
|
|
424
|
+
declare const permissionEventProps: (permission: WirePermissionKind, status?: WirePermissionStatus) => Record<string, string>;
|
|
425
|
+
/**
|
|
426
|
+
* Normalize whatever the host's `request` / `getStatus` actually returned.
|
|
427
|
+
*
|
|
428
|
+
* A native permission bridge is the host's code, and hosts return `"undetermined"`, `true`, or a
|
|
429
|
+
* whole Expo response object. Anything the kit does not recognise is treated as `denied`: it is the
|
|
430
|
+
* only reading that cannot invent a grant, and every outcome continues the flow anyway.
|
|
431
|
+
*/
|
|
432
|
+
declare const normalizePermissionStatus: (value: unknown) => WirePermissionStatus;
|
|
433
|
+
|
|
209
434
|
/** Transport + tenant config for the managed Wire AI onboarding backend (A2A). */
|
|
210
435
|
type WireOnboardingConfig = {
|
|
211
436
|
/** Tenant API key (resolves the app server-side). */
|
|
@@ -263,6 +488,10 @@ type OnboardingResult = {
|
|
|
263
488
|
* - `fallback`: retries are exhausted; the kit degraded to the static `fallbackFlow`
|
|
264
489
|
* (or handed off to `onError`). This is the client-side mirror of the
|
|
265
490
|
* backend's `llm_fallback` reliability event.
|
|
491
|
+
* - `permission`: an injected permission screen moved (`shown` / `accepted` / `granted` /
|
|
492
|
+
* `denied` / `skipped` / `settings`). Carries NO funnel weight: a permission screen
|
|
493
|
+
* is not a question, so it never appears in `answers` and never gates completion.
|
|
494
|
+
* `toAnalyticsEvent` maps it to the canonical `wire_permission_*` name.
|
|
266
495
|
*/
|
|
267
496
|
type OnboardingEvent = {
|
|
268
497
|
type: "started";
|
|
@@ -284,6 +513,12 @@ type OnboardingEvent = {
|
|
|
284
513
|
} | {
|
|
285
514
|
type: "fallback";
|
|
286
515
|
reason: "backend" | "timeout";
|
|
516
|
+
} | {
|
|
517
|
+
type: "permission";
|
|
518
|
+
permission: WirePermissionKind;
|
|
519
|
+
stage: PermissionStage;
|
|
520
|
+
/** What the OS actually answered, when it answered. Absent on `shown` / `accepted`. */
|
|
521
|
+
status?: WirePermissionStatus;
|
|
287
522
|
};
|
|
288
523
|
/**
|
|
289
524
|
* Copy overrides for the kit's built-in (English) strings, so a host can localize
|
|
@@ -328,6 +563,48 @@ type WireOnboardingProps = {
|
|
|
328
563
|
icons?: Record<string, React.ReactNode>;
|
|
329
564
|
/** Per-step validators keyed by base-question key, e.g. `{ username: checkUsername }`. */
|
|
330
565
|
validators?: Record<string, StepValidator>;
|
|
566
|
+
/**
|
|
567
|
+
* PERMISSION SCREENS injected into the server-driven flow at a position you choose.
|
|
568
|
+
*
|
|
569
|
+
* The screen explains why the app wants the permission and asks the OS **only** on the primary
|
|
570
|
+
* tap. That priming pattern is not decoration: iOS grants an app exactly ONE native notification
|
|
571
|
+
* prompt for its whole lifetime, and firing it on mount spends it on a user who was told nothing.
|
|
572
|
+
* "Maybe later" advances the flow with the prompt still unspent.
|
|
573
|
+
*
|
|
574
|
+
* ```tsx
|
|
575
|
+
* import * as Notifications from "expo-notifications";
|
|
576
|
+
*
|
|
577
|
+
* <WireOnboarding
|
|
578
|
+
* permissionScreens={[
|
|
579
|
+
* {
|
|
580
|
+
* permission: "notifications",
|
|
581
|
+
* placement: "beforeEnd",
|
|
582
|
+
* request: async () => {
|
|
583
|
+
* const { status, canAskAgain } = await Notifications.requestPermissionsAsync();
|
|
584
|
+
* return status === "granted" ? "granted" : canAskAgain ? "denied" : "blocked";
|
|
585
|
+
* },
|
|
586
|
+
* getStatus: async () => {
|
|
587
|
+
* const { status, canAskAgain } = await Notifications.getPermissionsAsync();
|
|
588
|
+
* return status === "granted" ? "granted" : canAskAgain ? "denied" : "blocked";
|
|
589
|
+
* },
|
|
590
|
+
* onResult: (_p, outcome) => { if (outcome === "granted") scheduleFirstReminder(); },
|
|
591
|
+
* },
|
|
592
|
+
* ]}
|
|
593
|
+
* />
|
|
594
|
+
* ```
|
|
595
|
+
*
|
|
596
|
+
* The kit adds NO dependency for this: it imports no `expo-notifications`, no
|
|
597
|
+
* `react-native-permissions`, nothing native. You inject `request`, exactly the way you hand the
|
|
598
|
+
* RevenueCat bridge real RevenueCat objects.
|
|
599
|
+
*
|
|
600
|
+
* A permission screen is NOT a question. It sends nothing to the backend, never enters the
|
|
601
|
+
* thread, mints no `key` / `slot_id`, and never appears in `onComplete`'s `answers`. Completion
|
|
602
|
+
* never blocks on a grant: grant, deny, skip and blocked all continue the flow. Each screen is
|
|
603
|
+
* shown at most once per session, and with `storage` that survives an app kill (a resumed session
|
|
604
|
+
* does not re-ask). Analytics ride `onEvent` (`type: "permission"`) and the canonical
|
|
605
|
+
* `wire_permission_*` events, stamped with the same `device_key` as the rest of the funnel.
|
|
606
|
+
*/
|
|
607
|
+
permissionScreens?: PermissionScreenConfig[];
|
|
331
608
|
/** Fired once the flow reaches its terminal StatusCard. */
|
|
332
609
|
onComplete: (result: OnboardingResult) => void;
|
|
333
610
|
/**
|
|
@@ -490,9 +767,12 @@ type OnboardingProgress = {
|
|
|
490
767
|
* `answers.interests` silently becomes `answers.what_are_you_into_v2`, with no error anywhere. A
|
|
491
768
|
* slot is the question's identity independent of its wording.
|
|
492
769
|
*
|
|
493
|
-
* FULLY ADDITIVE AND
|
|
494
|
-
*
|
|
495
|
-
*
|
|
770
|
+
* FULLY ADDITIVE, AND LIVE SINCE 2026-07-28 (server `47dae92`). The deployed server sends it on
|
|
771
|
+
* `progress` for every AI-GENERATED question, as `adaptive_<n>` 1-based over adaptive answers, and
|
|
772
|
+
* for a CONFIGURED question only when the tenant set one. Where the tenant set none the field is
|
|
773
|
+
* simply absent. Every fallback is PER-CARD, so a thread that mixes slotted and unslotted cards
|
|
774
|
+
* (the real shape during a rollout) keys each one correctly, and a backend or tenant that never
|
|
775
|
+
* sends it produces byte-identical behaviour to 0.12.2.
|
|
496
776
|
*/
|
|
497
777
|
slot_id?: string;
|
|
498
778
|
/** Whether the CURRENT screen may be skipped (backend-marked; default false → no Skip shown). */
|
|
@@ -530,7 +810,12 @@ declare const WIRE_ONBOARDING_EVENTS: {
|
|
|
530
810
|
};
|
|
531
811
|
type WireOnboardingEventName = (typeof WIRE_ONBOARDING_EVENTS)[keyof typeof WIRE_ONBOARDING_EVENTS];
|
|
532
812
|
type AnalyticsEvent = {
|
|
533
|
-
|
|
813
|
+
/**
|
|
814
|
+
* A permission screen maps to its own canonical `wire_permission_*` name rather than to an
|
|
815
|
+
* onboarding one: it is a distinct funnel (see `permissions/permissionEvents.ts`), and folding it
|
|
816
|
+
* into `wire_onboarding_turn` would make every permission rate unreadable.
|
|
817
|
+
*/
|
|
818
|
+
name: WireOnboardingEventName | WirePermissionEventName;
|
|
534
819
|
params?: Record<string, unknown>;
|
|
535
820
|
};
|
|
536
821
|
/**
|
|
@@ -1100,4 +1385,4 @@ declare const resetCurrentSessionId: () => void;
|
|
|
1100
1385
|
*/
|
|
1101
1386
|
declare const ensureCurrentSessionId: () => string;
|
|
1102
1387
|
|
|
1103
|
-
export {
|
|
1388
|
+
export { type IdentifyOnboardingOptions as $, AUTO_DEVICE_ID_PREFIX as A, resetEventQueueKeys as B, type ClearUserContextOptions as C, type DeviceKeyStorage as D, type EventQueueOptions as E, resolveAutoDeviceKey as F, setCurrentSessionId as G, toAnalyticsEvent as H, type WireOnboardingProps as I, type WireOnboardingConfig as J, type WirePermissionStatus as K, type WirePermissionOutcome as L, type OnboardingEvent as M, type OnboardingCopy as N, type OnboardingResult as O, type PermissionStage as P, type DeviceContext as Q, type ResolveAutoDeviceKeyOptions as R, type StepValidator as S, type PermissionScreenConfig as T, type PermissionPlacement as U, DEFAULT_PERMISSION_COPY as V, type WireUserContext as W, type DeviceFormFactor as X, EXTRA_KEY_PREFIX as Y, GENERIC_PERMISSION_COPY as Z, type IdentifyOnboardingBinding as _, type AnalyticsEvent as a, type IdentityRecord as a0, type IdentitySource as a1, type IdentitySpace as a2, NOTIFICATIONS_PERMISSION_COPY as a3, type OnboardingProgress as a4, type PermissionScreenCopy as a5, type ResolveUserContextOptions as a6, type ResolvedUserContext as a7, USER_ID_MAX_LENGTH as a8, WIRE_PERMISSION_EVENTS as a9, type WirePermissionEventName as aa, type WirePermissionKind as ab, activationJoinContext as ac, collectDeviceContext as ad, hashEmailFnv1a as ae, hostIdentity as af, hydrateAutoDeviceKey as ag, hydrateDeviceIdentity as ah, identifyOnboarding as ai, isWireScalar as aj, mintDeviceId as ak, namespaceExtra as al, normalizePermissionStatus as am, permissionEventName as an, permissionEventProps as ao, resetIdentityProvenance as ap, resolveIdentity as aq, resolvePermissionCopy as ar, resolveUserContext as as, sanitizeUserId as at, type ClientEvent as b, type ClientEventTarget as c, type ClientEventType as d, type ContextEnvelope as e, type ContextEnvelopeInput as f, type EnvelopeSource as g, type EventQueue as h, WIRE_ONBOARDING_EVENTS as i, type WireOnboardingEventName as j, analyticsUserIdStorageKey as k, buildContextEnvelope as l, clearPiiFromContext as m, clearUserContext as n, createEventQueue as o, deviceIdStorageKey as p, ensureCurrentSessionId as q, getCurrentSessionId as r, looksLikeEmail as s, makeSessionId as t, reportClientEvent as u, reportClientEventAwait as v, reportClientEvents as w, reportClientEventsAwait as x, resetAutoDeviceKeys as y, resetCurrentSessionId as z };
|