@wireai/activation 0.13.0 → 0.13.3

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.
Files changed (82) hide show
  1. package/AGENTS.md +7 -4
  2. package/CHANGELOG.md +341 -4
  3. package/README.md +98 -2
  4. package/dist/analytics/index.d.mts +4 -4
  5. package/dist/analytics/index.d.ts +4 -4
  6. package/dist/analytics/index.js +164 -471
  7. package/dist/analytics/index.js.map +1 -1
  8. package/dist/analytics/index.mjs +162 -469
  9. package/dist/analytics/index.mjs.map +1 -1
  10. package/dist/coachmarks/index.d.mts +14 -1
  11. package/dist/coachmarks/index.d.ts +14 -1
  12. package/dist/coachmarks/index.js +58 -14
  13. package/dist/coachmarks/index.js.map +1 -1
  14. package/dist/coachmarks/index.mjs +58 -14
  15. package/dist/coachmarks/index.mjs.map +1 -1
  16. package/dist/{currentSession-_GynvhzT.d.mts → currentSession-BoWtr3Jp.d.mts} +299 -14
  17. package/dist/{currentSession-D7zabMXK.d.ts → currentSession-CCOMlaQ2.d.ts} +299 -14
  18. package/dist/{decision-Bl_M2y3r.d.mts → decision-Cjw0jbEj.d.mts} +1 -1
  19. package/dist/{decision-Cau5KmP6.d.ts → decision-GfpXAsk_.d.ts} +1 -1
  20. package/dist/index.d.mts +218 -4
  21. package/dist/index.d.ts +218 -4
  22. package/dist/index.js +1104 -888
  23. package/dist/index.js.map +1 -1
  24. package/dist/index.mjs +887 -692
  25. package/dist/index.mjs.map +1 -1
  26. package/dist/questionnaire/index.d.mts +42 -10
  27. package/dist/questionnaire/index.d.ts +42 -10
  28. package/dist/questionnaire/index.js +45 -13
  29. package/dist/questionnaire/index.js.map +1 -1
  30. package/dist/questionnaire/index.mjs +45 -13
  31. package/dist/questionnaire/index.mjs.map +1 -1
  32. package/dist/reviews/index.d.mts +4 -4
  33. package/dist/reviews/index.d.ts +4 -4
  34. package/dist/reviews/index.js +39 -8
  35. package/dist/reviews/index.js.map +1 -1
  36. package/dist/reviews/index.mjs +39 -8
  37. package/dist/reviews/index.mjs.map +1 -1
  38. package/dist/showcase/index.js +7 -2
  39. package/dist/showcase/index.js.map +1 -1
  40. package/dist/showcase/index.mjs +7 -2
  41. package/dist/showcase/index.mjs.map +1 -1
  42. package/dist/{transport-DsRe4epC.d.ts → transport-CefdERPs.d.mts} +44 -5
  43. package/dist/{transport-CF_eHwzC.d.mts → transport-DFuPyCoJ.d.ts} +44 -5
  44. package/dist/{types-Buj9Lw9t.d.ts → types-UVYt9BJe.d.ts} +41 -1
  45. package/dist/{types-CNUqMK0D.d.mts → types-l2tfg23P.d.mts} +41 -1
  46. package/package.json +4 -1
  47. package/src/OnboardingFlow.tsx +175 -15
  48. package/src/WireOnboarding.tsx +70 -8
  49. package/src/activation/wireActivation.ts +3 -3
  50. package/src/analytics/analyticsEvent.ts +16 -1
  51. package/src/analytics/analyticsFacade.ts +2 -2
  52. package/src/analytics/eventQueue.ts +85 -20
  53. package/src/analytics/reportClientEvent.ts +68 -12
  54. package/src/cards/CardGridSelectCard.tsx +1 -1
  55. package/src/cards/ChipSelectCard.tsx +2 -2
  56. package/src/cards/PermissionCard.tsx +438 -0
  57. package/src/cards/SelectionCard.tsx +1 -1
  58. package/src/cards/index.ts +7 -0
  59. package/src/coachmarks/runtime.ts +62 -2
  60. package/src/coachmarks/useCoachmarkTour.ts +62 -14
  61. package/src/context/deviceId.ts +4 -4
  62. package/src/features/WireFeaturesProvider.tsx +4 -1
  63. package/src/identity/userIdentity.ts +1 -1
  64. package/src/illustrations/defaultIllustrations.tsx +44 -3
  65. package/src/index.ts +38 -0
  66. package/src/permissions/index.ts +64 -0
  67. package/src/permissions/permissionCopy.ts +87 -0
  68. package/src/permissions/permissionEvents.ts +76 -0
  69. package/src/permissions/permissionMemory.ts +88 -0
  70. package/src/permissions/placement.ts +88 -0
  71. package/src/permissions/types.ts +131 -0
  72. package/src/questionnaire/QuestionnaireGate.tsx +90 -10
  73. package/src/questionnaire/index.ts +9 -1
  74. package/src/questionnaire/transport.ts +46 -11
  75. package/src/reviews/ReviewGate.tsx +61 -6
  76. package/src/reviews/index.ts +10 -1
  77. package/src/reviews/transport.ts +58 -12
  78. package/src/session/persistedSession.ts +10 -3
  79. package/src/session-analytics/useLifecycleEvents.ts +1 -1
  80. package/src/theme/ThemeContext.tsx +42 -1
  81. package/src/types.ts +66 -4
  82. package/src/utils/submitResult.ts +39 -0
@@ -153,15 +153,24 @@ type ClientEvent = {
153
153
  */
154
154
  user_id?: string;
155
155
  /**
156
- * Client-stamped epoch-ms timestamp of when the event was ENQUEUED on the device. Optional and
157
- * ADDITIVE: the offline queue stamps it at enqueue time (see `createEventQueue`) so two otherwise
158
- * byte-identical events fired seconds apart (a genuine repeat, e.g. the user taps "share" twice)
159
- * are NOT collapsed by the queue's identical-JSON de-dup while two truly simultaneous
160
- * re-enqueues of the same instant (a redundant re-render) still share a `ts` and collapse. A raw
161
- * `Date.now()`, never a wall-clock the server trusts (the server derives its own receive time);
162
- * an old/strict server that does not model it simply ignores the unknown field.
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 CURRENTLY INERT: no server emits it yet. Every fallback is PER-CARD, so a
494
- * thread that mixes slotted and unslotted cards (the real shape during a rollout) keys each one
495
- * correctly, and a backend that never sends it produces byte-identical behaviour to 0.12.2.
770
+ * FULLY ADDITIVE, AND LIVE SINCE 2026-07-28. 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
- name: WireOnboardingEventName;
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
  /**
@@ -1057,7 +1342,7 @@ declare const hydrateAutoDeviceKey: (opts?: ResolveAutoDeviceKeyOptions) => Prom
1057
1342
  * The PROVENANCE-CARRYING sibling of {@link hydrateAutoDeviceKey}: the same awaited read, but it
1058
1343
  * answers "is this id one this install will KEEP?" instead of only "what is the id?".
1059
1344
  *
1060
- * WHY IT EXISTS (K1). `<WireOnboarding>` gated auto-injection on `Boolean(storage)` — the presence of
1345
+ * WHY IT EXISTS. `<WireOnboarding>` gated auto-injection on `Boolean(storage)` — the presence of
1061
1346
  * the prop — because a string carries no provenance and there was nothing better to gate on. A
1062
1347
  * REJECTING adapter therefore injected a fresh `wdev_*` on every launch: strictly worse than
1063
1348
  * injecting nothing, since the server counts `min_sessions` by distinct opens grouped on `device_key`,
@@ -1100,4 +1385,4 @@ declare const resetCurrentSessionId: () => void;
1100
1385
  */
1101
1386
  declare const ensureCurrentSessionId: () => string;
1102
1387
 
1103
- export { USER_ID_MAX_LENGTH 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 OnboardingEvent as K, type OnboardingCopy as L, type DeviceContext as M, type DeviceFormFactor as N, type OnboardingResult as O, EXTRA_KEY_PREFIX as P, type IdentifyOnboardingBinding as Q, type ResolveAutoDeviceKeyOptions as R, type StepValidator as S, type IdentifyOnboardingOptions as T, type IdentityRecord as U, type IdentitySource as V, type WireUserContext as W, type IdentitySpace as X, type OnboardingProgress as Y, type ResolveUserContextOptions as Z, type ResolvedUserContext as _, type AnalyticsEvent as a, activationJoinContext as a0, collectDeviceContext as a1, hashEmailFnv1a as a2, hostIdentity as a3, hydrateAutoDeviceKey as a4, hydrateDeviceIdentity as a5, identifyOnboarding as a6, isWireScalar as a7, mintDeviceId as a8, namespaceExtra as a9, resetIdentityProvenance as aa, resolveIdentity as ab, resolveUserContext as ac, sanitizeUserId as ad, 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 };
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 };
@@ -1,4 +1,4 @@
1
- import { c as ReviewDecision, j as ReviewSubmission, e as RatingRoute } from './types-CNUqMK0D.mjs';
1
+ import { c as ReviewDecision, j as ReviewSubmission, e as RatingRoute } from './types-l2tfg23P.mjs';
2
2
 
3
3
  /**
4
4
  * decision.ts — the pure decision logic for the review gate.
@@ -1,4 +1,4 @@
1
- import { c as ReviewDecision, j as ReviewSubmission, e as RatingRoute } from './types-Buj9Lw9t.js';
1
+ import { c as ReviewDecision, j as ReviewSubmission, e as RatingRoute } from './types-UVYt9BJe.js';
2
2
 
3
3
  /**
4
4
  * decision.ts — the pure decision logic for the review gate.