@wireai/activation 0.2.0 → 0.4.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 (63) hide show
  1. package/dist/analytics/index.d.mts +69 -2
  2. package/dist/analytics/index.d.ts +69 -2
  3. package/dist/analytics/index.js +123 -2
  4. package/dist/analytics/index.js.map +1 -1
  5. package/dist/analytics/index.mjs +122 -3
  6. package/dist/analytics/index.mjs.map +1 -1
  7. package/dist/coachmarks/index.js +6 -1
  8. package/dist/coachmarks/index.js.map +1 -1
  9. package/dist/coachmarks/index.mjs +6 -1
  10. package/dist/coachmarks/index.mjs.map +1 -1
  11. package/dist/{eventQueue-CxKi7Qd5.d.mts → eventQueue-CA1d8Fmn.d.mts} +7 -3
  12. package/dist/{eventQueue-rV1dtJJR.d.ts → eventQueue-CrNB9gzH.d.ts} +7 -3
  13. package/dist/index.d.mts +5 -4
  14. package/dist/index.d.ts +5 -4
  15. package/dist/index.js +220 -151
  16. package/dist/index.js.map +1 -1
  17. package/dist/index.mjs +221 -152
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/questionnaire/index.d.mts +1 -1
  20. package/dist/questionnaire/index.d.ts +1 -1
  21. package/dist/questionnaire/index.js +29 -5
  22. package/dist/questionnaire/index.js.map +1 -1
  23. package/dist/questionnaire/index.mjs +30 -6
  24. package/dist/questionnaire/index.mjs.map +1 -1
  25. package/dist/reviews/index.d.mts +1 -1
  26. package/dist/reviews/index.d.ts +1 -1
  27. package/dist/reviews/index.js +40 -9
  28. package/dist/reviews/index.js.map +1 -1
  29. package/dist/reviews/index.mjs +42 -11
  30. package/dist/reviews/index.mjs.map +1 -1
  31. package/dist/showcase/index.d.mts +1 -14
  32. package/dist/showcase/index.d.ts +1 -14
  33. package/dist/showcase/index.js +8 -2
  34. package/dist/showcase/index.js.map +1 -1
  35. package/dist/showcase/index.mjs +8 -2
  36. package/dist/showcase/index.mjs.map +1 -1
  37. package/package.json +3 -2
  38. package/src/OnboardingFlow.tsx +8 -4
  39. package/src/WireOnboarding.tsx +8 -3
  40. package/src/analytics/analyticsFacade.ts +184 -0
  41. package/src/analytics/eventQueue.ts +5 -0
  42. package/src/analytics/index.ts +12 -0
  43. package/src/analytics/reportClientEvent.ts +11 -2
  44. package/src/analytics/useAnalytics.ts +42 -0
  45. package/src/analytics/useScreenTracking.ts +23 -1
  46. package/src/cards/InterstitialCard.tsx +1 -1
  47. package/src/cards/NumberStepperCard.tsx +12 -7
  48. package/src/cards/StatusCard.tsx +13 -8
  49. package/src/cards/TextInputCard.tsx +10 -5
  50. package/src/components/AnimatedSparkle.tsx +20 -3
  51. package/src/components/Button.tsx +10 -1
  52. package/src/components/CardHandoff.tsx +2 -6
  53. package/src/components/CardLayout.tsx +16 -10
  54. package/src/components/Illustration.tsx +9 -5
  55. package/src/components/LoadingBlock.tsx +44 -29
  56. package/src/components/LoadingScreen.tsx +3 -2
  57. package/src/components/OnboardingScaffold.tsx +21 -23
  58. package/src/features/WireFeaturesProvider.tsx +7 -2
  59. package/src/questionnaire/QuestionnaireGate.tsx +15 -3
  60. package/src/reviews/ReviewGate.tsx +27 -7
  61. package/src/reviews/ReviewModal.tsx +4 -0
  62. package/src/showcase/FeatureShowcase.tsx +2 -1
  63. package/src/components/loaderChrome.ts +0 -28
package/dist/index.js CHANGED
@@ -85,11 +85,15 @@ var useOnboardingTheme = () => {
85
85
  const ctx = React14.useContext(OnboardingThemeContext);
86
86
  return ctx != null ? ctx : mergeTheme();
87
87
  };
88
- var IllustrationContext = React14.createContext({});
88
+ var EMPTY_REGISTRY = {};
89
+ var IllustrationContext = React14.createContext(EMPTY_REGISTRY);
89
90
  var IllustrationProvider = ({
90
91
  registry,
91
92
  children
92
- }) => /* @__PURE__ */ jsxRuntime.jsx(IllustrationContext.Provider, { value: registry != null ? registry : {}, children });
93
+ }) => {
94
+ const value = React14.useMemo(() => registry != null ? registry : EMPTY_REGISTRY, [registry]);
95
+ return /* @__PURE__ */ jsxRuntime.jsx(IllustrationContext.Provider, { value, children });
96
+ };
93
97
  var useIllustration = (name) => {
94
98
  const registry = React14.useContext(IllustrationContext);
95
99
  if (!name) return void 0;
@@ -380,23 +384,33 @@ var _AnimatedSparkle = ({ size = 30, color, variant = "spin" }) => {
380
384
  outputRange: [1, LOADER_PULSE_MIN_OPACITY]
381
385
  });
382
386
  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(
387
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: [styles2.box, { width: size, height: size }, animatedStyle], children: /* @__PURE__ */ jsxRuntime.jsx(
384
388
  reactNative.Text,
385
389
  {
386
390
  accessibilityElementsHidden: true,
387
391
  importantForAccessibility: "no",
388
- style: [styles2.glyph, { fontSize: size, lineHeight: size * 1.1, color: color != null ? color : t.colors.primary }],
392
+ style: [
393
+ styles2.glyph,
394
+ {
395
+ width: size,
396
+ fontSize: size,
397
+ lineHeight: size,
398
+ color: color != null ? color : t.colors.primary
399
+ }
400
+ ],
389
401
  children: "\u2726"
390
402
  }
391
403
  ) });
392
404
  };
393
405
  var AnimatedSparkle = React14__default.default.memo(_AnimatedSparkle);
394
406
  var styles2 = reactNative.StyleSheet.create({
395
- glyph: { textAlign: "center" }
407
+ box: { alignItems: "center", justifyContent: "center" },
408
+ glyph: {
409
+ textAlign: "center",
410
+ textAlignVertical: "center",
411
+ includeFontPadding: false
412
+ }
396
413
  });
397
- var LoaderChromeContext = React14.createContext({ offsetY: 0 });
398
- var LoaderChromeProvider = LoaderChromeContext.Provider;
399
- var useLoaderChrome = () => React14.useContext(LoaderChromeContext);
400
414
  var STAGE_SIZE = 96;
401
415
  var ORBIT_DOT = 9;
402
416
  var OrbitDot = ({ color, reduced }) => {
@@ -460,39 +474,45 @@ var _LoadingBlock = ({
460
474
  }) => {
461
475
  const t = useOnboardingTheme();
462
476
  const reduced = useReducedMotion();
463
- const { offsetY } = useLoaderChrome();
464
477
  const [showSkeleton, setShowSkeleton] = React14.useState(false);
465
478
  React14.useEffect(() => {
466
479
  const timer = setTimeout(() => setShowSkeleton(true), SKELETON_DELAY_MS);
467
480
  return () => clearTimeout(timer);
468
481
  }, []);
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
- );
482
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles3.center, { padding: t.spacing.lg }], children: [
483
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.stage, children: [
484
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles3.ring, { borderColor: t.colors.border, borderRadius: t.radius.full }] }),
485
+ /* @__PURE__ */ jsxRuntime.jsx(OrbitDot, { color: t.colors.primary, reduced }),
486
+ /* @__PURE__ */ jsxRuntime.jsx(AnimatedSparkle, { size: 40, variant: "pulse" })
487
+ ] }),
488
+ /* @__PURE__ */ jsxRuntime.jsxs(
489
+ reactNative.View,
490
+ {
491
+ style: [
492
+ styles3.labels,
493
+ { marginTop: STAGE_SIZE / 2 + t.spacing.lg, gap: t.spacing.md, paddingHorizontal: t.spacing.lg }
494
+ ],
495
+ pointerEvents: "none",
496
+ children: [
497
+ title ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [headingStyle(t.fonts), { color: t.colors.text, textAlign: "center" }], children: title }) : null,
498
+ hint ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }], children: hint }) : null,
499
+ showSkeleton ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles3.skeleton, { gap: t.spacing.sm, marginTop: t.spacing.sm }], children: [
500
+ /* @__PURE__ */ jsxRuntime.jsx(SkeletonLine, { width: "80%", delay: 0, color: t.colors.surface, reduced }),
501
+ /* @__PURE__ */ jsxRuntime.jsx(SkeletonLine, { width: "60%", delay: 200, color: t.colors.surface, reduced })
502
+ ] }) : null
503
+ ]
504
+ }
505
+ )
506
+ ] });
492
507
  };
493
508
  var LoadingBlock = React14__default.default.memo(_LoadingBlock);
494
509
  var styles3 = reactNative.StyleSheet.create({
495
510
  center: { flex: 1, alignItems: "center", justifyContent: "center" },
511
+ // Absolutely-positioned label column, pinned just under the centered stage: its top
512
+ // edge starts at the flow center (top: 50%) and the inline marginTop (STAGE_SIZE/2 +
513
+ // a gap) drops it below the stage. Out of flow → it never moves the stage, so the
514
+ // 300ms skeleton reveal grows DOWNWARD only and the icon stays centered.
515
+ labels: { position: "absolute", top: "50%", left: 0, right: 0, alignItems: "center" },
496
516
  stage: {
497
517
  width: STAGE_SIZE,
498
518
  height: STAGE_SIZE,
@@ -575,7 +595,6 @@ var styles5 = reactNative.StyleSheet.create({
575
595
  track: { height: 6, width: "100%", overflow: "hidden" },
576
596
  fill: { height: 6 }
577
597
  });
578
- var PROGRESS_TRACK = 6;
579
598
  var _OnboardingScaffold = ({
580
599
  step,
581
600
  complete,
@@ -587,33 +606,41 @@ var _OnboardingScaffold = ({
587
606
  children
588
607
  }) => {
589
608
  const t = useOnboardingTheme();
609
+ const styles16 = React14.useMemo(() => makeStyles(t), [t]);
590
610
  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
611
  return /* @__PURE__ */ jsxRuntime.jsxs(
594
612
  reactNativeSafeAreaContext.SafeAreaView,
595
613
  {
596
614
  edges: ["top", "bottom"],
597
- style: [styles6.flex, { backgroundColor: t.colors.background }],
615
+ style: [styles16.flex, { backgroundColor: t.colors.background }],
598
616
  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
617
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles16.header, { paddingHorizontal: t.spacing.md, paddingTop: t.spacing.md }], children: [
618
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles16.progressWrap, children: /* @__PURE__ */ jsxRuntime.jsx(StepProgress, { step, complete, approxScreens }) }),
619
+ 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
620
  ] }),
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
621
+ /* @__PURE__ */ jsxRuntime.jsx(
622
+ reactNative.View,
623
+ {
624
+ style: [styles16.body, { paddingHorizontal: t.spacing.md, paddingTop: t.spacing.md }],
625
+ children
626
+ }
627
+ ),
628
+ 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
629
  ]
606
630
  }
607
631
  );
608
632
  };
609
633
  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
- });
634
+ function makeStyles(t) {
635
+ return reactNative.StyleSheet.create({
636
+ flex: { flex: 1 },
637
+ header: { flexDirection: "row", alignItems: "center" },
638
+ progressWrap: { flex: 1 },
639
+ body: { flex: 1 },
640
+ footer: { flexDirection: "row", justifyContent: "flex-start" },
641
+ skip: { marginLeft: t.spacing.md }
642
+ });
643
+ }
617
644
  var _Button = ({
618
645
  title,
619
646
  onPress,
@@ -644,6 +671,14 @@ var _Button = ({
644
671
  },
645
672
  [pressT, reduced]
646
673
  );
674
+ const lastPress = React14.useRef(0);
675
+ const handlePress = React14.useCallback(() => {
676
+ if (!onPress) return;
677
+ const now = Date.now();
678
+ if (now - lastPress.current < 500) return;
679
+ lastPress.current = now;
680
+ onPress();
681
+ }, [onPress]);
647
682
  const handlePressIn = React14.useCallback(() => pressTo(1), [pressTo]);
648
683
  const handlePressOut = React14.useCallback(() => pressTo(0), [pressTo]);
649
684
  const scale = pressT.interpolate({
@@ -657,19 +692,19 @@ var _Button = ({
657
692
  return /* @__PURE__ */ jsxRuntime.jsx(
658
693
  reactNative.Pressable,
659
694
  {
660
- onPress: disabled ? void 0 : onPress,
695
+ onPress: disabled ? void 0 : handlePress,
661
696
  onPressIn: disabled ? void 0 : handlePressIn,
662
697
  onPressOut: disabled ? void 0 : handlePressOut,
663
698
  disabled,
664
699
  accessibilityRole: "button",
665
700
  accessibilityState: { disabled },
666
- style: fullWidth ? styles7.fill : void 0,
701
+ style: fullWidth ? styles6.fill : void 0,
667
702
  children: /* @__PURE__ */ jsxRuntime.jsx(
668
703
  reactNative.Animated.View,
669
704
  {
670
705
  style: [
671
- styles7.base,
672
- fullWidth && styles7.fill,
706
+ styles6.base,
707
+ fullWidth && styles6.fill,
673
708
  {
674
709
  borderRadius: t.button.radius,
675
710
  minHeight: t.button.height,
@@ -697,7 +732,7 @@ var _Button = ({
697
732
  );
698
733
  };
699
734
  var Button = React14__default.default.memo(_Button);
700
- var styles7 = reactNative.StyleSheet.create({
735
+ var styles6 = reactNative.StyleSheet.create({
701
736
  base: { alignItems: "center", justifyContent: "center" },
702
737
  fill: { width: "100%" }
703
738
  });
@@ -708,14 +743,14 @@ var _ErrorBlock = ({
708
743
  onRetry
709
744
  }) => {
710
745
  const t = useOnboardingTheme();
711
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles8.center, { gap: t.spacing.md, padding: t.spacing.lg }], children: [
746
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles7.center, { gap: t.spacing.md, padding: t.spacing.lg }], children: [
712
747
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [headingStyle(t.fonts), { color: t.colors.text, textAlign: "center" }], children: title }),
713
748
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }], children: message }),
714
749
  onRetry ? /* @__PURE__ */ jsxRuntime.jsx(Button, { title: retryLabel, onPress: onRetry, variant: "outline" }) : null
715
750
  ] });
716
751
  };
717
752
  var ErrorBlock = React14__default.default.memo(_ErrorBlock);
718
- var styles8 = reactNative.StyleSheet.create({
753
+ var styles7 = reactNative.StyleSheet.create({
719
754
  center: { flex: 1, alignItems: "center", justifyContent: "center" }
720
755
  });
721
756
  var _CardLayout = ({
@@ -727,9 +762,10 @@ var _CardLayout = ({
727
762
  titleNode
728
763
  }) => {
729
764
  const t = useOnboardingTheme();
765
+ const styles16 = React14.useMemo(() => makeStyles2(t), [t]);
730
766
  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: [
767
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.KeyboardAvoidingView, { style: styles16.fill, behavior: "padding", children: [
768
+ hasHeader ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles16.header, children: [
733
769
  titleNode != null ? titleNode : title ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [labelStyle(t.fonts), { color: t.colors.text }], children: title }) : null,
734
770
  subtitle ? /* @__PURE__ */ jsxRuntime.jsx(
735
771
  reactNative.Text,
@@ -745,10 +781,10 @@ var _CardLayout = ({
745
781
  /* @__PURE__ */ jsxRuntime.jsx(
746
782
  reactNative.ScrollView,
747
783
  {
748
- style: styles9.fill,
784
+ style: styles16.fill,
749
785
  contentContainerStyle: [
750
- styles9.scrollContent,
751
- align === "center" && styles9.center
786
+ styles16.scrollContent,
787
+ align === "center" && styles16.center
752
788
  ],
753
789
  keyboardShouldPersistTaps: "handled",
754
790
  keyboardDismissMode: "on-drag",
@@ -756,17 +792,21 @@ var _CardLayout = ({
756
792
  children
757
793
  }
758
794
  ),
759
- footer ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { paddingTop: t.spacing.lg }, children: footer }) : null
795
+ footer ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles16.footer, children: footer }) : null
760
796
  ] });
761
797
  };
762
798
  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
- });
799
+ function makeStyles2(t) {
800
+ return reactNative.StyleSheet.create({
801
+ fill: { flex: 1 },
802
+ header: { marginBottom: t.spacing.lg },
803
+ footer: { paddingTop: t.spacing.lg },
804
+ // flexGrow lets a short content block grow to fill (pinning the footer) while a
805
+ // tall one scrolls past the visible area.
806
+ scrollContent: { flexGrow: 1 },
807
+ center: { justifyContent: "center" }
808
+ });
809
+ }
770
810
  var easeWire2 = reactNative.Easing.bezier(...WIRE_BEZIER);
771
811
  var BADGE_SIZE = 64;
772
812
  var BURST_SIZE = 104;
@@ -841,14 +881,14 @@ var _CompletionView = ({
841
881
  {
842
882
  align: "center",
843
883
  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: [
884
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles8.center, { gap: t.spacing.md }], children: [
885
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles8.badgeStage, children: [
846
886
  /* @__PURE__ */ jsxRuntime.jsx(
847
887
  reactNative.Animated.View,
848
888
  {
849
889
  pointerEvents: "none",
850
890
  style: [
851
- styles10.burst,
891
+ styles8.burst,
852
892
  {
853
893
  backgroundColor: t.colors.primarySoft,
854
894
  borderRadius: t.radius.full,
@@ -862,7 +902,7 @@ var _CompletionView = ({
862
902
  reactNative.Animated.View,
863
903
  {
864
904
  style: [
865
- styles10.badge,
905
+ styles8.badge,
866
906
  {
867
907
  backgroundColor: t.colors.primarySoft,
868
908
  borderRadius: t.radius.full,
@@ -870,7 +910,7 @@ var _CompletionView = ({
870
910
  opacity: pop
871
911
  }
872
912
  ],
873
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles10.check, { color: t.colors.primary }], children: "\u2713" })
913
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles8.check, { color: t.colors.primary }], children: "\u2713" })
874
914
  }
875
915
  )
876
916
  ] }),
@@ -881,7 +921,7 @@ var _CompletionView = ({
881
921
  );
882
922
  };
883
923
  var CompletionView = React14__default.default.memo(_CompletionView);
884
- var styles10 = reactNative.StyleSheet.create({
924
+ var styles8 = reactNative.StyleSheet.create({
885
925
  center: { width: "100%", alignItems: "center", justifyContent: "center" },
886
926
  badgeStage: {
887
927
  width: BURST_SIZE,
@@ -904,7 +944,6 @@ var _CardHandoff = ({
904
944
  const enterT = React14.useRef(new reactNative.Animated.Value(0)).current;
905
945
  const exitT = React14.useRef(new reactNative.Animated.Value(0)).current;
906
946
  const prev = React14.useRef({ key: transitionKey, node: children, variant });
907
- const [enterVariant, setEnterVariant] = React14.useState(variant);
908
947
  const running = React14.useRef({});
909
948
  const mounted = React14.useRef(false);
910
949
  React14.useEffect(() => {
@@ -913,7 +952,6 @@ var _CardHandoff = ({
913
952
  prev.current = { key: transitionKey, node: children, variant };
914
953
  if (!mounted.current) {
915
954
  mounted.current = true;
916
- setEnterVariant(variant);
917
955
  running.current.enter = startEnter(enterT, variant, reduced, 0);
918
956
  return;
919
957
  }
@@ -921,7 +959,6 @@ var _CardHandoff = ({
921
959
  (_a = running.current.enter) == null ? void 0 : _a.stop();
922
960
  (_b = running.current.exit) == null ? void 0 : _b.stop();
923
961
  setLeaving(last);
924
- setEnterVariant(variant);
925
962
  enterT.setValue(0);
926
963
  exitT.setValue(0);
927
964
  const exitDuration = reduced ? REDUCED_FADE_MS : last.variant === "fade" ? LOADER_HANDOFF_FADE_MS : CARD_EXIT_MS;
@@ -952,7 +989,7 @@ var _CardHandoff = ({
952
989
  );
953
990
  const enterStyle = {
954
991
  opacity: enterT,
955
- transform: transformFor(enterVariant, enterT, reduced, "enter")
992
+ transform: transformFor(variant, enterT, reduced, "enter")
956
993
  };
957
994
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
958
995
  leaving ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -969,7 +1006,7 @@ var _CardHandoff = ({
969
1006
  children: leaving.node
970
1007
  }
971
1008
  ) : null,
972
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: [styles11.fill, enterStyle], children })
1009
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: [styles9.fill, enterStyle], children }, variant)
973
1010
  ] });
974
1011
  };
975
1012
  var startEnter = (value, variant, reduced, delay, onDone) => {
@@ -1010,7 +1047,7 @@ var transformFor = (variant, t, reduced, direction) => {
1010
1047
  ];
1011
1048
  };
1012
1049
  var CardHandoff = React14__default.default.memo(_CardHandoff);
1013
- var styles11 = reactNative.StyleSheet.create({
1050
+ var styles9 = reactNative.StyleSheet.create({
1014
1051
  fill: { flex: 1 }
1015
1052
  });
1016
1053
 
@@ -1140,6 +1177,10 @@ var OnboardingFlow = ({
1140
1177
  onEventRef.current = onEvent;
1141
1178
  const onErrorRef = React14.useRef(onError);
1142
1179
  onErrorRef.current = onError;
1180
+ const onCompleteRef = React14.useRef(onComplete);
1181
+ onCompleteRef.current = onComplete;
1182
+ const validatorsRef = React14.useRef(validators);
1183
+ validatorsRef.current = validators;
1143
1184
  const reportTargetRef = React14.useRef(reportTarget);
1144
1185
  reportTargetRef.current = reportTarget;
1145
1186
  const sessionIdRef = React14.useRef(sessionId);
@@ -1255,8 +1296,8 @@ var OnboardingFlow = ({
1255
1296
  const handleFinish = React14.useCallback(() => {
1256
1297
  if (finished.current) return;
1257
1298
  finished.current = true;
1258
- onComplete({ answers: deriveAnswers(messages), raw: messages });
1259
- }, [messages, onComplete]);
1299
+ onCompleteRef.current({ answers: deriveAnswers(messages), raw: messages });
1300
+ }, [messages]);
1260
1301
  const handleRetry = React14.useCallback(() => {
1261
1302
  attempts.current = 0;
1262
1303
  degradedRef.current = false;
@@ -1284,9 +1325,10 @@ var OnboardingFlow = ({
1284
1325
  }, []);
1285
1326
  const wrappedActions = React14.useCallback(
1286
1327
  (messageId) => {
1328
+ var _a2;
1287
1329
  const base = makeActions(messageId);
1288
1330
  const key = progress.key;
1289
- const validator = key ? validators == null ? void 0 : validators[key] : void 0;
1331
+ const validator = key ? (_a2 = validatorsRef.current) == null ? void 0 : _a2[key] : void 0;
1290
1332
  if (!validator) return base;
1291
1333
  const out = { ...base };
1292
1334
  for (const name of ANSWER_CALLBACKS) {
@@ -1297,12 +1339,12 @@ var OnboardingFlow = ({
1297
1339
  if (typeof value !== "string") return original(...args);
1298
1340
  setValidating(true);
1299
1341
  validator(value).then((res) => {
1300
- var _a2;
1342
+ var _a3;
1301
1343
  if (res.ok) {
1302
1344
  setValidationError(void 0);
1303
1345
  original(...args);
1304
1346
  } else {
1305
- setValidationError((_a2 = res.error) != null ? _a2 : "That value isn't available.");
1347
+ setValidationError((_a3 = res.error) != null ? _a3 : "That value isn't available.");
1306
1348
  }
1307
1349
  }).catch(() => {
1308
1350
  original(...args);
@@ -1311,7 +1353,7 @@ var OnboardingFlow = ({
1311
1353
  }
1312
1354
  return out;
1313
1355
  },
1314
- [makeActions, progress.key, validators]
1356
+ [makeActions, progress.key]
1315
1357
  );
1316
1358
  if (degraded) {
1317
1359
  if (fallbackFlow !== void 0) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallbackFlow });
@@ -1466,7 +1508,7 @@ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
1466
1508
  reactNative.Animated.View,
1467
1509
  {
1468
1510
  style: [
1469
- styles12.chip,
1511
+ styles10.chip,
1470
1512
  {
1471
1513
  borderRadius: t.radius.full,
1472
1514
  paddingHorizontal: t.spacing.md,
@@ -1483,7 +1525,7 @@ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
1483
1525
  pointerEvents: "none",
1484
1526
  style: [
1485
1527
  reactNative.StyleSheet.absoluteFill,
1486
- styles12.fillOverlay,
1528
+ styles10.fillOverlay,
1487
1529
  {
1488
1530
  borderRadius: t.radius.full,
1489
1531
  borderColor: t.colors.primary,
@@ -1497,7 +1539,7 @@ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
1497
1539
  reactNative.Animated.Text,
1498
1540
  {
1499
1541
  style: [
1500
- styles12.check,
1542
+ styles10.check,
1501
1543
  { color: t.colors.onPrimary, opacity: checkT, transform: [{ scale: checkScale }] }
1502
1544
  ],
1503
1545
  children: "\u2713"
@@ -1508,7 +1550,7 @@ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
1508
1550
  {
1509
1551
  style: [
1510
1552
  captionStyle(t.fonts),
1511
- styles12.chipLabel,
1553
+ styles10.chipLabel,
1512
1554
  { color: isSelected ? t.colors.onPrimary : t.colors.textMuted }
1513
1555
  ],
1514
1556
  children: chip
@@ -1570,7 +1612,7 @@ var _ChipSelectCard = ({
1570
1612
  disabled: selected.length === 0 || submitted
1571
1613
  }
1572
1614
  ),
1573
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles12.chipsRow, { gap: t.spacing.sm + t.spacing.xs }], children: chips.map((chip) => /* @__PURE__ */ jsxRuntime.jsx(
1615
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles10.chipsRow, { gap: t.spacing.sm + t.spacing.xs }], children: chips.map((chip) => /* @__PURE__ */ jsxRuntime.jsx(
1574
1616
  ChipItem,
1575
1617
  {
1576
1618
  chip,
@@ -1590,7 +1632,7 @@ var ChipSelectCard = {
1590
1632
  propsSchema: schema,
1591
1633
  defaultProps: { multiSelect: true, submitLabel: "Continue" }
1592
1634
  };
1593
- var styles12 = reactNative.StyleSheet.create({
1635
+ var styles10 = reactNative.StyleSheet.create({
1594
1636
  chipsRow: { flexDirection: "row", flexWrap: "wrap" },
1595
1637
  chip: { borderWidth: 1.5, flexDirection: "row", alignItems: "center", gap: 6, overflow: "hidden" },
1596
1638
  fillOverlay: { borderWidth: 1.5 },
@@ -1622,6 +1664,7 @@ var _TextInputCard = ({
1622
1664
  lines = 6
1623
1665
  }) => {
1624
1666
  const t = useOnboardingTheme();
1667
+ const styles16 = React14.useMemo(() => makeStyles3(t), [t]);
1625
1668
  const { height: screenHeight } = reactNative.useWindowDimensions();
1626
1669
  const [value, setValue] = React14.useState("");
1627
1670
  const [submitted, setSubmitted] = React14.useState(false);
@@ -1648,7 +1691,7 @@ var _TextInputCard = ({
1648
1691
  disabled: !value.trim() || locked
1649
1692
  }
1650
1693
  ),
1651
- children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { gap: t.spacing.sm }, children: [
1694
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles16.row, children: [
1652
1695
  /* @__PURE__ */ jsxRuntime.jsx(
1653
1696
  reactNative.TextInput,
1654
1697
  {
@@ -1662,7 +1705,7 @@ var _TextInputCard = ({
1662
1705
  textAlignVertical: multiline ? "top" : "center",
1663
1706
  style: [
1664
1707
  bodyStyle(t.fonts),
1665
- styles13.input,
1708
+ styles16.input,
1666
1709
  {
1667
1710
  color: t.colors.text,
1668
1711
  backgroundColor: t.colors.surface,
@@ -1686,9 +1729,12 @@ var TextInputCard = {
1686
1729
  propsSchema: schema2,
1687
1730
  defaultProps: { submitLabel: "Submit", multiline: true, lines: 6 }
1688
1731
  };
1689
- var styles13 = reactNative.StyleSheet.create({
1690
- input: { borderWidth: 1 }
1691
- });
1732
+ function makeStyles3(t) {
1733
+ return reactNative.StyleSheet.create({
1734
+ row: { gap: t.spacing.sm },
1735
+ input: { borderWidth: 1 }
1736
+ });
1737
+ }
1692
1738
  var easeWire5 = reactNative.Easing.bezier(...WIRE_BEZIER);
1693
1739
  var optionObjectSchema = zod.z.object({
1694
1740
  value: zod.z.string().describe("Option value"),
@@ -1767,7 +1813,7 @@ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
1767
1813
  reactNative.Animated.View,
1768
1814
  {
1769
1815
  style: [
1770
- styles14.option,
1816
+ styles11.option,
1771
1817
  {
1772
1818
  padding: t.spacing.md,
1773
1819
  borderRadius: t.radius.md,
@@ -1784,7 +1830,7 @@ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
1784
1830
  pointerEvents: "none",
1785
1831
  style: [
1786
1832
  reactNative.StyleSheet.absoluteFill,
1787
- styles14.selectedOverlay,
1833
+ styles11.selectedOverlay,
1788
1834
  {
1789
1835
  borderRadius: t.radius.md,
1790
1836
  borderColor: t.colors.primary,
@@ -1798,7 +1844,7 @@ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
1798
1844
  reactNative.View,
1799
1845
  {
1800
1846
  style: [
1801
- styles14.indicator,
1847
+ styles11.indicator,
1802
1848
  { borderRadius: t.radius.full },
1803
1849
  isSelected ? { borderColor: t.colors.primary, backgroundColor: t.colors.primary } : { borderColor: t.colors.borderStrong, backgroundColor: "transparent" }
1804
1850
  ],
@@ -1806,7 +1852,7 @@ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
1806
1852
  reactNative.Animated.Text,
1807
1853
  {
1808
1854
  style: [
1809
- styles14.check,
1855
+ styles11.check,
1810
1856
  { color: t.colors.onPrimary, opacity: checkT, transform: [{ scale: checkScale }] }
1811
1857
  ],
1812
1858
  children: "\u2713"
@@ -1885,7 +1931,7 @@ var SelectionCard = {
1885
1931
  propsSchema: schema3,
1886
1932
  defaultProps: { submitLabel: "Continue", multiSelect: false }
1887
1933
  };
1888
- var styles14 = reactNative.StyleSheet.create({
1934
+ var styles11 = reactNative.StyleSheet.create({
1889
1935
  option: { borderWidth: 1.5, flexDirection: "row", alignItems: "center", overflow: "hidden" },
1890
1936
  selectedOverlay: { borderWidth: 1.5 },
1891
1937
  indicator: { width: 24, height: 24, borderWidth: 2, alignItems: "center", justifyContent: "center" },
@@ -1907,17 +1953,22 @@ var STATUS_ICONS = {
1907
1953
  info: "\u2139"
1908
1954
  };
1909
1955
  var _StatusCard = ({ status, title, message, ctaLabel, onContinue, onPress, onSubmit }) => {
1910
- var _a, _b;
1956
+ var _a;
1911
1957
  const t = useOnboardingTheme();
1912
1958
  const reduced = useReducedMotion();
1913
1959
  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;
1960
+ const color = React14.useMemo(() => {
1961
+ switch (status) {
1962
+ case "success":
1963
+ return t.colors.success;
1964
+ case "warning":
1965
+ return t.colors.warning;
1966
+ case "error":
1967
+ return t.colors.error;
1968
+ default:
1969
+ return t.colors.info;
1970
+ }
1971
+ }, [status, t.colors]);
1921
1972
  const popT = React14.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
1922
1973
  const copyT = React14.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
1923
1974
  React14.useEffect(() => {
@@ -1959,8 +2010,8 @@ var _StatusCard = ({ status, title, message, ctaLabel, onContinue, onPress, onSu
1959
2010
  {
1960
2011
  align: "center",
1961
2012
  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: [
2013
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles12.center, { gap: t.spacing.sm }], children: [
2014
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles12.titleRow, { gap: t.spacing.sm }], children: [
1964
2015
  /* @__PURE__ */ jsxRuntime.jsx(
1965
2016
  reactNative.Animated.Text,
1966
2017
  {
@@ -1968,7 +2019,7 @@ var _StatusCard = ({ status, title, message, ctaLabel, onContinue, onPress, onSu
1968
2019
  headingStyle(t.fonts),
1969
2020
  { color, opacity: popT, transform: [{ scale: popScale }] }
1970
2021
  ],
1971
- children: (_b = STATUS_ICONS[status]) != null ? _b : "\xB7"
2022
+ children: (_a = STATUS_ICONS[status]) != null ? _a : "\xB7"
1972
2023
  }
1973
2024
  ),
1974
2025
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2007,7 +2058,7 @@ var StatusCard = {
2007
2058
  component: React14__default.default.memo(_StatusCard),
2008
2059
  propsSchema: schema4
2009
2060
  };
2010
- var styles15 = reactNative.StyleSheet.create({
2061
+ var styles12 = reactNative.StyleSheet.create({
2011
2062
  center: { width: "100%", alignItems: "center", justifyContent: "center" },
2012
2063
  titleRow: { flexDirection: "row", alignItems: "center", justifyContent: "center" }
2013
2064
  });
@@ -2032,6 +2083,7 @@ var _NumberStepperCard = ({
2032
2083
  onSubmit
2033
2084
  }) => {
2034
2085
  const t = useOnboardingTheme();
2086
+ const styles16 = React14.useMemo(() => makeStyles4(t), [t]);
2035
2087
  const safeMin = safeNum(min, 1);
2036
2088
  const safeMaxRaw = safeNum(max, 30);
2037
2089
  const safeMax = safeMaxRaw < safeMin ? safeMin : safeMaxRaw;
@@ -2064,7 +2116,7 @@ var _NumberStepperCard = ({
2064
2116
  title: label,
2065
2117
  align: "center",
2066
2118
  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: [
2119
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles16.wrap, children: [
2068
2120
  /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles16.stepper, { gap: t.spacing.lg }], children: [
2069
2121
  /* @__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
2122
  /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles16.valueBox, children: [
@@ -2085,11 +2137,14 @@ var NumberStepperCard = {
2085
2137
  propsSchema: schema5,
2086
2138
  defaultProps: { min: 1, max: 30, defaultValue: 1, step: 1, submitLabel: "Confirm" }
2087
2139
  };
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
- });
2140
+ function makeStyles4(t) {
2141
+ return reactNative.StyleSheet.create({
2142
+ wrap: { gap: t.spacing.md, alignItems: "center" },
2143
+ stepper: { flexDirection: "row", alignItems: "center", justifyContent: "center" },
2144
+ stepBtn: { width: 44, height: 44, borderWidth: 2, alignItems: "center", justifyContent: "center" },
2145
+ valueBox: { alignItems: "center", minWidth: 80 }
2146
+ });
2147
+ }
2093
2148
  var easeWire7 = reactNative.Easing.bezier(...WIRE_BEZIER);
2094
2149
  var RISE_PX = 10;
2095
2150
  var schema6 = zod.z.object({
@@ -2151,20 +2206,20 @@ var _WorkItem = ({
2151
2206
  return /* @__PURE__ */ jsxRuntime.jsxs(
2152
2207
  reactNative.Animated.View,
2153
2208
  {
2154
- style: [styles17.itemRow, { gap: t.spacing.sm + t.spacing.xs, opacity: rowT, transform: [{ translateY: rise }] }],
2209
+ style: [styles13.itemRow, { gap: t.spacing.sm + t.spacing.xs, opacity: rowT, transform: [{ translateY: rise }] }],
2155
2210
  children: [
2156
2211
  /* @__PURE__ */ jsxRuntime.jsx(
2157
2212
  reactNative.View,
2158
2213
  {
2159
2214
  style: [
2160
- styles17.itemBadge,
2215
+ styles13.itemBadge,
2161
2216
  { backgroundColor: t.colors.primarySoft, borderRadius: t.radius.full }
2162
2217
  ],
2163
2218
  children: /* @__PURE__ */ jsxRuntime.jsx(
2164
2219
  reactNative.Animated.Text,
2165
2220
  {
2166
2221
  style: [
2167
- styles17.itemCheck,
2222
+ styles13.itemCheck,
2168
2223
  { color: t.colors.primary, opacity: checkT, transform: [{ scale: checkScale }] }
2169
2224
  ],
2170
2225
  children: "\u2713"
@@ -2172,7 +2227,7 @@ var _WorkItem = ({
2172
2227
  )
2173
2228
  }
2174
2229
  ),
2175
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), styles17.itemText, { color: t.colors.text }], children: text })
2230
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), styles13.itemText, { color: t.colors.text }], children: text })
2176
2231
  ]
2177
2232
  }
2178
2233
  );
@@ -2250,18 +2305,18 @@ var _InterstitialCard = ({
2250
2305
  disabled: submitted || !canAdvance
2251
2306
  }
2252
2307
  ),
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(
2308
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles13.center, { gap: t.spacing.md }], children: [
2309
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: [styles13.fullWidth, riseStyle(artT)], children: showImage ? /* @__PURE__ */ jsxRuntime.jsx(
2255
2310
  reactNative.Image,
2256
2311
  {
2257
2312
  source: { uri: imageUrl },
2258
- style: styles17.image,
2313
+ style: styles13.image,
2259
2314
  resizeMode: "contain",
2260
2315
  onError: () => setImgFailed(true)
2261
2316
  }
2262
- ) : art ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles17.art, children: art }) : null }),
2317
+ ) : art ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles13.art, children: art }) : null }),
2263
2318
  /* @__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
2319
+ (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
2320
  ] })
2266
2321
  }
2267
2322
  );
@@ -2273,7 +2328,7 @@ var InterstitialCard = {
2273
2328
  propsSchema: schema6,
2274
2329
  defaultProps: { ctaLabel: "Continue", illustrationFallback: "momentum" }
2275
2330
  };
2276
- var styles17 = reactNative.StyleSheet.create({
2331
+ var styles13 = reactNative.StyleSheet.create({
2277
2332
  center: { width: "100%", alignItems: "center", justifyContent: "center" },
2278
2333
  fullWidth: { width: "100%", alignItems: "center" },
2279
2334
  art: { alignItems: "center", justifyContent: "center" },
@@ -2511,6 +2566,10 @@ var WireOnboarding = ({
2511
2566
  );
2512
2567
  const sessionId = (_a = session == null ? void 0 : session.id) != null ? _a : "";
2513
2568
  const startupUserIdRef = React14.useRef(boundUserId);
2569
+ const metadataKey = config.metadata ? JSON.stringify(config.metadata) : "";
2570
+ const metadataStable = React14.useMemo(() => metadataKey ? JSON.parse(metadataKey) : void 0, [metadataKey]);
2571
+ const componentNamesKey = (components != null ? components : onboardingComponents).map((c) => c.name).join(",");
2572
+ const componentsStable = React14.useMemo(() => components != null ? components : onboardingComponents, [componentNamesKey]);
2514
2573
  const llm = React14.useMemo(() => {
2515
2574
  const baseUrl = config.serverUrl.replace(/\/$/, "");
2516
2575
  return {
@@ -2523,9 +2582,9 @@ var WireOnboarding = ({
2523
2582
  // names of the components this device can actually render — so the backend only
2524
2583
  // emits cards the kit knows how to draw.
2525
2584
  metadata: {
2526
- ...config.metadata,
2585
+ ...metadataStable,
2527
2586
  sessionId,
2528
- supportedComponents: (components != null ? components : onboardingComponents).map((c) => c.name),
2587
+ supportedComponents: componentsStable.map((c) => c.name),
2529
2588
  // Device snapshot + host-injected user context ride the session-start metadata so the
2530
2589
  // backend can segment the funnel. Old servers ignore these unknown keys (backward compat).
2531
2590
  device,
@@ -2538,7 +2597,7 @@ var WireOnboarding = ({
2538
2597
  },
2539
2598
  timeoutMs: 6e4
2540
2599
  };
2541
- }, [config.serverUrl, config.appId, config.apiKey, config.metadata, sessionId, components, device, userContextStable]);
2600
+ }, [config.serverUrl, config.appId, config.apiKey, metadataStable, sessionId, componentsStable, device, userContextStable]);
2542
2601
  const reportTarget = React14.useMemo(
2543
2602
  () => ({ serverUrl: config.serverUrl, apiKey: config.apiKey }),
2544
2603
  [config.serverUrl, config.apiKey]
@@ -2603,7 +2662,7 @@ function DemoOnboarding({
2603
2662
  const [open, setOpen] = React14.useState(false);
2604
2663
  const [runId, setRunId] = React14.useState(0);
2605
2664
  const t = React14.useMemo(() => mergeTheme(theme != null ? theme : {}), [theme]);
2606
- const styles20 = React14.useMemo(() => makeStyles(t), [t]);
2665
+ const styles16 = React14.useMemo(() => makeStyles5(t), [t]);
2607
2666
  const openDemo = React14.useCallback(() => {
2608
2667
  setRunId((n) => n + 1);
2609
2668
  setOpen(true);
@@ -2617,7 +2676,7 @@ function DemoOnboarding({
2617
2676
  [onComplete]
2618
2677
  );
2619
2678
  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 }) }),
2679
+ 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
2680
  /* @__PURE__ */ jsxRuntime.jsx(
2622
2681
  reactNative.Modal,
2623
2682
  {
@@ -2625,7 +2684,7 @@ function DemoOnboarding({
2625
2684
  animationType: "slide",
2626
2685
  presentationStyle: "fullScreen",
2627
2686
  onRequestClose: close,
2628
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles20.modal, children: config ? /* @__PURE__ */ jsxRuntime.jsx(
2687
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles16.modal, children: config ? /* @__PURE__ */ jsxRuntime.jsx(
2629
2688
  WireOnboarding,
2630
2689
  {
2631
2690
  config,
@@ -2638,16 +2697,16 @@ function DemoOnboarding({
2638
2697
  onError: close
2639
2698
  },
2640
2699
  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" }) })
2700
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles16.notice, children: [
2701
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles16.noticeTitle, children: "Wire AI not configured" }),
2702
+ /* @__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." }),
2703
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: close, style: styles16.trigger, accessibilityRole: "button", children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles16.triggerText, children: "Close" }) })
2645
2704
  ] }) })
2646
2705
  }
2647
2706
  )
2648
2707
  ] });
2649
2708
  }
2650
- function makeStyles(t) {
2709
+ function makeStyles5(t) {
2651
2710
  return reactNative.StyleSheet.create({
2652
2711
  trigger: {
2653
2712
  backgroundColor: t.colors.primary,
@@ -2720,13 +2779,13 @@ var _DoneBlock = ({
2720
2779
  message = "Personalizing your experience\u2026"
2721
2780
  }) => {
2722
2781
  const t = useOnboardingTheme();
2723
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles18.center, { gap: t.spacing.sm, padding: t.spacing.lg }], children: [
2782
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles14.center, { gap: t.spacing.sm, padding: t.spacing.lg }], children: [
2724
2783
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [headingStyle(t.fonts), { color: t.colors.success, textAlign: "center" }], children: title }),
2725
2784
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }], children: message })
2726
2785
  ] });
2727
2786
  };
2728
2787
  var DoneBlock = React14__default.default.memo(_DoneBlock);
2729
- var styles18 = reactNative.StyleSheet.create({
2788
+ var styles14 = reactNative.StyleSheet.create({
2730
2789
  center: { flex: 1, alignItems: "center", justifyContent: "center" }
2731
2790
  });
2732
2791
 
@@ -2805,25 +2864,25 @@ var _CenteredModal = React14.forwardRef(
2805
2864
  /* @__PURE__ */ jsxRuntime.jsx(
2806
2865
  reactNative.Animated.View,
2807
2866
  {
2808
- style: [styles19.backdrop, { opacity: backdropOpacity }],
2867
+ style: [styles15.backdrop, { opacity: backdropOpacity }],
2809
2868
  pointerEvents: "none"
2810
2869
  }
2811
2870
  ),
2812
2871
  /* @__PURE__ */ jsxRuntime.jsx(
2813
2872
  reactNative.Pressable,
2814
2873
  {
2815
- style: styles19.fill,
2874
+ style: styles15.fill,
2816
2875
  onPress: onBackdrop,
2817
2876
  accessibilityRole: "button",
2818
2877
  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(
2878
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNativeSafeAreaContext.SafeAreaView, { style: styles15.center, pointerEvents: "box-none", children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => {
2879
+ }, style: styles15.cardWrap, children: /* @__PURE__ */ jsxRuntime.jsx(
2821
2880
  reactNative.Animated.View,
2822
2881
  {
2823
2882
  accessibilityViewIsModal: true,
2824
2883
  accessibilityRole: "alert",
2825
2884
  style: [
2826
- styles19.card,
2885
+ styles15.card,
2827
2886
  {
2828
2887
  backgroundColor: theme.colors.surface,
2829
2888
  borderRadius: theme.radius.lg,
@@ -2845,7 +2904,7 @@ var _CenteredModal = React14.forwardRef(
2845
2904
  );
2846
2905
  _CenteredModal.displayName = "CenteredModal";
2847
2906
  var CenteredModal = React14__default.default.memo(_CenteredModal);
2848
- var styles19 = reactNative.StyleSheet.create({
2907
+ var styles15 = reactNative.StyleSheet.create({
2849
2908
  fill: { flex: 1 },
2850
2909
  backdrop: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "#000" },
2851
2910
  center: { flex: 1, alignItems: "center", justifyContent: "center", padding: 24 },
@@ -2999,7 +3058,12 @@ var useWireFeatures = (config) => {
2999
3058
  }, [canFetch, stableConfig]);
3000
3059
  return flags;
3001
3060
  };
3002
- var WireFeaturesContext = React14.createContext(null);
3061
+ var CONTEXT_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.context");
3062
+ var globalObj = global;
3063
+ if (!globalObj[CONTEXT_SYMBOL]) {
3064
+ globalObj[CONTEXT_SYMBOL] = React14.createContext(null);
3065
+ }
3066
+ var WireFeaturesContext = globalObj[CONTEXT_SYMBOL];
3003
3067
  var WireFeaturesProvider = ({
3004
3068
  config,
3005
3069
  flags,
@@ -3412,6 +3476,8 @@ var createEventQueue = (options) => {
3412
3476
  })();
3413
3477
  const postBatch = async (events) => {
3414
3478
  if (!(target == null ? void 0 : target.serverUrl) || events.length === 0) return false;
3479
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
3480
+ const timer = setTimeout(() => controller == null ? void 0 : controller.abort(), 15e3);
3415
3481
  try {
3416
3482
  const url = `${target.serverUrl.replace(/\/$/, "")}/v1/events`;
3417
3483
  const headers = { "Content-Type": "application/json" };
@@ -3419,11 +3485,14 @@ var createEventQueue = (options) => {
3419
3485
  const res = await fetch(url, {
3420
3486
  method: "POST",
3421
3487
  headers,
3422
- body: JSON.stringify({ events })
3488
+ body: JSON.stringify({ events }),
3489
+ signal: controller == null ? void 0 : controller.signal
3423
3490
  });
3424
3491
  return !!(res && res.ok);
3425
3492
  } catch {
3426
3493
  return false;
3494
+ } finally {
3495
+ clearTimeout(timer);
3427
3496
  }
3428
3497
  };
3429
3498
  const clearRetry = () => {