@wireai/activation 0.3.0 → 0.7.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.
Files changed (75) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +57 -0
  3. package/dist/analytics/index.d.mts +15 -2
  4. package/dist/analytics/index.d.ts +15 -2
  5. package/dist/analytics/index.js +224 -14
  6. package/dist/analytics/index.js.map +1 -1
  7. package/dist/analytics/index.mjs +222 -15
  8. package/dist/analytics/index.mjs.map +1 -1
  9. package/dist/coachmarks/index.js +6 -1
  10. package/dist/coachmarks/index.js.map +1 -1
  11. package/dist/coachmarks/index.mjs +6 -1
  12. package/dist/coachmarks/index.mjs.map +1 -1
  13. package/dist/{eventQueue-CA1d8Fmn.d.mts → currentSession-BJBB7i4-.d.mts} +143 -15
  14. package/dist/{eventQueue-CrNB9gzH.d.ts → currentSession-CxnP7gAa.d.ts} +143 -15
  15. package/dist/index.d.mts +40 -39
  16. package/dist/index.d.ts +40 -39
  17. package/dist/index.js +378 -154
  18. package/dist/index.js.map +1 -1
  19. package/dist/index.mjs +370 -156
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/questionnaire/index.d.mts +1 -1
  22. package/dist/questionnaire/index.d.ts +1 -1
  23. package/dist/questionnaire/index.js +29 -5
  24. package/dist/questionnaire/index.js.map +1 -1
  25. package/dist/questionnaire/index.mjs +30 -6
  26. package/dist/questionnaire/index.mjs.map +1 -1
  27. package/dist/reviews/index.d.mts +1 -1
  28. package/dist/reviews/index.d.ts +1 -1
  29. package/dist/reviews/index.js +40 -9
  30. package/dist/reviews/index.js.map +1 -1
  31. package/dist/reviews/index.mjs +42 -11
  32. package/dist/reviews/index.mjs.map +1 -1
  33. package/dist/showcase/index.d.mts +1 -14
  34. package/dist/showcase/index.d.ts +1 -14
  35. package/dist/showcase/index.js +8 -2
  36. package/dist/showcase/index.js.map +1 -1
  37. package/dist/showcase/index.mjs +8 -2
  38. package/dist/showcase/index.mjs.map +1 -1
  39. package/package.json +3 -2
  40. package/src/OnboardingFlow.tsx +8 -4
  41. package/src/WireOnboarding.tsx +12 -6
  42. package/src/analytics/analyticsFacade.ts +86 -10
  43. package/src/analytics/contextEnvelope.ts +13 -7
  44. package/src/analytics/currentSession.ts +35 -0
  45. package/src/analytics/eventQueue.ts +5 -0
  46. package/src/analytics/index.ts +3 -0
  47. package/src/analytics/useAnalytics.ts +7 -1
  48. package/src/analytics/useScreenTracking.ts +23 -1
  49. package/src/cards/InterstitialCard.tsx +1 -1
  50. package/src/cards/NumberStepperCard.tsx +12 -7
  51. package/src/cards/StatusCard.tsx +13 -8
  52. package/src/cards/TextInputCard.tsx +10 -5
  53. package/src/components/AnimatedSparkle.tsx +20 -3
  54. package/src/components/Button.tsx +10 -1
  55. package/src/components/CardHandoff.tsx +2 -6
  56. package/src/components/CardLayout.tsx +16 -10
  57. package/src/components/Illustration.tsx +9 -5
  58. package/src/components/LoadingBlock.tsx +44 -29
  59. package/src/components/LoadingScreen.tsx +3 -2
  60. package/src/components/OnboardingScaffold.tsx +21 -23
  61. package/src/context/userContext.ts +210 -0
  62. package/src/device/appVersion.ts +103 -0
  63. package/src/device/deviceContext.ts +17 -5
  64. package/src/features/WireFeaturesProvider.tsx +7 -2
  65. package/src/identity/userIdentity.ts +5 -0
  66. package/src/index.ts +23 -0
  67. package/src/questionnaire/QuestionnaireGate.tsx +15 -3
  68. package/src/reviews/ReviewGate.tsx +27 -7
  69. package/src/reviews/ReviewModal.tsx +4 -0
  70. package/src/session-analytics/reportSessionStart.ts +7 -0
  71. package/src/session-analytics/useLifecycleEvents.ts +4 -2
  72. package/src/session-analytics/useSessionStart.ts +2 -1
  73. package/src/showcase/FeatureShowcase.tsx +2 -1
  74. package/src/types.ts +6 -5
  75. package/src/components/loaderChrome.ts +0 -28
package/dist/index.js CHANGED
@@ -12,6 +12,12 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
12
  var React14__default = /*#__PURE__*/_interopDefault(React14);
13
13
 
14
14
  var __defProp = Object.defineProperty;
15
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
16
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
17
+ }) : x)(function(x) {
18
+ if (typeof require !== "undefined") return require.apply(this, arguments);
19
+ throw Error('Dynamic require of "' + x + '" is not supported');
20
+ });
15
21
  var __export = (target, all) => {
16
22
  for (var name in all)
17
23
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -85,11 +91,15 @@ var useOnboardingTheme = () => {
85
91
  const ctx = React14.useContext(OnboardingThemeContext);
86
92
  return ctx != null ? ctx : mergeTheme();
87
93
  };
88
- var IllustrationContext = React14.createContext({});
94
+ var EMPTY_REGISTRY = {};
95
+ var IllustrationContext = React14.createContext(EMPTY_REGISTRY);
89
96
  var IllustrationProvider = ({
90
97
  registry,
91
98
  children
92
- }) => /* @__PURE__ */ jsxRuntime.jsx(IllustrationContext.Provider, { value: registry != null ? registry : {}, children });
99
+ }) => {
100
+ const value = React14.useMemo(() => registry != null ? registry : EMPTY_REGISTRY, [registry]);
101
+ return /* @__PURE__ */ jsxRuntime.jsx(IllustrationContext.Provider, { value, children });
102
+ };
93
103
  var useIllustration = (name) => {
94
104
  const registry = React14.useContext(IllustrationContext);
95
105
  if (!name) return void 0;
@@ -380,23 +390,33 @@ var _AnimatedSparkle = ({ size = 30, color, variant = "spin" }) => {
380
390
  outputRange: [1, LOADER_PULSE_MIN_OPACITY]
381
391
  });
382
392
  const animatedStyle = reduced ? void 0 : variant === "pulse" ? { opacity: pulse } : { transform: [{ rotate }] };
383
- return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: animatedStyle, children: /* @__PURE__ */ jsxRuntime.jsx(
393
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: [styles2.box, { width: size, height: size }, animatedStyle], children: /* @__PURE__ */ jsxRuntime.jsx(
384
394
  reactNative.Text,
385
395
  {
386
396
  accessibilityElementsHidden: true,
387
397
  importantForAccessibility: "no",
388
- style: [styles2.glyph, { fontSize: size, lineHeight: size * 1.1, color: color != null ? color : t.colors.primary }],
398
+ style: [
399
+ styles2.glyph,
400
+ {
401
+ width: size,
402
+ fontSize: size,
403
+ lineHeight: size,
404
+ color: color != null ? color : t.colors.primary
405
+ }
406
+ ],
389
407
  children: "\u2726"
390
408
  }
391
409
  ) });
392
410
  };
393
411
  var AnimatedSparkle = React14__default.default.memo(_AnimatedSparkle);
394
412
  var styles2 = reactNative.StyleSheet.create({
395
- glyph: { textAlign: "center" }
413
+ box: { alignItems: "center", justifyContent: "center" },
414
+ glyph: {
415
+ textAlign: "center",
416
+ textAlignVertical: "center",
417
+ includeFontPadding: false
418
+ }
396
419
  });
397
- var LoaderChromeContext = React14.createContext({ offsetY: 0 });
398
- var LoaderChromeProvider = LoaderChromeContext.Provider;
399
- var useLoaderChrome = () => React14.useContext(LoaderChromeContext);
400
420
  var STAGE_SIZE = 96;
401
421
  var ORBIT_DOT = 9;
402
422
  var OrbitDot = ({ color, reduced }) => {
@@ -460,39 +480,45 @@ var _LoadingBlock = ({
460
480
  }) => {
461
481
  const t = useOnboardingTheme();
462
482
  const reduced = useReducedMotion();
463
- const { offsetY } = useLoaderChrome();
464
483
  const [showSkeleton, setShowSkeleton] = React14.useState(false);
465
484
  React14.useEffect(() => {
466
485
  const timer = setTimeout(() => setShowSkeleton(true), SKELETON_DELAY_MS);
467
486
  return () => clearTimeout(timer);
468
487
  }, []);
469
- return /* @__PURE__ */ jsxRuntime.jsxs(
470
- reactNative.View,
471
- {
472
- style: [
473
- styles3.center,
474
- { gap: t.spacing.md, padding: t.spacing.lg },
475
- offsetY ? { transform: [{ translateY: offsetY }] } : null
476
- ],
477
- children: [
478
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.stage, children: [
479
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles3.ring, { borderColor: t.colors.border, borderRadius: t.radius.full }] }),
480
- /* @__PURE__ */ jsxRuntime.jsx(OrbitDot, { color: t.colors.primary, reduced }),
481
- /* @__PURE__ */ jsxRuntime.jsx(AnimatedSparkle, { size: 40, variant: "pulse" })
482
- ] }),
483
- title ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [headingStyle(t.fonts), { color: t.colors.text, textAlign: "center" }], children: title }) : null,
484
- hint ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }], children: hint }) : null,
485
- showSkeleton ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles3.skeleton, { gap: t.spacing.sm, marginTop: t.spacing.sm }], children: [
486
- /* @__PURE__ */ jsxRuntime.jsx(SkeletonLine, { width: "80%", delay: 0, color: t.colors.surface, reduced }),
487
- /* @__PURE__ */ jsxRuntime.jsx(SkeletonLine, { width: "60%", delay: 200, color: t.colors.surface, reduced })
488
- ] }) : null
489
- ]
490
- }
491
- );
488
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles3.center, { padding: t.spacing.lg }], children: [
489
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.stage, children: [
490
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles3.ring, { borderColor: t.colors.border, borderRadius: t.radius.full }] }),
491
+ /* @__PURE__ */ jsxRuntime.jsx(OrbitDot, { color: t.colors.primary, reduced }),
492
+ /* @__PURE__ */ jsxRuntime.jsx(AnimatedSparkle, { size: 40, variant: "pulse" })
493
+ ] }),
494
+ /* @__PURE__ */ jsxRuntime.jsxs(
495
+ reactNative.View,
496
+ {
497
+ style: [
498
+ styles3.labels,
499
+ { marginTop: STAGE_SIZE / 2 + t.spacing.lg, gap: t.spacing.md, paddingHorizontal: t.spacing.lg }
500
+ ],
501
+ pointerEvents: "none",
502
+ children: [
503
+ title ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [headingStyle(t.fonts), { color: t.colors.text, textAlign: "center" }], children: title }) : null,
504
+ hint ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }], children: hint }) : null,
505
+ showSkeleton ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles3.skeleton, { gap: t.spacing.sm, marginTop: t.spacing.sm }], children: [
506
+ /* @__PURE__ */ jsxRuntime.jsx(SkeletonLine, { width: "80%", delay: 0, color: t.colors.surface, reduced }),
507
+ /* @__PURE__ */ jsxRuntime.jsx(SkeletonLine, { width: "60%", delay: 200, color: t.colors.surface, reduced })
508
+ ] }) : null
509
+ ]
510
+ }
511
+ )
512
+ ] });
492
513
  };
493
514
  var LoadingBlock = React14__default.default.memo(_LoadingBlock);
494
515
  var styles3 = reactNative.StyleSheet.create({
495
516
  center: { flex: 1, alignItems: "center", justifyContent: "center" },
517
+ // Absolutely-positioned label column, pinned just under the centered stage: its top
518
+ // edge starts at the flow center (top: 50%) and the inline marginTop (STAGE_SIZE/2 +
519
+ // a gap) drops it below the stage. Out of flow → it never moves the stage, so the
520
+ // 300ms skeleton reveal grows DOWNWARD only and the icon stays centered.
521
+ labels: { position: "absolute", top: "50%", left: 0, right: 0, alignItems: "center" },
496
522
  stage: {
497
523
  width: STAGE_SIZE,
498
524
  height: STAGE_SIZE,
@@ -575,7 +601,6 @@ var styles5 = reactNative.StyleSheet.create({
575
601
  track: { height: 6, width: "100%", overflow: "hidden" },
576
602
  fill: { height: 6 }
577
603
  });
578
- var PROGRESS_TRACK = 6;
579
604
  var _OnboardingScaffold = ({
580
605
  step,
581
606
  complete,
@@ -587,33 +612,41 @@ var _OnboardingScaffold = ({
587
612
  children
588
613
  }) => {
589
614
  const t = useOnboardingTheme();
615
+ const styles16 = React14.useMemo(() => makeStyles(t), [t]);
590
616
  const hit = { top: 12, bottom: 12, left: 12, right: 12 };
591
- const topChrome = t.spacing.md + PROGRESS_TRACK + t.spacing.md;
592
- const loaderOffsetY = -Math.round(topChrome / 2);
593
617
  return /* @__PURE__ */ jsxRuntime.jsxs(
594
618
  reactNativeSafeAreaContext.SafeAreaView,
595
619
  {
596
620
  edges: ["top", "bottom"],
597
- style: [styles6.flex, { backgroundColor: t.colors.background }],
621
+ style: [styles16.flex, { backgroundColor: t.colors.background }],
598
622
  children: [
599
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles6.header, { paddingHorizontal: t.spacing.md, paddingTop: t.spacing.md }], children: [
600
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles6.progressWrap, children: /* @__PURE__ */ jsxRuntime.jsx(StepProgress, { step, complete, approxScreens }) }),
601
- onSkip ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.TouchableOpacity, { onPress: onSkip, hitSlop: hit, style: { marginLeft: t.spacing.md }, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted }], children: skipLabel }) }) : null
623
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles16.header, { paddingHorizontal: t.spacing.md, paddingTop: t.spacing.md }], children: [
624
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles16.progressWrap, children: /* @__PURE__ */ jsxRuntime.jsx(StepProgress, { step, complete, approxScreens }) }),
625
+ onSkip ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.TouchableOpacity, { onPress: onSkip, hitSlop: hit, style: styles16.skip, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted }], children: skipLabel }) }) : null
602
626
  ] }),
603
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles6.body, { paddingHorizontal: t.spacing.md, paddingTop: t.spacing.md }], children: /* @__PURE__ */ jsxRuntime.jsx(LoaderChromeProvider, { value: { offsetY: loaderOffsetY }, children }) }),
604
- onBack ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles6.footer, { paddingHorizontal: t.spacing.md, paddingBottom: t.spacing.sm }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.TouchableOpacity, { onPress: onBack, hitSlop: hit, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted }], children: backLabel }) }) }) : null
627
+ /* @__PURE__ */ jsxRuntime.jsx(
628
+ reactNative.View,
629
+ {
630
+ style: [styles16.body, { paddingHorizontal: t.spacing.md, paddingTop: t.spacing.md }],
631
+ children
632
+ }
633
+ ),
634
+ onBack ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles16.footer, { paddingHorizontal: t.spacing.md, paddingBottom: t.spacing.sm }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.TouchableOpacity, { onPress: onBack, hitSlop: hit, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted }], children: backLabel }) }) }) : null
605
635
  ]
606
636
  }
607
637
  );
608
638
  };
609
639
  var OnboardingScaffold = React14__default.default.memo(_OnboardingScaffold);
610
- var styles6 = reactNative.StyleSheet.create({
611
- flex: { flex: 1 },
612
- header: { flexDirection: "row", alignItems: "center" },
613
- progressWrap: { flex: 1 },
614
- body: { flex: 1 },
615
- footer: { flexDirection: "row", justifyContent: "flex-start" }
616
- });
640
+ function makeStyles(t) {
641
+ return reactNative.StyleSheet.create({
642
+ flex: { flex: 1 },
643
+ header: { flexDirection: "row", alignItems: "center" },
644
+ progressWrap: { flex: 1 },
645
+ body: { flex: 1 },
646
+ footer: { flexDirection: "row", justifyContent: "flex-start" },
647
+ skip: { marginLeft: t.spacing.md }
648
+ });
649
+ }
617
650
  var _Button = ({
618
651
  title,
619
652
  onPress,
@@ -644,6 +677,14 @@ var _Button = ({
644
677
  },
645
678
  [pressT, reduced]
646
679
  );
680
+ const lastPress = React14.useRef(0);
681
+ const handlePress = React14.useCallback(() => {
682
+ if (!onPress) return;
683
+ const now = Date.now();
684
+ if (now - lastPress.current < 500) return;
685
+ lastPress.current = now;
686
+ onPress();
687
+ }, [onPress]);
647
688
  const handlePressIn = React14.useCallback(() => pressTo(1), [pressTo]);
648
689
  const handlePressOut = React14.useCallback(() => pressTo(0), [pressTo]);
649
690
  const scale = pressT.interpolate({
@@ -657,19 +698,19 @@ var _Button = ({
657
698
  return /* @__PURE__ */ jsxRuntime.jsx(
658
699
  reactNative.Pressable,
659
700
  {
660
- onPress: disabled ? void 0 : onPress,
701
+ onPress: disabled ? void 0 : handlePress,
661
702
  onPressIn: disabled ? void 0 : handlePressIn,
662
703
  onPressOut: disabled ? void 0 : handlePressOut,
663
704
  disabled,
664
705
  accessibilityRole: "button",
665
706
  accessibilityState: { disabled },
666
- style: fullWidth ? styles7.fill : void 0,
707
+ style: fullWidth ? styles6.fill : void 0,
667
708
  children: /* @__PURE__ */ jsxRuntime.jsx(
668
709
  reactNative.Animated.View,
669
710
  {
670
711
  style: [
671
- styles7.base,
672
- fullWidth && styles7.fill,
712
+ styles6.base,
713
+ fullWidth && styles6.fill,
673
714
  {
674
715
  borderRadius: t.button.radius,
675
716
  minHeight: t.button.height,
@@ -697,7 +738,7 @@ var _Button = ({
697
738
  );
698
739
  };
699
740
  var Button = React14__default.default.memo(_Button);
700
- var styles7 = reactNative.StyleSheet.create({
741
+ var styles6 = reactNative.StyleSheet.create({
701
742
  base: { alignItems: "center", justifyContent: "center" },
702
743
  fill: { width: "100%" }
703
744
  });
@@ -708,14 +749,14 @@ var _ErrorBlock = ({
708
749
  onRetry
709
750
  }) => {
710
751
  const t = useOnboardingTheme();
711
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles8.center, { gap: t.spacing.md, padding: t.spacing.lg }], children: [
752
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles7.center, { gap: t.spacing.md, padding: t.spacing.lg }], children: [
712
753
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [headingStyle(t.fonts), { color: t.colors.text, textAlign: "center" }], children: title }),
713
754
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }], children: message }),
714
755
  onRetry ? /* @__PURE__ */ jsxRuntime.jsx(Button, { title: retryLabel, onPress: onRetry, variant: "outline" }) : null
715
756
  ] });
716
757
  };
717
758
  var ErrorBlock = React14__default.default.memo(_ErrorBlock);
718
- var styles8 = reactNative.StyleSheet.create({
759
+ var styles7 = reactNative.StyleSheet.create({
719
760
  center: { flex: 1, alignItems: "center", justifyContent: "center" }
720
761
  });
721
762
  var _CardLayout = ({
@@ -727,9 +768,10 @@ var _CardLayout = ({
727
768
  titleNode
728
769
  }) => {
729
770
  const t = useOnboardingTheme();
771
+ const styles16 = React14.useMemo(() => makeStyles2(t), [t]);
730
772
  const hasHeader = !!title || !!subtitle || !!titleNode;
731
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.KeyboardAvoidingView, { style: styles9.fill, behavior: "padding", children: [
732
- hasHeader ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { marginBottom: t.spacing.lg }, children: [
773
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.KeyboardAvoidingView, { style: styles16.fill, behavior: "padding", children: [
774
+ hasHeader ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles16.header, children: [
733
775
  titleNode != null ? titleNode : title ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [labelStyle(t.fonts), { color: t.colors.text }], children: title }) : null,
734
776
  subtitle ? /* @__PURE__ */ jsxRuntime.jsx(
735
777
  reactNative.Text,
@@ -745,10 +787,10 @@ var _CardLayout = ({
745
787
  /* @__PURE__ */ jsxRuntime.jsx(
746
788
  reactNative.ScrollView,
747
789
  {
748
- style: styles9.fill,
790
+ style: styles16.fill,
749
791
  contentContainerStyle: [
750
- styles9.scrollContent,
751
- align === "center" && styles9.center
792
+ styles16.scrollContent,
793
+ align === "center" && styles16.center
752
794
  ],
753
795
  keyboardShouldPersistTaps: "handled",
754
796
  keyboardDismissMode: "on-drag",
@@ -756,17 +798,21 @@ var _CardLayout = ({
756
798
  children
757
799
  }
758
800
  ),
759
- footer ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { paddingTop: t.spacing.lg }, children: footer }) : null
801
+ footer ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles16.footer, children: footer }) : null
760
802
  ] });
761
803
  };
762
804
  var CardLayout = React14__default.default.memo(_CardLayout);
763
- var styles9 = reactNative.StyleSheet.create({
764
- fill: { flex: 1 },
765
- // flexGrow lets a short content block grow to fill (pinning the footer) while a
766
- // tall one scrolls past the visible area.
767
- scrollContent: { flexGrow: 1 },
768
- center: { justifyContent: "center" }
769
- });
805
+ function makeStyles2(t) {
806
+ return reactNative.StyleSheet.create({
807
+ fill: { flex: 1 },
808
+ header: { marginBottom: t.spacing.lg },
809
+ footer: { paddingTop: t.spacing.lg },
810
+ // flexGrow lets a short content block grow to fill (pinning the footer) while a
811
+ // tall one scrolls past the visible area.
812
+ scrollContent: { flexGrow: 1 },
813
+ center: { justifyContent: "center" }
814
+ });
815
+ }
770
816
  var easeWire2 = reactNative.Easing.bezier(...WIRE_BEZIER);
771
817
  var BADGE_SIZE = 64;
772
818
  var BURST_SIZE = 104;
@@ -841,14 +887,14 @@ var _CompletionView = ({
841
887
  {
842
888
  align: "center",
843
889
  footer: /* @__PURE__ */ jsxRuntime.jsx(Button, { title: ctaLabel, onPress: handlePress, variant: "primary", disabled: submitted }),
844
- children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles10.center, { gap: t.spacing.md }], children: [
845
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles10.badgeStage, children: [
890
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles8.center, { gap: t.spacing.md }], children: [
891
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles8.badgeStage, children: [
846
892
  /* @__PURE__ */ jsxRuntime.jsx(
847
893
  reactNative.Animated.View,
848
894
  {
849
895
  pointerEvents: "none",
850
896
  style: [
851
- styles10.burst,
897
+ styles8.burst,
852
898
  {
853
899
  backgroundColor: t.colors.primarySoft,
854
900
  borderRadius: t.radius.full,
@@ -862,7 +908,7 @@ var _CompletionView = ({
862
908
  reactNative.Animated.View,
863
909
  {
864
910
  style: [
865
- styles10.badge,
911
+ styles8.badge,
866
912
  {
867
913
  backgroundColor: t.colors.primarySoft,
868
914
  borderRadius: t.radius.full,
@@ -870,7 +916,7 @@ var _CompletionView = ({
870
916
  opacity: pop
871
917
  }
872
918
  ],
873
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles10.check, { color: t.colors.primary }], children: "\u2713" })
919
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles8.check, { color: t.colors.primary }], children: "\u2713" })
874
920
  }
875
921
  )
876
922
  ] }),
@@ -881,7 +927,7 @@ var _CompletionView = ({
881
927
  );
882
928
  };
883
929
  var CompletionView = React14__default.default.memo(_CompletionView);
884
- var styles10 = reactNative.StyleSheet.create({
930
+ var styles8 = reactNative.StyleSheet.create({
885
931
  center: { width: "100%", alignItems: "center", justifyContent: "center" },
886
932
  badgeStage: {
887
933
  width: BURST_SIZE,
@@ -904,7 +950,6 @@ var _CardHandoff = ({
904
950
  const enterT = React14.useRef(new reactNative.Animated.Value(0)).current;
905
951
  const exitT = React14.useRef(new reactNative.Animated.Value(0)).current;
906
952
  const prev = React14.useRef({ key: transitionKey, node: children, variant });
907
- const [enterVariant, setEnterVariant] = React14.useState(variant);
908
953
  const running = React14.useRef({});
909
954
  const mounted = React14.useRef(false);
910
955
  React14.useEffect(() => {
@@ -913,7 +958,6 @@ var _CardHandoff = ({
913
958
  prev.current = { key: transitionKey, node: children, variant };
914
959
  if (!mounted.current) {
915
960
  mounted.current = true;
916
- setEnterVariant(variant);
917
961
  running.current.enter = startEnter(enterT, variant, reduced, 0);
918
962
  return;
919
963
  }
@@ -921,7 +965,6 @@ var _CardHandoff = ({
921
965
  (_a = running.current.enter) == null ? void 0 : _a.stop();
922
966
  (_b = running.current.exit) == null ? void 0 : _b.stop();
923
967
  setLeaving(last);
924
- setEnterVariant(variant);
925
968
  enterT.setValue(0);
926
969
  exitT.setValue(0);
927
970
  const exitDuration = reduced ? REDUCED_FADE_MS : last.variant === "fade" ? LOADER_HANDOFF_FADE_MS : CARD_EXIT_MS;
@@ -952,7 +995,7 @@ var _CardHandoff = ({
952
995
  );
953
996
  const enterStyle = {
954
997
  opacity: enterT,
955
- transform: transformFor(enterVariant, enterT, reduced, "enter")
998
+ transform: transformFor(variant, enterT, reduced, "enter")
956
999
  };
957
1000
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
958
1001
  leaving ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -969,7 +1012,7 @@ var _CardHandoff = ({
969
1012
  children: leaving.node
970
1013
  }
971
1014
  ) : null,
972
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: [styles11.fill, enterStyle], children })
1015
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: [styles9.fill, enterStyle], children }, variant)
973
1016
  ] });
974
1017
  };
975
1018
  var startEnter = (value, variant, reduced, delay, onDone) => {
@@ -1010,7 +1053,7 @@ var transformFor = (variant, t, reduced, direction) => {
1010
1053
  ];
1011
1054
  };
1012
1055
  var CardHandoff = React14__default.default.memo(_CardHandoff);
1013
- var styles11 = reactNative.StyleSheet.create({
1056
+ var styles9 = reactNative.StyleSheet.create({
1014
1057
  fill: { flex: 1 }
1015
1058
  });
1016
1059
 
@@ -1140,6 +1183,10 @@ var OnboardingFlow = ({
1140
1183
  onEventRef.current = onEvent;
1141
1184
  const onErrorRef = React14.useRef(onError);
1142
1185
  onErrorRef.current = onError;
1186
+ const onCompleteRef = React14.useRef(onComplete);
1187
+ onCompleteRef.current = onComplete;
1188
+ const validatorsRef = React14.useRef(validators);
1189
+ validatorsRef.current = validators;
1143
1190
  const reportTargetRef = React14.useRef(reportTarget);
1144
1191
  reportTargetRef.current = reportTarget;
1145
1192
  const sessionIdRef = React14.useRef(sessionId);
@@ -1255,8 +1302,8 @@ var OnboardingFlow = ({
1255
1302
  const handleFinish = React14.useCallback(() => {
1256
1303
  if (finished.current) return;
1257
1304
  finished.current = true;
1258
- onComplete({ answers: deriveAnswers(messages), raw: messages });
1259
- }, [messages, onComplete]);
1305
+ onCompleteRef.current({ answers: deriveAnswers(messages), raw: messages });
1306
+ }, [messages]);
1260
1307
  const handleRetry = React14.useCallback(() => {
1261
1308
  attempts.current = 0;
1262
1309
  degradedRef.current = false;
@@ -1284,9 +1331,10 @@ var OnboardingFlow = ({
1284
1331
  }, []);
1285
1332
  const wrappedActions = React14.useCallback(
1286
1333
  (messageId) => {
1334
+ var _a2;
1287
1335
  const base = makeActions(messageId);
1288
1336
  const key = progress.key;
1289
- const validator = key ? validators == null ? void 0 : validators[key] : void 0;
1337
+ const validator = key ? (_a2 = validatorsRef.current) == null ? void 0 : _a2[key] : void 0;
1290
1338
  if (!validator) return base;
1291
1339
  const out = { ...base };
1292
1340
  for (const name of ANSWER_CALLBACKS) {
@@ -1297,12 +1345,12 @@ var OnboardingFlow = ({
1297
1345
  if (typeof value !== "string") return original(...args);
1298
1346
  setValidating(true);
1299
1347
  validator(value).then((res) => {
1300
- var _a2;
1348
+ var _a3;
1301
1349
  if (res.ok) {
1302
1350
  setValidationError(void 0);
1303
1351
  original(...args);
1304
1352
  } else {
1305
- setValidationError((_a2 = res.error) != null ? _a2 : "That value isn't available.");
1353
+ setValidationError((_a3 = res.error) != null ? _a3 : "That value isn't available.");
1306
1354
  }
1307
1355
  }).catch(() => {
1308
1356
  original(...args);
@@ -1311,7 +1359,7 @@ var OnboardingFlow = ({
1311
1359
  }
1312
1360
  return out;
1313
1361
  },
1314
- [makeActions, progress.key, validators]
1362
+ [makeActions, progress.key]
1315
1363
  );
1316
1364
  if (degraded) {
1317
1365
  if (fallbackFlow !== void 0) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallbackFlow });
@@ -1466,7 +1514,7 @@ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
1466
1514
  reactNative.Animated.View,
1467
1515
  {
1468
1516
  style: [
1469
- styles12.chip,
1517
+ styles10.chip,
1470
1518
  {
1471
1519
  borderRadius: t.radius.full,
1472
1520
  paddingHorizontal: t.spacing.md,
@@ -1483,7 +1531,7 @@ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
1483
1531
  pointerEvents: "none",
1484
1532
  style: [
1485
1533
  reactNative.StyleSheet.absoluteFill,
1486
- styles12.fillOverlay,
1534
+ styles10.fillOverlay,
1487
1535
  {
1488
1536
  borderRadius: t.radius.full,
1489
1537
  borderColor: t.colors.primary,
@@ -1497,7 +1545,7 @@ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
1497
1545
  reactNative.Animated.Text,
1498
1546
  {
1499
1547
  style: [
1500
- styles12.check,
1548
+ styles10.check,
1501
1549
  { color: t.colors.onPrimary, opacity: checkT, transform: [{ scale: checkScale }] }
1502
1550
  ],
1503
1551
  children: "\u2713"
@@ -1508,7 +1556,7 @@ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
1508
1556
  {
1509
1557
  style: [
1510
1558
  captionStyle(t.fonts),
1511
- styles12.chipLabel,
1559
+ styles10.chipLabel,
1512
1560
  { color: isSelected ? t.colors.onPrimary : t.colors.textMuted }
1513
1561
  ],
1514
1562
  children: chip
@@ -1570,7 +1618,7 @@ var _ChipSelectCard = ({
1570
1618
  disabled: selected.length === 0 || submitted
1571
1619
  }
1572
1620
  ),
1573
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles12.chipsRow, { gap: t.spacing.sm + t.spacing.xs }], children: chips.map((chip) => /* @__PURE__ */ jsxRuntime.jsx(
1621
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles10.chipsRow, { gap: t.spacing.sm + t.spacing.xs }], children: chips.map((chip) => /* @__PURE__ */ jsxRuntime.jsx(
1574
1622
  ChipItem,
1575
1623
  {
1576
1624
  chip,
@@ -1590,7 +1638,7 @@ var ChipSelectCard = {
1590
1638
  propsSchema: schema,
1591
1639
  defaultProps: { multiSelect: true, submitLabel: "Continue" }
1592
1640
  };
1593
- var styles12 = reactNative.StyleSheet.create({
1641
+ var styles10 = reactNative.StyleSheet.create({
1594
1642
  chipsRow: { flexDirection: "row", flexWrap: "wrap" },
1595
1643
  chip: { borderWidth: 1.5, flexDirection: "row", alignItems: "center", gap: 6, overflow: "hidden" },
1596
1644
  fillOverlay: { borderWidth: 1.5 },
@@ -1622,6 +1670,7 @@ var _TextInputCard = ({
1622
1670
  lines = 6
1623
1671
  }) => {
1624
1672
  const t = useOnboardingTheme();
1673
+ const styles16 = React14.useMemo(() => makeStyles3(t), [t]);
1625
1674
  const { height: screenHeight } = reactNative.useWindowDimensions();
1626
1675
  const [value, setValue] = React14.useState("");
1627
1676
  const [submitted, setSubmitted] = React14.useState(false);
@@ -1648,7 +1697,7 @@ var _TextInputCard = ({
1648
1697
  disabled: !value.trim() || locked
1649
1698
  }
1650
1699
  ),
1651
- children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { gap: t.spacing.sm }, children: [
1700
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles16.row, children: [
1652
1701
  /* @__PURE__ */ jsxRuntime.jsx(
1653
1702
  reactNative.TextInput,
1654
1703
  {
@@ -1662,7 +1711,7 @@ var _TextInputCard = ({
1662
1711
  textAlignVertical: multiline ? "top" : "center",
1663
1712
  style: [
1664
1713
  bodyStyle(t.fonts),
1665
- styles13.input,
1714
+ styles16.input,
1666
1715
  {
1667
1716
  color: t.colors.text,
1668
1717
  backgroundColor: t.colors.surface,
@@ -1686,9 +1735,12 @@ var TextInputCard = {
1686
1735
  propsSchema: schema2,
1687
1736
  defaultProps: { submitLabel: "Submit", multiline: true, lines: 6 }
1688
1737
  };
1689
- var styles13 = reactNative.StyleSheet.create({
1690
- input: { borderWidth: 1 }
1691
- });
1738
+ function makeStyles3(t) {
1739
+ return reactNative.StyleSheet.create({
1740
+ row: { gap: t.spacing.sm },
1741
+ input: { borderWidth: 1 }
1742
+ });
1743
+ }
1692
1744
  var easeWire5 = reactNative.Easing.bezier(...WIRE_BEZIER);
1693
1745
  var optionObjectSchema = zod.z.object({
1694
1746
  value: zod.z.string().describe("Option value"),
@@ -1767,7 +1819,7 @@ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
1767
1819
  reactNative.Animated.View,
1768
1820
  {
1769
1821
  style: [
1770
- styles14.option,
1822
+ styles11.option,
1771
1823
  {
1772
1824
  padding: t.spacing.md,
1773
1825
  borderRadius: t.radius.md,
@@ -1784,7 +1836,7 @@ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
1784
1836
  pointerEvents: "none",
1785
1837
  style: [
1786
1838
  reactNative.StyleSheet.absoluteFill,
1787
- styles14.selectedOverlay,
1839
+ styles11.selectedOverlay,
1788
1840
  {
1789
1841
  borderRadius: t.radius.md,
1790
1842
  borderColor: t.colors.primary,
@@ -1798,7 +1850,7 @@ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
1798
1850
  reactNative.View,
1799
1851
  {
1800
1852
  style: [
1801
- styles14.indicator,
1853
+ styles11.indicator,
1802
1854
  { borderRadius: t.radius.full },
1803
1855
  isSelected ? { borderColor: t.colors.primary, backgroundColor: t.colors.primary } : { borderColor: t.colors.borderStrong, backgroundColor: "transparent" }
1804
1856
  ],
@@ -1806,7 +1858,7 @@ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
1806
1858
  reactNative.Animated.Text,
1807
1859
  {
1808
1860
  style: [
1809
- styles14.check,
1861
+ styles11.check,
1810
1862
  { color: t.colors.onPrimary, opacity: checkT, transform: [{ scale: checkScale }] }
1811
1863
  ],
1812
1864
  children: "\u2713"
@@ -1885,7 +1937,7 @@ var SelectionCard = {
1885
1937
  propsSchema: schema3,
1886
1938
  defaultProps: { submitLabel: "Continue", multiSelect: false }
1887
1939
  };
1888
- var styles14 = reactNative.StyleSheet.create({
1940
+ var styles11 = reactNative.StyleSheet.create({
1889
1941
  option: { borderWidth: 1.5, flexDirection: "row", alignItems: "center", overflow: "hidden" },
1890
1942
  selectedOverlay: { borderWidth: 1.5 },
1891
1943
  indicator: { width: 24, height: 24, borderWidth: 2, alignItems: "center", justifyContent: "center" },
@@ -1907,17 +1959,22 @@ var STATUS_ICONS = {
1907
1959
  info: "\u2139"
1908
1960
  };
1909
1961
  var _StatusCard = ({ status, title, message, ctaLabel, onContinue, onPress, onSubmit }) => {
1910
- var _a, _b;
1962
+ var _a;
1911
1963
  const t = useOnboardingTheme();
1912
1964
  const reduced = useReducedMotion();
1913
1965
  const [submitted, setSubmitted] = React14.useState(false);
1914
- const colorFor = {
1915
- success: t.colors.success,
1916
- warning: t.colors.warning,
1917
- error: t.colors.error,
1918
- info: t.colors.info
1919
- };
1920
- const color = (_a = colorFor[status]) != null ? _a : t.colors.info;
1966
+ const color = React14.useMemo(() => {
1967
+ switch (status) {
1968
+ case "success":
1969
+ return t.colors.success;
1970
+ case "warning":
1971
+ return t.colors.warning;
1972
+ case "error":
1973
+ return t.colors.error;
1974
+ default:
1975
+ return t.colors.info;
1976
+ }
1977
+ }, [status, t.colors]);
1921
1978
  const popT = React14.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
1922
1979
  const copyT = React14.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
1923
1980
  React14.useEffect(() => {
@@ -1959,8 +2016,8 @@ var _StatusCard = ({ status, title, message, ctaLabel, onContinue, onPress, onSu
1959
2016
  {
1960
2017
  align: "center",
1961
2018
  footer: ctaLabel ? /* @__PURE__ */ jsxRuntime.jsx(Button, { title: ctaLabel, onPress: handleContinue, variant: "primary", disabled: submitted }) : void 0,
1962
- children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles15.center, { gap: t.spacing.sm }], children: [
1963
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles15.titleRow, { gap: t.spacing.sm }], children: [
2019
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles12.center, { gap: t.spacing.sm }], children: [
2020
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles12.titleRow, { gap: t.spacing.sm }], children: [
1964
2021
  /* @__PURE__ */ jsxRuntime.jsx(
1965
2022
  reactNative.Animated.Text,
1966
2023
  {
@@ -1968,7 +2025,7 @@ var _StatusCard = ({ status, title, message, ctaLabel, onContinue, onPress, onSu
1968
2025
  headingStyle(t.fonts),
1969
2026
  { color, opacity: popT, transform: [{ scale: popScale }] }
1970
2027
  ],
1971
- children: (_b = STATUS_ICONS[status]) != null ? _b : "\xB7"
2028
+ children: (_a = STATUS_ICONS[status]) != null ? _a : "\xB7"
1972
2029
  }
1973
2030
  ),
1974
2031
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2007,7 +2064,7 @@ var StatusCard = {
2007
2064
  component: React14__default.default.memo(_StatusCard),
2008
2065
  propsSchema: schema4
2009
2066
  };
2010
- var styles15 = reactNative.StyleSheet.create({
2067
+ var styles12 = reactNative.StyleSheet.create({
2011
2068
  center: { width: "100%", alignItems: "center", justifyContent: "center" },
2012
2069
  titleRow: { flexDirection: "row", alignItems: "center", justifyContent: "center" }
2013
2070
  });
@@ -2032,6 +2089,7 @@ var _NumberStepperCard = ({
2032
2089
  onSubmit
2033
2090
  }) => {
2034
2091
  const t = useOnboardingTheme();
2092
+ const styles16 = React14.useMemo(() => makeStyles4(t), [t]);
2035
2093
  const safeMin = safeNum(min, 1);
2036
2094
  const safeMaxRaw = safeNum(max, 30);
2037
2095
  const safeMax = safeMaxRaw < safeMin ? safeMin : safeMaxRaw;
@@ -2064,7 +2122,7 @@ var _NumberStepperCard = ({
2064
2122
  title: label,
2065
2123
  align: "center",
2066
2124
  footer: /* @__PURE__ */ jsxRuntime.jsx(Button, { title: submitLabel, onPress: handleSubmit, variant: "primary", disabled: submitted }),
2067
- children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { gap: t.spacing.md, alignItems: "center" }, children: [
2125
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles16.wrap, children: [
2068
2126
  /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles16.stepper, { gap: t.spacing.lg }], children: [
2069
2127
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: atMin || submitted ? void 0 : handleDecrement, disabled: atMin || submitted, style: stepBtnStyle(atMin), children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [headingStyle(t.fonts), { color: atMin ? t.colors.textMuted : t.colors.primary }], children: "\u2212" }) }),
2070
2128
  /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles16.valueBox, children: [
@@ -2085,11 +2143,14 @@ var NumberStepperCard = {
2085
2143
  propsSchema: schema5,
2086
2144
  defaultProps: { min: 1, max: 30, defaultValue: 1, step: 1, submitLabel: "Confirm" }
2087
2145
  };
2088
- var styles16 = reactNative.StyleSheet.create({
2089
- stepper: { flexDirection: "row", alignItems: "center", justifyContent: "center" },
2090
- stepBtn: { width: 44, height: 44, borderWidth: 2, alignItems: "center", justifyContent: "center" },
2091
- valueBox: { alignItems: "center", minWidth: 80 }
2092
- });
2146
+ function makeStyles4(t) {
2147
+ return reactNative.StyleSheet.create({
2148
+ wrap: { gap: t.spacing.md, alignItems: "center" },
2149
+ stepper: { flexDirection: "row", alignItems: "center", justifyContent: "center" },
2150
+ stepBtn: { width: 44, height: 44, borderWidth: 2, alignItems: "center", justifyContent: "center" },
2151
+ valueBox: { alignItems: "center", minWidth: 80 }
2152
+ });
2153
+ }
2093
2154
  var easeWire7 = reactNative.Easing.bezier(...WIRE_BEZIER);
2094
2155
  var RISE_PX = 10;
2095
2156
  var schema6 = zod.z.object({
@@ -2151,20 +2212,20 @@ var _WorkItem = ({
2151
2212
  return /* @__PURE__ */ jsxRuntime.jsxs(
2152
2213
  reactNative.Animated.View,
2153
2214
  {
2154
- style: [styles17.itemRow, { gap: t.spacing.sm + t.spacing.xs, opacity: rowT, transform: [{ translateY: rise }] }],
2215
+ style: [styles13.itemRow, { gap: t.spacing.sm + t.spacing.xs, opacity: rowT, transform: [{ translateY: rise }] }],
2155
2216
  children: [
2156
2217
  /* @__PURE__ */ jsxRuntime.jsx(
2157
2218
  reactNative.View,
2158
2219
  {
2159
2220
  style: [
2160
- styles17.itemBadge,
2221
+ styles13.itemBadge,
2161
2222
  { backgroundColor: t.colors.primarySoft, borderRadius: t.radius.full }
2162
2223
  ],
2163
2224
  children: /* @__PURE__ */ jsxRuntime.jsx(
2164
2225
  reactNative.Animated.Text,
2165
2226
  {
2166
2227
  style: [
2167
- styles17.itemCheck,
2228
+ styles13.itemCheck,
2168
2229
  { color: t.colors.primary, opacity: checkT, transform: [{ scale: checkScale }] }
2169
2230
  ],
2170
2231
  children: "\u2713"
@@ -2172,7 +2233,7 @@ var _WorkItem = ({
2172
2233
  )
2173
2234
  }
2174
2235
  ),
2175
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), styles17.itemText, { color: t.colors.text }], children: text })
2236
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), styles13.itemText, { color: t.colors.text }], children: text })
2176
2237
  ]
2177
2238
  }
2178
2239
  );
@@ -2250,18 +2311,18 @@ var _InterstitialCard = ({
2250
2311
  disabled: submitted || !canAdvance
2251
2312
  }
2252
2313
  ),
2253
- children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles17.center, { gap: t.spacing.md }], children: [
2254
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: [styles17.fullWidth, riseStyle(artT)], children: showImage ? /* @__PURE__ */ jsxRuntime.jsx(
2314
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles13.center, { gap: t.spacing.md }], children: [
2315
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: [styles13.fullWidth, riseStyle(artT)], children: showImage ? /* @__PURE__ */ jsxRuntime.jsx(
2255
2316
  reactNative.Image,
2256
2317
  {
2257
2318
  source: { uri: imageUrl },
2258
- style: styles17.image,
2319
+ style: styles13.image,
2259
2320
  resizeMode: "contain",
2260
2321
  onError: () => setImgFailed(true)
2261
2322
  }
2262
- ) : art ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles17.art, children: art }) : null }),
2323
+ ) : art ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles13.art, children: art }) : null }),
2263
2324
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: riseStyle(titleT), children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [headingStyle(t.fonts), { color: t.colors.text, textAlign: "center" }], children: title }) }),
2264
- (items == null ? void 0 : items.length) ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles17.items, { gap: t.spacing.md, marginTop: t.spacing.sm }], children: items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(WorkItem, { text: item, index: i, reduced }, item)) }) : body ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: riseStyle(bodyT), children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }], children: body }) }) : null
2325
+ (items == null ? void 0 : items.length) ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles13.items, { gap: t.spacing.md, marginTop: t.spacing.sm }], children: items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(WorkItem, { text: item, index: i, reduced }, `${item}-${i}`)) }) : body ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: riseStyle(bodyT), children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }], children: body }) }) : null
2265
2326
  ] })
2266
2327
  }
2267
2328
  );
@@ -2273,7 +2334,7 @@ var InterstitialCard = {
2273
2334
  propsSchema: schema6,
2274
2335
  defaultProps: { ctaLabel: "Continue", illustrationFallback: "momentum" }
2275
2336
  };
2276
- var styles17 = reactNative.StyleSheet.create({
2337
+ var styles13 = reactNative.StyleSheet.create({
2277
2338
  center: { width: "100%", alignItems: "center", justifyContent: "center" },
2278
2339
  fullWidth: { width: "100%", alignItems: "center" },
2279
2340
  art: { alignItems: "center", justifyContent: "center" },
@@ -2294,6 +2355,57 @@ var onboardingComponents = [
2294
2355
  NumberStepperCard,
2295
2356
  InterstitialCard
2296
2357
  ];
2358
+
2359
+ // src/device/appVersion.ts
2360
+ var coerceVersion = (value) => {
2361
+ if (typeof value !== "string") return void 0;
2362
+ const trimmed = value.trim();
2363
+ return trimmed.length > 0 ? trimmed : void 0;
2364
+ };
2365
+ var runtimeRequire = (moduleName) => {
2366
+ try {
2367
+ if (typeof __require !== "function") return void 0;
2368
+ return __require(moduleName);
2369
+ } catch {
2370
+ return void 0;
2371
+ }
2372
+ };
2373
+ var interop = (mod) => {
2374
+ if (!mod || typeof mod !== "object") return void 0;
2375
+ const def = mod.default;
2376
+ if (def && typeof def === "object") return def;
2377
+ return mod;
2378
+ };
2379
+ var safeInterop = (requireModule, moduleName) => {
2380
+ try {
2381
+ return interop(requireModule(moduleName));
2382
+ } catch {
2383
+ return void 0;
2384
+ }
2385
+ };
2386
+ var detectAppVersion = (requireModule = runtimeRequire) => {
2387
+ try {
2388
+ const constants = safeInterop(requireModule, "expo-constants");
2389
+ if (constants) {
2390
+ const expoConfig = constants.expoConfig;
2391
+ if (expoConfig && typeof expoConfig === "object") {
2392
+ const fromExpoConfig = coerceVersion(expoConfig.version);
2393
+ if (fromExpoConfig) return fromExpoConfig;
2394
+ }
2395
+ const fromNative = coerceVersion(constants.nativeAppVersion);
2396
+ if (fromNative) return fromNative;
2397
+ }
2398
+ const application = safeInterop(requireModule, "expo-application");
2399
+ if (application) {
2400
+ const fromApplication = coerceVersion(application.nativeApplicationVersion);
2401
+ if (fromApplication) return fromApplication;
2402
+ }
2403
+ } catch {
2404
+ }
2405
+ return void 0;
2406
+ };
2407
+
2408
+ // src/device/deviceContext.ts
2297
2409
  var deriveFormFactor = (iosIdiom, width, height) => {
2298
2410
  if (iosIdiom === "pad") return "tablet";
2299
2411
  if (iosIdiom === "phone") return "phone";
@@ -2363,9 +2475,21 @@ var collectDeviceContext = () => {
2363
2475
  if (resolved.timeZone) ctx.timeZone = resolved.timeZone;
2364
2476
  } catch {
2365
2477
  }
2478
+ const appVersion = detectAppVersion();
2479
+ if (appVersion) ctx.appVersion = appVersion;
2366
2480
  return ctx;
2367
2481
  };
2368
2482
 
2483
+ // src/analytics/currentSession.ts
2484
+ var _currentSessionId;
2485
+ var setCurrentSessionId = (id) => {
2486
+ if (typeof id === "string" && id.length > 0) _currentSessionId = id;
2487
+ };
2488
+ var getCurrentSessionId = () => _currentSessionId;
2489
+ var resetCurrentSessionId = () => {
2490
+ _currentSessionId = void 0;
2491
+ };
2492
+
2369
2493
  // src/session/persistedSession.ts
2370
2494
  var DEFAULT_SESSION_TTL_MS = 36e5;
2371
2495
  var READ_TIMEOUT_MS = 1500;
@@ -2444,6 +2568,7 @@ var identifyOnboarding = async (opts) => {
2444
2568
  contextId = stored == null ? void 0 : stored.id;
2445
2569
  }
2446
2570
  }
2571
+ if (!contextId) contextId = getCurrentSessionId();
2447
2572
  if (!contextId) return false;
2448
2573
  reportClientEvent(
2449
2574
  { serverUrl: opts.config.serverUrl, apiKey: opts.config.apiKey },
@@ -2511,6 +2636,10 @@ var WireOnboarding = ({
2511
2636
  );
2512
2637
  const sessionId = (_a = session == null ? void 0 : session.id) != null ? _a : "";
2513
2638
  const startupUserIdRef = React14.useRef(boundUserId);
2639
+ const metadataKey = config.metadata ? JSON.stringify(config.metadata) : "";
2640
+ const metadataStable = React14.useMemo(() => metadataKey ? JSON.parse(metadataKey) : void 0, [metadataKey]);
2641
+ const componentNamesKey = (components != null ? components : onboardingComponents).map((c) => c.name).join(",");
2642
+ const componentsStable = React14.useMemo(() => components != null ? components : onboardingComponents, [componentNamesKey]);
2514
2643
  const llm = React14.useMemo(() => {
2515
2644
  const baseUrl = config.serverUrl.replace(/\/$/, "");
2516
2645
  return {
@@ -2523,9 +2652,9 @@ var WireOnboarding = ({
2523
2652
  // names of the components this device can actually render — so the backend only
2524
2653
  // emits cards the kit knows how to draw.
2525
2654
  metadata: {
2526
- ...config.metadata,
2655
+ ...metadataStable,
2527
2656
  sessionId,
2528
- supportedComponents: (components != null ? components : onboardingComponents).map((c) => c.name),
2657
+ supportedComponents: componentsStable.map((c) => c.name),
2529
2658
  // Device snapshot + host-injected user context ride the session-start metadata so the
2530
2659
  // backend can segment the funnel. Old servers ignore these unknown keys (backward compat).
2531
2660
  device,
@@ -2538,7 +2667,7 @@ var WireOnboarding = ({
2538
2667
  },
2539
2668
  timeoutMs: 6e4
2540
2669
  };
2541
- }, [config.serverUrl, config.appId, config.apiKey, config.metadata, sessionId, components, device, userContextStable]);
2670
+ }, [config.serverUrl, config.appId, config.apiKey, metadataStable, sessionId, componentsStable, device, userContextStable]);
2542
2671
  const reportTarget = React14.useMemo(
2543
2672
  () => ({ serverUrl: config.serverUrl, apiKey: config.apiKey }),
2544
2673
  [config.serverUrl, config.apiKey]
@@ -2603,7 +2732,7 @@ function DemoOnboarding({
2603
2732
  const [open, setOpen] = React14.useState(false);
2604
2733
  const [runId, setRunId] = React14.useState(0);
2605
2734
  const t = React14.useMemo(() => mergeTheme(theme != null ? theme : {}), [theme]);
2606
- const styles20 = React14.useMemo(() => makeStyles(t), [t]);
2735
+ const styles16 = React14.useMemo(() => makeStyles5(t), [t]);
2607
2736
  const openDemo = React14.useCallback(() => {
2608
2737
  setRunId((n) => n + 1);
2609
2738
  setOpen(true);
@@ -2617,7 +2746,7 @@ function DemoOnboarding({
2617
2746
  [onComplete]
2618
2747
  );
2619
2748
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2620
- renderTrigger ? renderTrigger(openDemo) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: openDemo, style: styles20.trigger, accessibilityRole: "button", children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles20.triggerText, children: label }) }),
2749
+ renderTrigger ? renderTrigger(openDemo) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: openDemo, style: styles16.trigger, accessibilityRole: "button", children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles16.triggerText, children: label }) }),
2621
2750
  /* @__PURE__ */ jsxRuntime.jsx(
2622
2751
  reactNative.Modal,
2623
2752
  {
@@ -2625,7 +2754,7 @@ function DemoOnboarding({
2625
2754
  animationType: "slide",
2626
2755
  presentationStyle: "fullScreen",
2627
2756
  onRequestClose: close,
2628
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles20.modal, children: config ? /* @__PURE__ */ jsxRuntime.jsx(
2757
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles16.modal, children: config ? /* @__PURE__ */ jsxRuntime.jsx(
2629
2758
  WireOnboarding,
2630
2759
  {
2631
2760
  config,
@@ -2638,16 +2767,16 @@ function DemoOnboarding({
2638
2767
  onError: close
2639
2768
  },
2640
2769
  runId
2641
- ) : /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles20.notice, children: [
2642
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles20.noticeTitle, children: "Wire AI not configured" }),
2643
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles20.noticeBody, children: "Set the Wire AI key + server URL (e.g. via wireConfigFromEnv) to demo the AI onboarding here." }),
2644
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: close, style: styles20.trigger, accessibilityRole: "button", children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles20.triggerText, children: "Close" }) })
2770
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles16.notice, children: [
2771
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles16.noticeTitle, children: "Wire AI not configured" }),
2772
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles16.noticeBody, children: "Set the Wire AI key + server URL (e.g. via wireConfigFromEnv) to demo the AI onboarding here." }),
2773
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: close, style: styles16.trigger, accessibilityRole: "button", children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles16.triggerText, children: "Close" }) })
2645
2774
  ] }) })
2646
2775
  }
2647
2776
  )
2648
2777
  ] });
2649
2778
  }
2650
- function makeStyles(t) {
2779
+ function makeStyles5(t) {
2651
2780
  return reactNative.StyleSheet.create({
2652
2781
  trigger: {
2653
2782
  backgroundColor: t.colors.primary,
@@ -2720,13 +2849,13 @@ var _DoneBlock = ({
2720
2849
  message = "Personalizing your experience\u2026"
2721
2850
  }) => {
2722
2851
  const t = useOnboardingTheme();
2723
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles18.center, { gap: t.spacing.sm, padding: t.spacing.lg }], children: [
2852
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles14.center, { gap: t.spacing.sm, padding: t.spacing.lg }], children: [
2724
2853
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [headingStyle(t.fonts), { color: t.colors.success, textAlign: "center" }], children: title }),
2725
2854
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }], children: message })
2726
2855
  ] });
2727
2856
  };
2728
2857
  var DoneBlock = React14__default.default.memo(_DoneBlock);
2729
- var styles18 = reactNative.StyleSheet.create({
2858
+ var styles14 = reactNative.StyleSheet.create({
2730
2859
  center: { flex: 1, alignItems: "center", justifyContent: "center" }
2731
2860
  });
2732
2861
 
@@ -2805,25 +2934,25 @@ var _CenteredModal = React14.forwardRef(
2805
2934
  /* @__PURE__ */ jsxRuntime.jsx(
2806
2935
  reactNative.Animated.View,
2807
2936
  {
2808
- style: [styles19.backdrop, { opacity: backdropOpacity }],
2937
+ style: [styles15.backdrop, { opacity: backdropOpacity }],
2809
2938
  pointerEvents: "none"
2810
2939
  }
2811
2940
  ),
2812
2941
  /* @__PURE__ */ jsxRuntime.jsx(
2813
2942
  reactNative.Pressable,
2814
2943
  {
2815
- style: styles19.fill,
2944
+ style: styles15.fill,
2816
2945
  onPress: onBackdrop,
2817
2946
  accessibilityRole: "button",
2818
2947
  accessibilityLabel: "Dismiss",
2819
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNativeSafeAreaContext.SafeAreaView, { style: styles19.center, pointerEvents: "box-none", children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => {
2820
- }, style: styles19.cardWrap, children: /* @__PURE__ */ jsxRuntime.jsx(
2948
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNativeSafeAreaContext.SafeAreaView, { style: styles15.center, pointerEvents: "box-none", children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => {
2949
+ }, style: styles15.cardWrap, children: /* @__PURE__ */ jsxRuntime.jsx(
2821
2950
  reactNative.Animated.View,
2822
2951
  {
2823
2952
  accessibilityViewIsModal: true,
2824
2953
  accessibilityRole: "alert",
2825
2954
  style: [
2826
- styles19.card,
2955
+ styles15.card,
2827
2956
  {
2828
2957
  backgroundColor: theme.colors.surface,
2829
2958
  borderRadius: theme.radius.lg,
@@ -2845,7 +2974,7 @@ var _CenteredModal = React14.forwardRef(
2845
2974
  );
2846
2975
  _CenteredModal.displayName = "CenteredModal";
2847
2976
  var CenteredModal = React14__default.default.memo(_CenteredModal);
2848
- var styles19 = reactNative.StyleSheet.create({
2977
+ var styles15 = reactNative.StyleSheet.create({
2849
2978
  fill: { flex: 1 },
2850
2979
  backdrop: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "#000" },
2851
2980
  center: { flex: 1, alignItems: "center", justifyContent: "center", padding: 24 },
@@ -2999,7 +3128,12 @@ var useWireFeatures = (config) => {
2999
3128
  }, [canFetch, stableConfig]);
3000
3129
  return flags;
3001
3130
  };
3002
- var WireFeaturesContext = React14.createContext(null);
3131
+ var CONTEXT_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.context");
3132
+ var globalObj = global;
3133
+ if (!globalObj[CONTEXT_SYMBOL]) {
3134
+ globalObj[CONTEXT_SYMBOL] = React14.createContext(null);
3135
+ }
3136
+ var WireFeaturesContext = globalObj[CONTEXT_SYMBOL];
3003
3137
  var WireFeaturesProvider = ({
3004
3138
  config,
3005
3139
  flags,
@@ -3091,6 +3225,80 @@ var attributionMetadata = (a) => {
3091
3225
  return { attribution };
3092
3226
  };
3093
3227
 
3228
+ // src/context/userContext.ts
3229
+ var RESERVED_USER_CONTEXT_KEYS = [
3230
+ "device_key",
3231
+ "app_version",
3232
+ "app_build",
3233
+ "network_type",
3234
+ "session_count",
3235
+ "returning",
3236
+ "platform",
3237
+ "user_email",
3238
+ "user_email_hashed"
3239
+ ];
3240
+ var EXTRA_KEY_PREFIX = "custom.";
3241
+ var isWireScalar = (value) => {
3242
+ const t = typeof value;
3243
+ if (t === "string" || t === "boolean") return true;
3244
+ if (t === "number") return Number.isFinite(value);
3245
+ return false;
3246
+ };
3247
+ var hashEmailFnv1a = (email) => {
3248
+ const normalized = email.trim().toLowerCase();
3249
+ let hash = 2166136261;
3250
+ for (let i = 0; i < normalized.length; i++) {
3251
+ hash ^= normalized.charCodeAt(i);
3252
+ hash = Math.imul(hash, 16777619);
3253
+ }
3254
+ return (hash >>> 0).toString(16).padStart(8, "0");
3255
+ };
3256
+ var cleanString = (value) => {
3257
+ if (typeof value !== "string") return void 0;
3258
+ const trimmed = value.trim();
3259
+ return trimmed.length > 0 ? trimmed : void 0;
3260
+ };
3261
+ var namespaceExtra = (extra) => {
3262
+ const out = {};
3263
+ if (!extra || typeof extra !== "object") return out;
3264
+ for (const [key, value] of Object.entries(extra)) {
3265
+ const cleanKey = cleanString(key);
3266
+ if (!cleanKey) continue;
3267
+ if (!isWireScalar(value)) continue;
3268
+ out[`${EXTRA_KEY_PREFIX}${cleanKey}`] = value;
3269
+ }
3270
+ return out;
3271
+ };
3272
+ var resolveUserContext = (ctx = {}, opts = {}) => {
3273
+ var _a;
3274
+ const result = {};
3275
+ const bucket = {};
3276
+ const userId = sanitizeUserId(ctx.userId);
3277
+ if (userId) result.userId = userId;
3278
+ const deviceKey = cleanString(ctx.deviceKey);
3279
+ if (deviceKey) {
3280
+ result.deviceKey = deviceKey;
3281
+ bucket.device_key = deviceKey;
3282
+ }
3283
+ const appVersion = (_a = cleanString(ctx.appVersion)) != null ? _a : cleanString(opts.autoAppVersion);
3284
+ if (appVersion) {
3285
+ result.appVersion = appVersion;
3286
+ bucket.app_version = appVersion;
3287
+ }
3288
+ const email = cleanString(ctx.userEmail);
3289
+ if (email) {
3290
+ if (ctx.hashEmail) {
3291
+ bucket.user_email = hashEmailFnv1a(email);
3292
+ bucket.user_email_hashed = true;
3293
+ } else {
3294
+ bucket.user_email = email;
3295
+ }
3296
+ }
3297
+ Object.assign(bucket, namespaceExtra(ctx.extra));
3298
+ if (Object.keys(bucket).length > 0) result.userContext = bucket;
3299
+ return result;
3300
+ };
3301
+
3094
3302
  // src/session-analytics/reportSessionStart.ts
3095
3303
  var SESSION_STARTED_EVENT = "app.session_started";
3096
3304
  var _emitted = /* @__PURE__ */ new Set();
@@ -3103,6 +3311,7 @@ var reportSessionStart = (opts) => {
3103
3311
  const target = opts.target;
3104
3312
  if (!opts.sink && !(target == null ? void 0 : target.serverUrl)) return;
3105
3313
  const sessionId = (_a = opts.sessionId) != null ? _a : makeSessionId();
3314
+ setCurrentSessionId(sessionId);
3106
3315
  if (opts.once !== false) {
3107
3316
  if (_emitted.has(sessionId)) return;
3108
3317
  if (_emitted.size >= _GUARD_MAX) {
@@ -3159,7 +3368,7 @@ var useSessionStart = (config, options = {}) => {
3159
3368
  if (opts.enabled === false) return;
3160
3369
  const target = { serverUrl: cfg.serverUrl, apiKey: (_a = cfg.apiKey) != null ? _a : "" };
3161
3370
  const device = collectDeviceContext();
3162
- if (cfg.appVersion && !device.appVersion) device.appVersion = cfg.appVersion;
3371
+ if (cfg.appVersion) device.appVersion = cfg.appVersion;
3163
3372
  reportSessionStart({
3164
3373
  target,
3165
3374
  // A fresh per-open id each fire; the emitter's once-guard dedupes within the open.
@@ -3412,6 +3621,8 @@ var createEventQueue = (options) => {
3412
3621
  })();
3413
3622
  const postBatch = async (events) => {
3414
3623
  if (!(target == null ? void 0 : target.serverUrl) || events.length === 0) return false;
3624
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
3625
+ const timer = setTimeout(() => controller == null ? void 0 : controller.abort(), 15e3);
3415
3626
  try {
3416
3627
  const url = `${target.serverUrl.replace(/\/$/, "")}/v1/events`;
3417
3628
  const headers = { "Content-Type": "application/json" };
@@ -3419,11 +3630,14 @@ var createEventQueue = (options) => {
3419
3630
  const res = await fetch(url, {
3420
3631
  method: "POST",
3421
3632
  headers,
3422
- body: JSON.stringify({ events })
3633
+ body: JSON.stringify({ events }),
3634
+ signal: controller == null ? void 0 : controller.signal
3423
3635
  });
3424
3636
  return !!(res && res.ok);
3425
3637
  } catch {
3426
3638
  return false;
3639
+ } finally {
3640
+ clearTimeout(timer);
3427
3641
  }
3428
3642
  };
3429
3643
  const clearRetry = () => {
@@ -3528,7 +3742,7 @@ var useLifecycleEvents = (config, options = {}) => {
3528
3742
  const { config: cfg, options: opts } = latest.current;
3529
3743
  if (opts.enabled !== false) {
3530
3744
  const device = collectDeviceContext();
3531
- if ((cfg == null ? void 0 : cfg.appVersion) && !device.appVersion) device.appVersion = cfg.appVersion;
3745
+ if (cfg == null ? void 0 : cfg.appVersion) device.appVersion = cfg.appVersion;
3532
3746
  reportFirstOpen({
3533
3747
  target: targetOf(cfg),
3534
3748
  sink: resolveSink(),
@@ -3550,7 +3764,7 @@ var useLifecycleEvents = (config, options = {}) => {
3550
3764
  if (opts.enabled === false) return;
3551
3765
  if (!(cfg == null ? void 0 : cfg.serverUrl) && !opts.sink) return;
3552
3766
  const device = collectDeviceContext();
3553
- if ((cfg == null ? void 0 : cfg.appVersion) && !device.appVersion) device.appVersion = cfg.appVersion;
3767
+ if (cfg == null ? void 0 : cfg.appVersion) device.appVersion = cfg.appVersion;
3554
3768
  reportSessionStart({
3555
3769
  target: targetOf(cfg),
3556
3770
  sink: resolveSink(),
@@ -3594,6 +3808,7 @@ exports.DEFAULT_FEATURES_TTL_MS = DEFAULT_FEATURES_TTL_MS;
3594
3808
  exports.DEFAULT_SESSION_TTL_MS = DEFAULT_SESSION_TTL_MS;
3595
3809
  exports.DemoOnboarding = DemoOnboarding;
3596
3810
  exports.DoneBlock = DoneBlock;
3811
+ exports.EXTRA_KEY_PREFIX = EXTRA_KEY_PREFIX;
3597
3812
  exports.ErrorBlock = ErrorBlock;
3598
3813
  exports.FIRST_OPEN_EVENT = FIRST_OPEN_EVENT;
3599
3814
  exports.IllustrationProvider = IllustrationProvider;
@@ -3605,6 +3820,7 @@ exports.OnboardingButton = Button;
3605
3820
  exports.OnboardingFlow = OnboardingFlow;
3606
3821
  exports.OnboardingScaffold = OnboardingScaffold;
3607
3822
  exports.OnboardingThemeProvider = OnboardingThemeProvider;
3823
+ exports.RESERVED_USER_CONTEXT_KEYS = RESERVED_USER_CONTEXT_KEYS;
3608
3824
  exports.SESSION_STARTED_EVENT = SESSION_STARTED_EVENT;
3609
3825
  exports.SelectionCard = SelectionCard;
3610
3826
  exports.StatusCard = StatusCard;
@@ -3621,17 +3837,22 @@ exports.defaultIllustrations = defaultIllustrations;
3621
3837
  exports.defaultOnboardingTheme = defaultOnboardingTheme;
3622
3838
  exports.defaultWireFeatures = defaultWireFeatures;
3623
3839
  exports.deriveAnswers = deriveAnswers;
3840
+ exports.detectAppVersion = detectAppVersion;
3624
3841
  exports.featuresCacheKey = featuresCacheKey;
3625
3842
  exports.featuresEqual = featuresEqual;
3626
3843
  exports.fetchWireFeatures = fetchWireFeatures;
3627
3844
  exports.firstOpenStorageKey = firstOpenStorageKey;
3845
+ exports.getCurrentSessionId = getCurrentSessionId;
3846
+ exports.hashEmailFnv1a = hashEmailFnv1a;
3628
3847
  exports.identifyOnboarding = identifyOnboarding;
3629
3848
  exports.isFeaturesFresh = isFeaturesFresh;
3630
3849
  exports.isOnboardingEnabled = isOnboardingEnabled;
3850
+ exports.isWireScalar = isWireScalar;
3631
3851
  exports.loadPersistedSession = loadPersistedSession;
3632
3852
  exports.makeSessionId = makeSessionId;
3633
3853
  exports.mergeTheme = mergeTheme;
3634
3854
  exports.motionSpec = motionSpec_exports;
3855
+ exports.namespaceExtra = namespaceExtra;
3635
3856
  exports.onboardingComponents = onboardingComponents;
3636
3857
  exports.parseWireFeatures = parseWireFeatures;
3637
3858
  exports.peekPersistedSession = peekPersistedSession;
@@ -3641,11 +3862,14 @@ exports.reportClientEvent = reportClientEvent;
3641
3862
  exports.reportClientEvents = reportClientEvents;
3642
3863
  exports.reportFirstOpen = reportFirstOpen;
3643
3864
  exports.reportSessionStart = reportSessionStart;
3865
+ exports.resetCurrentSessionId = resetCurrentSessionId;
3644
3866
  exports.resetFirstOpenLatch = resetFirstOpenLatch;
3645
3867
  exports.resetSessionStartGuard = resetSessionStartGuard;
3868
+ exports.resolveUserContext = resolveUserContext;
3646
3869
  exports.sanitizeUserId = sanitizeUserId;
3647
3870
  exports.savePersistedSession = savePersistedSession;
3648
3871
  exports.sessionStorageKey = sessionStorageKey;
3872
+ exports.setCurrentSessionId = setCurrentSessionId;
3649
3873
  exports.themeFromBrand = themeFromBrand;
3650
3874
  exports.toAnalyticsEvent = toAnalyticsEvent;
3651
3875
  exports.useIllustration = useIllustration;