@wireai/activation 0.13.0 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/AGENTS.md +3 -1
  2. package/CHANGELOG.md +116 -3
  3. package/README.md +82 -0
  4. package/dist/analytics/index.d.mts +2 -2
  5. package/dist/analytics/index.d.ts +2 -2
  6. package/dist/analytics/index.js +61 -2
  7. package/dist/analytics/index.js.map +1 -1
  8. package/dist/analytics/index.mjs +61 -2
  9. package/dist/analytics/index.mjs.map +1 -1
  10. package/dist/{currentSession-_GynvhzT.d.mts → currentSession-ClkLjcJ0.d.mts} +298 -13
  11. package/dist/{currentSession-D7zabMXK.d.ts → currentSession-DOVZEWJl.d.ts} +298 -13
  12. package/dist/index.d.mts +195 -4
  13. package/dist/index.d.ts +195 -4
  14. package/dist/index.js +909 -340
  15. package/dist/index.js.map +1 -1
  16. package/dist/index.mjs +693 -145
  17. package/dist/index.mjs.map +1 -1
  18. package/dist/questionnaire/index.js.map +1 -1
  19. package/dist/questionnaire/index.mjs.map +1 -1
  20. package/dist/reviews/index.js +12 -1
  21. package/dist/reviews/index.js.map +1 -1
  22. package/dist/reviews/index.mjs +12 -1
  23. package/dist/reviews/index.mjs.map +1 -1
  24. package/package.json +1 -1
  25. package/src/OnboardingFlow.tsx +131 -0
  26. package/src/WireOnboarding.tsx +63 -2
  27. package/src/analytics/analyticsEvent.ts +16 -1
  28. package/src/analytics/eventQueue.ts +2 -0
  29. package/src/analytics/reportClientEvent.ts +68 -12
  30. package/src/cards/PermissionCard.tsx +438 -0
  31. package/src/cards/index.ts +7 -0
  32. package/src/illustrations/defaultIllustrations.tsx +44 -3
  33. package/src/index.ts +38 -0
  34. package/src/permissions/index.ts +64 -0
  35. package/src/permissions/permissionCopy.ts +87 -0
  36. package/src/permissions/permissionEvents.ts +76 -0
  37. package/src/permissions/permissionMemory.ts +88 -0
  38. package/src/permissions/placement.ts +88 -0
  39. package/src/permissions/types.ts +131 -0
  40. package/src/session/persistedSession.ts +10 -3
  41. package/src/types.ts +66 -4
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var reactNative = require('react-native');
4
- var React18 = require('react');
4
+ var React19 = require('react');
5
5
  var wireaiRn = require('wireai-rn');
6
6
  var jsxRuntime = require('react/jsx-runtime');
7
7
  var reactNativeSafeAreaContext = require('react-native-safe-area-context');
@@ -9,7 +9,7 @@ var zod = require('zod');
9
9
 
10
10
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
11
 
12
- var React18__default = /*#__PURE__*/_interopDefault(React18);
12
+ var React19__default = /*#__PURE__*/_interopDefault(React19);
13
13
 
14
14
  var __defProp = Object.defineProperty;
15
15
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -464,33 +464,33 @@ var mergeTheme = (override) => {
464
464
  button: { ...defaultOnboardingTheme.button, ...override.button }
465
465
  };
466
466
  };
467
- var OnboardingThemeContext = React18.createContext(null);
467
+ var OnboardingThemeContext = React19.createContext(null);
468
468
  var OnboardingThemeProvider = ({
469
469
  theme,
470
470
  children
471
471
  }) => {
472
- const resolved = React18.useMemo(() => mergeTheme(theme), [theme]);
472
+ const resolved = React19.useMemo(() => mergeTheme(theme), [theme]);
473
473
  return /* @__PURE__ */ jsxRuntime.jsx(OnboardingThemeContext.Provider, { value: resolved, children });
474
474
  };
475
475
  var useOnboardingTheme = () => {
476
- const ctx = React18.useContext(OnboardingThemeContext);
476
+ const ctx = React19.useContext(OnboardingThemeContext);
477
477
  return ctx != null ? ctx : mergeTheme();
478
478
  };
479
479
  var EMPTY_REGISTRY = {};
480
- var IllustrationContext = React18.createContext(EMPTY_REGISTRY);
480
+ var IllustrationContext = React19.createContext(EMPTY_REGISTRY);
481
481
  var IllustrationProvider = ({
482
482
  registry,
483
483
  children
484
484
  }) => {
485
- const value = React18.useMemo(() => registry != null ? registry : EMPTY_REGISTRY, [registry]);
485
+ const value = React19.useMemo(() => registry != null ? registry : EMPTY_REGISTRY, [registry]);
486
486
  return /* @__PURE__ */ jsxRuntime.jsx(IllustrationContext.Provider, { value, children });
487
487
  };
488
488
  var useIllustration = (name2) => {
489
- const registry = React18.useContext(IllustrationContext);
489
+ const registry = React19.useContext(IllustrationContext);
490
490
  if (!name2) return void 0;
491
491
  return registry[name2];
492
492
  };
493
- var Glyph = ({ children }) => {
493
+ var Disc = ({ children }) => {
494
494
  const t = useOnboardingTheme();
495
495
  return /* @__PURE__ */ jsxRuntime.jsx(
496
496
  reactNative.View,
@@ -499,11 +499,23 @@ var Glyph = ({ children }) => {
499
499
  styles.disc,
500
500
  { backgroundColor: t.colors.primarySoft, borderRadius: t.radius.full }
501
501
  ],
502
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.glyphText, { color: t.colors.primary }], allowFontScaling: false, children })
502
+ children
503
503
  }
504
504
  );
505
505
  };
506
+ var Glyph = ({ children }) => {
507
+ const t = useOnboardingTheme();
508
+ return /* @__PURE__ */ jsxRuntime.jsx(Disc, { children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.glyphText, { color: t.colors.primary }], allowFontScaling: false, children }) });
509
+ };
506
510
  var MomentumGlyph = () => /* @__PURE__ */ jsxRuntime.jsx(Glyph, { children: "\u2197" });
511
+ var NotificationsGlyph = () => {
512
+ const t = useOnboardingTheme();
513
+ return /* @__PURE__ */ jsxRuntime.jsx(Disc, { children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles.bell, children: [
514
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles.bellBody, { backgroundColor: t.colors.primary }] }),
515
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles.bellRim, { backgroundColor: t.colors.primary }] }),
516
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles.bellClapper, { backgroundColor: t.colors.primary }] })
517
+ ] }) });
518
+ };
507
519
  var BeforeAfterGlyph = () => {
508
520
  const t = useOnboardingTheme();
509
521
  return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles.row, children: [
@@ -530,7 +542,8 @@ var BeforeAfterGlyph = () => {
530
542
  };
531
543
  var defaultIllustrations = {
532
544
  momentum: /* @__PURE__ */ jsxRuntime.jsx(MomentumGlyph, {}),
533
- "before-after": /* @__PURE__ */ jsxRuntime.jsx(BeforeAfterGlyph, {})
545
+ "before-after": /* @__PURE__ */ jsxRuntime.jsx(BeforeAfterGlyph, {}),
546
+ notifications: /* @__PURE__ */ jsxRuntime.jsx(NotificationsGlyph, {})
534
547
  };
535
548
  var styles = reactNative.StyleSheet.create({
536
549
  disc: {
@@ -558,19 +571,24 @@ var styles = reactNative.StyleSheet.create({
558
571
  arrow: {
559
572
  fontSize: 28,
560
573
  fontWeight: "700"
561
- }
574
+ },
575
+ // The bell: a domed body over a wider rim, with the clapper hanging below it.
576
+ bell: { alignItems: "center", justifyContent: "center" },
577
+ bellBody: { width: 34, height: 30, borderTopLeftRadius: 17, borderTopRightRadius: 17 },
578
+ bellRim: { width: 44, height: 5, borderRadius: 3, marginTop: 2 },
579
+ bellClapper: { width: 9, height: 9, borderRadius: 5, marginTop: 3 }
562
580
  });
563
581
  var EMPTY_REGISTRY2 = {};
564
- var IconContext = React18.createContext(EMPTY_REGISTRY2);
582
+ var IconContext = React19.createContext(EMPTY_REGISTRY2);
565
583
  var IconRegistryProvider = ({
566
584
  registry,
567
585
  children
568
586
  }) => {
569
- const value = React18.useMemo(() => registry != null ? registry : EMPTY_REGISTRY2, [registry]);
587
+ const value = React19.useMemo(() => registry != null ? registry : EMPTY_REGISTRY2, [registry]);
570
588
  return /* @__PURE__ */ jsxRuntime.jsx(IconContext.Provider, { value, children });
571
589
  };
572
590
  var useHostIcon = (name2) => {
573
- const registry = React18.useContext(IconContext);
591
+ const registry = React19.useContext(IconContext);
574
592
  if (!name2) return void 0;
575
593
  if (!Object.prototype.hasOwnProperty.call(registry, name2)) return void 0;
576
594
  return registry[name2];
@@ -729,8 +747,8 @@ var stateChangeDuration = (reduced, durationMs) => reduced ? 0 : durationMs;
729
747
  var loopEnabled = (reduced) => !reduced;
730
748
  var lastKnown = false;
731
749
  var useReducedMotion = () => {
732
- const [reduced, setReduced] = React18.useState(lastKnown);
733
- React18.useEffect(() => {
750
+ const [reduced, setReduced] = React19.useState(lastKnown);
751
+ React19.useEffect(() => {
734
752
  let mounted = true;
735
753
  reactNative.AccessibilityInfo.isReduceMotionEnabled().then((value) => {
736
754
  lastKnown = value;
@@ -752,8 +770,8 @@ var ROTATION_MS = 1800;
752
770
  var _AnimatedSparkle = ({ size = 30, color, variant = "spin" }) => {
753
771
  const t = useOnboardingTheme();
754
772
  const reduced = useReducedMotion();
755
- const progress = React18.useRef(new reactNative.Animated.Value(0)).current;
756
- React18.useEffect(() => {
773
+ const progress = React19.useRef(new reactNative.Animated.Value(0)).current;
774
+ React19.useEffect(() => {
757
775
  if (!loopEnabled(reduced)) {
758
776
  progress.setValue(0);
759
777
  return;
@@ -808,7 +826,7 @@ var _AnimatedSparkle = ({ size = 30, color, variant = "spin" }) => {
808
826
  }
809
827
  ) });
810
828
  };
811
- var AnimatedSparkle = React18__default.default.memo(_AnimatedSparkle);
829
+ var AnimatedSparkle = React19__default.default.memo(_AnimatedSparkle);
812
830
  var styles2 = reactNative.StyleSheet.create({
813
831
  box: { alignItems: "center", justifyContent: "center" },
814
832
  glyph: {
@@ -820,8 +838,8 @@ var styles2 = reactNative.StyleSheet.create({
820
838
  var STAGE_SIZE = 96;
821
839
  var ORBIT_DOT = 9;
822
840
  var OrbitDot = ({ color, reduced }) => {
823
- const spin = React18.useRef(new reactNative.Animated.Value(0)).current;
824
- React18.useEffect(() => {
841
+ const spin = React19.useRef(new reactNative.Animated.Value(0)).current;
842
+ React19.useEffect(() => {
825
843
  if (!loopEnabled(reduced)) return;
826
844
  const loop = reactNative.Animated.loop(
827
845
  reactNative.Animated.timing(spin, {
@@ -844,8 +862,8 @@ var SkeletonLine = ({
844
862
  color,
845
863
  reduced
846
864
  }) => {
847
- const pulse = React18.useRef(new reactNative.Animated.Value(1)).current;
848
- React18.useEffect(() => {
865
+ const pulse = React19.useRef(new reactNative.Animated.Value(1)).current;
866
+ React19.useEffect(() => {
849
867
  if (!loopEnabled(reduced)) return;
850
868
  const loop = reactNative.Animated.loop(
851
869
  reactNative.Animated.sequence([
@@ -880,8 +898,8 @@ var _LoadingBlock = ({
880
898
  }) => {
881
899
  const t = useOnboardingTheme();
882
900
  const reduced = useReducedMotion();
883
- const [showSkeleton, setShowSkeleton] = React18.useState(false);
884
- React18.useEffect(() => {
901
+ const [showSkeleton, setShowSkeleton] = React19.useState(false);
902
+ React19.useEffect(() => {
885
903
  const timer = setTimeout(() => setShowSkeleton(true), SKELETON_DELAY_MS);
886
904
  return () => clearTimeout(timer);
887
905
  }, []);
@@ -911,7 +929,7 @@ var _LoadingBlock = ({
911
929
  )
912
930
  ] });
913
931
  };
914
- var LoadingBlock = React18__default.default.memo(_LoadingBlock);
932
+ var LoadingBlock = React19__default.default.memo(_LoadingBlock);
915
933
  var styles3 = reactNative.StyleSheet.create({
916
934
  center: { flex: 1, alignItems: "center", justifyContent: "center" },
917
935
  // Absolutely-positioned label column, pinned just under the centered stage: its top
@@ -956,7 +974,7 @@ var _LoadingScreen = ({ title, hint }) => {
956
974
  }
957
975
  );
958
976
  };
959
- var LoadingScreen = React18__default.default.memo(_LoadingScreen);
977
+ var LoadingScreen = React19__default.default.memo(_LoadingScreen);
960
978
  var styles4 = reactNative.StyleSheet.create({
961
979
  flex: { flex: 1 }
962
980
  });
@@ -975,8 +993,8 @@ var targetFor = (step, complete, approxScreens) => {
975
993
  var _StepProgress = ({ step, complete, approxScreens }) => {
976
994
  const t = useOnboardingTheme();
977
995
  const reduced = useReducedMotion();
978
- const fill = React18.useRef(new reactNative.Animated.Value(targetFor(step, complete, approxScreens))).current;
979
- React18.useEffect(() => {
996
+ const fill = React19.useRef(new reactNative.Animated.Value(targetFor(step, complete, approxScreens))).current;
997
+ React19.useEffect(() => {
980
998
  reactNative.Animated.timing(fill, {
981
999
  toValue: targetFor(step, complete, approxScreens),
982
1000
  duration: scaledMs(stateChangeDuration(reduced, PROGRESS_MS)),
@@ -996,7 +1014,7 @@ var _StepProgress = ({ step, complete, approxScreens }) => {
996
1014
  }
997
1015
  ) });
998
1016
  };
999
- var StepProgress = React18__default.default.memo(_StepProgress);
1017
+ var StepProgress = React19__default.default.memo(_StepProgress);
1000
1018
  var styles5 = reactNative.StyleSheet.create({
1001
1019
  track: { height: 6, width: "100%", overflow: "hidden" },
1002
1020
  fill: { height: 6 }
@@ -1012,31 +1030,31 @@ var _OnboardingScaffold = ({
1012
1030
  children
1013
1031
  }) => {
1014
1032
  const t = useOnboardingTheme();
1015
- const styles17 = React18.useMemo(() => makeStyles(t), [t]);
1033
+ const styles18 = React19.useMemo(() => makeStyles(t), [t]);
1016
1034
  const hit = { top: 12, bottom: 12, left: 12, right: 12 };
1017
1035
  return /* @__PURE__ */ jsxRuntime.jsxs(
1018
1036
  reactNativeSafeAreaContext.SafeAreaView,
1019
1037
  {
1020
1038
  edges: ["top", "bottom"],
1021
- style: [styles17.flex, { backgroundColor: t.colors.background }],
1039
+ style: [styles18.flex, { backgroundColor: t.colors.background }],
1022
1040
  children: [
1023
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles17.header, { paddingHorizontal: t.spacing.md, paddingTop: t.spacing.md }], children: [
1024
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles17.progressWrap, children: /* @__PURE__ */ jsxRuntime.jsx(StepProgress, { step, complete, approxScreens }) }),
1025
- onSkip ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.TouchableOpacity, { onPress: onSkip, hitSlop: hit, style: styles17.skip, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted }], children: skipLabel }) }) : null
1041
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles18.header, { paddingHorizontal: t.spacing.md, paddingTop: t.spacing.md }], children: [
1042
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles18.progressWrap, children: /* @__PURE__ */ jsxRuntime.jsx(StepProgress, { step, complete, approxScreens }) }),
1043
+ onSkip ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.TouchableOpacity, { onPress: onSkip, hitSlop: hit, style: styles18.skip, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted }], children: skipLabel }) }) : null
1026
1044
  ] }),
1027
1045
  /* @__PURE__ */ jsxRuntime.jsx(
1028
1046
  reactNative.View,
1029
1047
  {
1030
- style: [styles17.body, { paddingHorizontal: t.spacing.md, paddingTop: t.spacing.md }],
1048
+ style: [styles18.body, { paddingHorizontal: t.spacing.md, paddingTop: t.spacing.md }],
1031
1049
  children
1032
1050
  }
1033
1051
  ),
1034
- onBack ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles17.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
1052
+ onBack ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles18.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
1035
1053
  ]
1036
1054
  }
1037
1055
  );
1038
1056
  };
1039
- var OnboardingScaffold = React18__default.default.memo(_OnboardingScaffold);
1057
+ var OnboardingScaffold = React19__default.default.memo(_OnboardingScaffold);
1040
1058
  function makeStyles(t) {
1041
1059
  return reactNative.StyleSheet.create({
1042
1060
  flex: { flex: 1 },
@@ -1057,8 +1075,8 @@ var _Button = ({
1057
1075
  const t = useOnboardingTheme();
1058
1076
  const reduced = useReducedMotion();
1059
1077
  const isPrimary = variant === "primary";
1060
- const pressT = React18.useRef(new reactNative.Animated.Value(0)).current;
1061
- const pressTo = React18.useCallback(
1078
+ const pressT = React19.useRef(new reactNative.Animated.Value(0)).current;
1079
+ const pressTo = React19.useCallback(
1062
1080
  (to) => {
1063
1081
  if (reduced) {
1064
1082
  reactNative.Animated.timing(pressT, {
@@ -1077,16 +1095,16 @@ var _Button = ({
1077
1095
  },
1078
1096
  [pressT, reduced]
1079
1097
  );
1080
- const lastPress = React18.useRef(0);
1081
- const handlePress = React18.useCallback(() => {
1098
+ const lastPress = React19.useRef(0);
1099
+ const handlePress = React19.useCallback(() => {
1082
1100
  if (!onPress) return;
1083
1101
  const now = Date.now();
1084
1102
  if (now - lastPress.current < 500) return;
1085
1103
  lastPress.current = now;
1086
1104
  onPress();
1087
1105
  }, [onPress]);
1088
- const handlePressIn = React18.useCallback(() => pressTo(1), [pressTo]);
1089
- const handlePressOut = React18.useCallback(() => pressTo(0), [pressTo]);
1106
+ const handlePressIn = React19.useCallback(() => pressTo(1), [pressTo]);
1107
+ const handlePressOut = React19.useCallback(() => pressTo(0), [pressTo]);
1090
1108
  const scale = pressT.interpolate({
1091
1109
  inputRange: [0, 1],
1092
1110
  outputRange: [1, reduced ? 1 : BUTTON_PRESS_SCALE]
@@ -1137,7 +1155,7 @@ var _Button = ({
1137
1155
  }
1138
1156
  );
1139
1157
  };
1140
- var Button = React18__default.default.memo(_Button);
1158
+ var Button = React19__default.default.memo(_Button);
1141
1159
  var styles6 = reactNative.StyleSheet.create({
1142
1160
  base: { alignItems: "center", justifyContent: "center" },
1143
1161
  fill: { width: "100%" }
@@ -1155,7 +1173,7 @@ var _ErrorBlock = ({
1155
1173
  onRetry ? /* @__PURE__ */ jsxRuntime.jsx(Button, { title: retryLabel, onPress: onRetry, variant: "outline" }) : null
1156
1174
  ] });
1157
1175
  };
1158
- var ErrorBlock = React18__default.default.memo(_ErrorBlock);
1176
+ var ErrorBlock = React19__default.default.memo(_ErrorBlock);
1159
1177
  var styles7 = reactNative.StyleSheet.create({
1160
1178
  center: { flex: 1, alignItems: "center", justifyContent: "center" }
1161
1179
  });
@@ -1168,10 +1186,10 @@ var _CardLayout = ({
1168
1186
  titleNode
1169
1187
  }) => {
1170
1188
  const t = useOnboardingTheme();
1171
- const styles17 = React18.useMemo(() => makeStyles2(t), [t]);
1189
+ const styles18 = React19.useMemo(() => makeStyles2(t), [t]);
1172
1190
  const hasHeader = !!title || !!subtitle || !!titleNode;
1173
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.KeyboardAvoidingView, { style: styles17.fill, behavior: "padding", children: [
1174
- hasHeader ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles17.header, children: [
1191
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.KeyboardAvoidingView, { style: styles18.fill, behavior: "padding", children: [
1192
+ hasHeader ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles18.header, children: [
1175
1193
  titleNode != null ? titleNode : title ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [labelStyle(t.fonts), { color: t.colors.text }], children: title }) : null,
1176
1194
  subtitle ? /* @__PURE__ */ jsxRuntime.jsx(
1177
1195
  reactNative.Text,
@@ -1187,10 +1205,10 @@ var _CardLayout = ({
1187
1205
  /* @__PURE__ */ jsxRuntime.jsx(
1188
1206
  reactNative.ScrollView,
1189
1207
  {
1190
- style: styles17.fill,
1208
+ style: styles18.fill,
1191
1209
  contentContainerStyle: [
1192
- styles17.scrollContent,
1193
- align === "center" && styles17.center
1210
+ styles18.scrollContent,
1211
+ align === "center" && styles18.center
1194
1212
  ],
1195
1213
  keyboardShouldPersistTaps: "handled",
1196
1214
  keyboardDismissMode: "on-drag",
@@ -1198,10 +1216,10 @@ var _CardLayout = ({
1198
1216
  children
1199
1217
  }
1200
1218
  ),
1201
- footer ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles17.footer, children: footer }) : null
1219
+ footer ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles18.footer, children: footer }) : null
1202
1220
  ] });
1203
1221
  };
1204
- var CardLayout = React18__default.default.memo(_CardLayout);
1222
+ var CardLayout = React19__default.default.memo(_CardLayout);
1205
1223
  function makeStyles2(t) {
1206
1224
  return reactNative.StyleSheet.create({
1207
1225
  fill: { flex: 1 },
@@ -1224,12 +1242,12 @@ var _CompletionView = ({
1224
1242
  }) => {
1225
1243
  const t = useOnboardingTheme();
1226
1244
  const reduced = useReducedMotion();
1227
- const [submitted, setSubmitted] = React18.useState(false);
1228
- const pop = React18.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
1229
- const burst = React18.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
1230
- const titleT = React18.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
1231
- const messageT = React18.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
1232
- React18.useEffect(() => {
1245
+ const [submitted, setSubmitted] = React19.useState(false);
1246
+ const pop = React19.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
1247
+ const burst = React19.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
1248
+ const titleT = React19.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
1249
+ const messageT = React19.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
1250
+ React19.useEffect(() => {
1233
1251
  if (reduced) {
1234
1252
  pop.setValue(1);
1235
1253
  burst.setValue(1);
@@ -1264,7 +1282,7 @@ var _CompletionView = ({
1264
1282
  anims.forEach((a) => a.start());
1265
1283
  return () => anims.forEach((a) => a.stop());
1266
1284
  }, [reduced, pop, burst, titleT, messageT]);
1267
- const handlePress = React18.useCallback(() => {
1285
+ const handlePress = React19.useCallback(() => {
1268
1286
  if (submitted) return;
1269
1287
  setSubmitted(true);
1270
1288
  onContinue();
@@ -1326,7 +1344,7 @@ var _CompletionView = ({
1326
1344
  }
1327
1345
  );
1328
1346
  };
1329
- var CompletionView = React18__default.default.memo(_CompletionView);
1347
+ var CompletionView = React19__default.default.memo(_CompletionView);
1330
1348
  var styles8 = reactNative.StyleSheet.create({
1331
1349
  center: { width: "100%", alignItems: "center", justifyContent: "center" },
1332
1350
  badgeStage: {
@@ -1348,13 +1366,13 @@ var _CardHandoff = ({
1348
1366
  children
1349
1367
  }) => {
1350
1368
  const reduced = useReducedMotion();
1351
- const [leaving, setLeaving] = React18.useState(null);
1352
- const enterT = React18.useRef(new reactNative.Animated.Value(0)).current;
1353
- const exitT = React18.useRef(new reactNative.Animated.Value(0)).current;
1354
- const prev = React18.useRef({ key: transitionKey, node: children, variant });
1355
- const running = React18.useRef({});
1356
- const mounted = React18.useRef(false);
1357
- React18.useEffect(() => {
1369
+ const [leaving, setLeaving] = React19.useState(null);
1370
+ const enterT = React19.useRef(new reactNative.Animated.Value(0)).current;
1371
+ const exitT = React19.useRef(new reactNative.Animated.Value(0)).current;
1372
+ const prev = React19.useRef({ key: transitionKey, node: children, variant });
1373
+ const running = React19.useRef({});
1374
+ const mounted = React19.useRef(false);
1375
+ React19.useEffect(() => {
1358
1376
  var _a2, _b;
1359
1377
  const last = prev.current;
1360
1378
  prev.current = { key: transitionKey, node: children, variant };
@@ -1387,7 +1405,7 @@ var _CardHandoff = ({
1387
1405
  );
1388
1406
  running.current.enter = enter;
1389
1407
  });
1390
- React18.useEffect(
1408
+ React19.useEffect(
1391
1409
  () => () => {
1392
1410
  var _a2, _b;
1393
1411
  (_a2 = running.current.enter) == null ? void 0 : _a2.stop();
@@ -1456,7 +1474,7 @@ var transformFor = (variant, t, reduced, layer, travel) => {
1456
1474
  }
1457
1475
  ];
1458
1476
  };
1459
- var CardHandoff = React18__default.default.memo(_CardHandoff);
1477
+ var CardHandoff = React19__default.default.memo(_CardHandoff);
1460
1478
  var styles9 = reactNative.StyleSheet.create({
1461
1479
  fill: { flex: 1 }
1462
1480
  });
@@ -1509,13 +1527,24 @@ var readProgress = (response) => {
1509
1527
 
1510
1528
  // src/analytics/reportClientEvent.ts
1511
1529
  var makeSessionId = () => `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
1530
+ var toWireEvents = (events) => events.map((event) => {
1531
+ if (typeof event.ts !== "number") return event;
1532
+ const { ts, ...rest } = event;
1533
+ if (!Number.isFinite(ts)) return rest;
1534
+ try {
1535
+ return { ...rest, ts: new Date(ts).toISOString() };
1536
+ } catch {
1537
+ return rest;
1538
+ }
1539
+ });
1512
1540
  var buildEventsRequest = (target, events) => {
1513
1541
  if (!(target == null ? void 0 : target.serverUrl) || events.length === 0) return null;
1514
1542
  try {
1515
1543
  const url = `${target.serverUrl.replace(/\/$/, "")}/v1/events`;
1516
1544
  const headers = { "Content-Type": "application/json" };
1517
1545
  if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
1518
- return { url, init: { method: "POST", headers, body: JSON.stringify({ events }) } };
1546
+ const body = JSON.stringify({ events: toWireEvents(events) });
1547
+ return { url, init: { method: "POST", headers, body } };
1519
1548
  } catch {
1520
1549
  return null;
1521
1550
  }
@@ -1527,7 +1556,13 @@ var readEventsAck = async (res) => {
1527
1556
  const body = await Promise.resolve(json.call(res));
1528
1557
  const skipped = body == null ? void 0 : body.skipped;
1529
1558
  if (typeof skipped !== "number" || !Number.isFinite(skipped)) return void 0;
1530
- const reasons = Array.isArray(body == null ? void 0 : body.errors) ? body.errors.map((e) => e == null ? void 0 : e.reason).filter((r) => typeof r === "string") : [];
1559
+ const reasons = Array.isArray(body == null ? void 0 : body.errors) ? body.errors.map((e) => {
1560
+ const entry = e;
1561
+ const reason = entry == null ? void 0 : entry.reason;
1562
+ if (typeof reason !== "string") return void 0;
1563
+ const field = entry == null ? void 0 : entry.field;
1564
+ return typeof field === "string" && field.length > 0 ? `${reason} (field: ${field})` : reason;
1565
+ }).filter((r) => typeof r === "string") : [];
1531
1566
  return { written: typeof (body == null ? void 0 : body.written) === "number" ? body.written : void 0, skipped, reasons };
1532
1567
  } catch {
1533
1568
  return void 0;
@@ -1597,6 +1632,382 @@ var sendPreview = (target, { sessionId, userMessage }) => {
1597
1632
  } catch {
1598
1633
  }
1599
1634
  };
1635
+
1636
+ // src/permissions/permissionEvents.ts
1637
+ var WIRE_PERMISSION_EVENTS = {
1638
+ /** The priming screen became visible. The denominator for every rate below. */
1639
+ screenShown: "wire_permission_screen_shown",
1640
+ /** The user tapped the primary, so the OS dialog is about to open. The rationale worked. */
1641
+ primerAccepted: "wire_permission_primer_accepted",
1642
+ /** The OS granted it. */
1643
+ granted: "wire_permission_granted",
1644
+ /** The OS refused it (a permanently blocked answer reports here too, with `status: "blocked"`). */
1645
+ denied: "wire_permission_denied",
1646
+ /** The user took the secondary. The one native prompt was NOT spent. */
1647
+ skipped: "wire_permission_skipped",
1648
+ /** A blocked user was redirected to the OS settings page. */
1649
+ settingsOpened: "wire_permission_settings_opened"
1650
+ };
1651
+ var permissionEventName = (stage) => {
1652
+ switch (stage) {
1653
+ case "shown":
1654
+ return WIRE_PERMISSION_EVENTS.screenShown;
1655
+ case "accepted":
1656
+ return WIRE_PERMISSION_EVENTS.primerAccepted;
1657
+ case "granted":
1658
+ return WIRE_PERMISSION_EVENTS.granted;
1659
+ case "denied":
1660
+ return WIRE_PERMISSION_EVENTS.denied;
1661
+ case "skipped":
1662
+ return WIRE_PERMISSION_EVENTS.skipped;
1663
+ case "settings":
1664
+ return WIRE_PERMISSION_EVENTS.settingsOpened;
1665
+ default: {
1666
+ const _exhaustive = stage;
1667
+ return _exhaustive;
1668
+ }
1669
+ }
1670
+ };
1671
+ var permissionEventProps = (permission, status) => status ? { permission, status } : { permission };
1672
+ var normalizePermissionStatus = (value) => value === "granted" || value === "denied" || value === "blocked" ? value : "denied";
1673
+
1674
+ // src/haptics/haptics.ts
1675
+ var play = async (tone) => {
1676
+ try {
1677
+ const mod = await import('expo-haptics');
1678
+ if (!mod) return false;
1679
+ if (tone === "selection") ;
1680
+ if (tone === "light") ;
1681
+ await mod.notificationAsync(mod.NotificationFeedbackType.Success);
1682
+ return true;
1683
+ } catch {
1684
+ return false;
1685
+ }
1686
+ };
1687
+ var playHaptic = (tone) => {
1688
+ void play(tone);
1689
+ };
1690
+
1691
+ // src/utils/warnInDev.ts
1692
+ var warnInDev = (message) => {
1693
+ if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
1694
+ console.warn(message);
1695
+ return true;
1696
+ }
1697
+ return false;
1698
+ };
1699
+ var easeWire4 = reactNative.Easing.bezier(...WIRE_BEZIER);
1700
+ var RISE_PX = 10;
1701
+ var PERMISSION_CARD_NAME = "PermissionCard";
1702
+ var REQUEST_WATCHDOG_MS = 9e4;
1703
+ var schema = zod.z.object({
1704
+ permission: zod.z.string().describe("Which OS permission this screen primes, e.g. 'notifications'"),
1705
+ title: zod.z.string().describe("Headline for the ask"),
1706
+ message: zod.z.string().describe("Why the app needs it, in the user's terms"),
1707
+ primaryLabel: zod.z.string().describe("Primary button, the only control that can open the OS dialog"),
1708
+ secondaryLabel: zod.z.string().describe("Secondary button, advances without spending the OS prompt"),
1709
+ blockedTitle: zod.z.string().optional().describe("Headline once the permission is permanently refused"),
1710
+ blockedMessage: zod.z.string().optional().describe("Rationale for the settings route"),
1711
+ settingsLabel: zod.z.string().optional().describe("Primary button label on the blocked route"),
1712
+ continueLabel: zod.z.string().optional().describe("Primary button label when there is nothing to ask"),
1713
+ illustration: zod.z.string().optional().describe("Name of an app-provided illustration (defaults to the permission name)")
1714
+ });
1715
+ var _PermissionCard = ({
1716
+ permission,
1717
+ title,
1718
+ message,
1719
+ primaryLabel,
1720
+ secondaryLabel,
1721
+ blockedTitle,
1722
+ blockedMessage,
1723
+ settingsLabel,
1724
+ continueLabel,
1725
+ illustration,
1726
+ request,
1727
+ getStatus,
1728
+ openSettings,
1729
+ onStage,
1730
+ onSettled
1731
+ }) => {
1732
+ const t = useOnboardingTheme();
1733
+ const reduced = useReducedMotion();
1734
+ const art = useIllustration(illustration != null ? illustration : permission);
1735
+ const [status, setStatus] = React19.useState(void 0);
1736
+ const [busy, setBusy] = React19.useState(false);
1737
+ const settledRef = React19.useRef(false);
1738
+ const acceptedRef = React19.useRef(false);
1739
+ const watchdogRef = React19.useRef(null);
1740
+ const clearWatchdog = React19.useCallback(() => {
1741
+ if (watchdogRef.current) {
1742
+ clearTimeout(watchdogRef.current);
1743
+ watchdogRef.current = null;
1744
+ }
1745
+ }, []);
1746
+ React19.useEffect(() => clearWatchdog, [clearWatchdog]);
1747
+ const onStageRef = React19.useRef(onStage);
1748
+ onStageRef.current = onStage;
1749
+ const onSettledRef = React19.useRef(onSettled);
1750
+ onSettledRef.current = onSettled;
1751
+ const getStatusRef = React19.useRef(getStatus);
1752
+ getStatusRef.current = getStatus;
1753
+ const shownRef = React19.useRef(false);
1754
+ React19.useEffect(() => {
1755
+ var _a2;
1756
+ if (shownRef.current) return;
1757
+ shownRef.current = true;
1758
+ (_a2 = onStageRef.current) == null ? void 0 : _a2.call(onStageRef, "shown");
1759
+ }, []);
1760
+ React19.useEffect(() => {
1761
+ const probe = getStatusRef.current;
1762
+ if (!probe) return;
1763
+ let cancelled = false;
1764
+ try {
1765
+ void Promise.resolve(probe()).then((value) => {
1766
+ if (!cancelled) setStatus(normalizePermissionStatus(value));
1767
+ }).catch(() => {
1768
+ });
1769
+ } catch {
1770
+ }
1771
+ return () => {
1772
+ cancelled = true;
1773
+ };
1774
+ }, []);
1775
+ const requestRef = React19.useRef(request);
1776
+ requestRef.current = request;
1777
+ React19.useEffect(() => {
1778
+ if (requestRef.current) return;
1779
+ warnInDev(
1780
+ `[wireai] <WireOnboarding> got a permission screen for "${permission}" with no \`request\` function, so it cannot open the OS dialog and renders as a plain continue. Pass request: () => Promise<'granted' | 'denied' | 'blocked'> (5 lines around expo-notifications, see the README).`
1781
+ );
1782
+ }, [permission]);
1783
+ const settle = React19.useCallback((outcome) => {
1784
+ var _a2;
1785
+ if (settledRef.current) return;
1786
+ settledRef.current = true;
1787
+ clearWatchdog();
1788
+ (_a2 = onSettledRef.current) == null ? void 0 : _a2.call(onSettledRef, outcome);
1789
+ }, [clearWatchdog]);
1790
+ const primaryAction = status === "blocked" ? "settings" : status === "granted" || !request ? "continue" : "ask";
1791
+ const handlePrimary = React19.useCallback(() => {
1792
+ var _a2, _b, _c, _d;
1793
+ if (settledRef.current || busy) return;
1794
+ if (primaryAction === "settings") {
1795
+ (_a2 = onStageRef.current) == null ? void 0 : _a2.call(onStageRef, "settings", "blocked");
1796
+ try {
1797
+ void Promise.resolve(openSettings == null ? void 0 : openSettings()).catch(() => {
1798
+ });
1799
+ } catch {
1800
+ }
1801
+ settle("blocked");
1802
+ return;
1803
+ }
1804
+ if (primaryAction === "continue") {
1805
+ if (status === "granted") {
1806
+ (_b = onStageRef.current) == null ? void 0 : _b.call(onStageRef, "granted", "granted");
1807
+ settle("granted");
1808
+ } else {
1809
+ (_c = onStageRef.current) == null ? void 0 : _c.call(onStageRef, "skipped");
1810
+ settle("skipped");
1811
+ }
1812
+ return;
1813
+ }
1814
+ if (!acceptedRef.current) {
1815
+ acceptedRef.current = true;
1816
+ (_d = onStageRef.current) == null ? void 0 : _d.call(onStageRef, "accepted");
1817
+ }
1818
+ setBusy(true);
1819
+ const finish = (value) => {
1820
+ var _a3, _b2;
1821
+ if (settledRef.current) return;
1822
+ clearWatchdog();
1823
+ const resolved = normalizePermissionStatus(value);
1824
+ setBusy(false);
1825
+ setStatus(resolved);
1826
+ if (resolved === "granted") {
1827
+ playHaptic("success");
1828
+ (_a3 = onStageRef.current) == null ? void 0 : _a3.call(onStageRef, "granted", "granted");
1829
+ } else {
1830
+ (_b2 = onStageRef.current) == null ? void 0 : _b2.call(onStageRef, "denied", resolved);
1831
+ }
1832
+ settle(resolved);
1833
+ };
1834
+ clearWatchdog();
1835
+ watchdogRef.current = setTimeout(() => {
1836
+ watchdogRef.current = null;
1837
+ if (settledRef.current) return;
1838
+ setBusy(false);
1839
+ warnInDev(
1840
+ `[wireai] the \`request\` for the "${permission}" permission screen has not settled after ${Math.round(REQUEST_WATCHDOG_MS / 1e3)}s, so the kit handed the controls back rather than leaving the user on a dead-end screen. It recorded NO outcome, because a pending request is not a denial. Make sure your request resolves to 'granted' | 'denied' | 'blocked' on every branch, including the one where the user dismisses the OS dialog.`
1841
+ );
1842
+ }, REQUEST_WATCHDOG_MS);
1843
+ try {
1844
+ void Promise.resolve(request == null ? void 0 : request()).then(finish, () => finish("denied"));
1845
+ } catch {
1846
+ finish("denied");
1847
+ }
1848
+ }, [busy, primaryAction, status, request, openSettings, settle, clearWatchdog, permission]);
1849
+ const handleSecondary = React19.useCallback(() => {
1850
+ var _a2;
1851
+ if (settledRef.current || busy) return;
1852
+ (_a2 = onStageRef.current) == null ? void 0 : _a2.call(onStageRef, "skipped");
1853
+ settle("skipped");
1854
+ }, [busy, settle]);
1855
+ const popT = React19.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
1856
+ const titleT = React19.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
1857
+ const bodyT = React19.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
1858
+ React19.useEffect(() => {
1859
+ if (reduced) {
1860
+ popT.setValue(1);
1861
+ titleT.setValue(1);
1862
+ bodyT.setValue(1);
1863
+ return;
1864
+ }
1865
+ const rise = (value, delay) => reactNative.Animated.timing(value, {
1866
+ toValue: 1,
1867
+ duration: scaledMs(INTERSTITIAL_HEAD_MS),
1868
+ delay,
1869
+ easing: easeWire4,
1870
+ useNativeDriver: true
1871
+ });
1872
+ const anims = [
1873
+ reactNative.Animated.spring(popT, {
1874
+ toValue: 1,
1875
+ friction: STATUS_POP_SPRING.friction,
1876
+ tension: STATUS_POP_SPRING.tension,
1877
+ useNativeDriver: true
1878
+ }),
1879
+ rise(titleT, scaledMs(INTERSTITIAL_HEAD_STAGGER_MS)),
1880
+ rise(bodyT, scaledMs(INTERSTITIAL_HEAD_STAGGER_MS * 2))
1881
+ ];
1882
+ anims.forEach((a) => a.start());
1883
+ return () => anims.forEach((a) => a.stop());
1884
+ }, [reduced, popT, titleT, bodyT]);
1885
+ const popScale = popT.interpolate({
1886
+ inputRange: [0, 1],
1887
+ outputRange: [STATUS_POP_SCALE_FROM, 1]
1888
+ });
1889
+ const riseStyle = (value) => ({
1890
+ opacity: value,
1891
+ transform: [
1892
+ { translateY: value.interpolate({ inputRange: [0, 1], outputRange: [RISE_PX, 0] }) }
1893
+ ]
1894
+ });
1895
+ const blocked = status === "blocked";
1896
+ const shownTitle = blocked ? blockedTitle != null ? blockedTitle : title : title;
1897
+ const shownMessage = blocked ? blockedMessage != null ? blockedMessage : message : message;
1898
+ const primaryTitle = primaryAction === "settings" ? settingsLabel != null ? settingsLabel : primaryLabel : primaryAction === "continue" ? continueLabel != null ? continueLabel : primaryLabel : primaryLabel;
1899
+ const showSecondary = status !== "granted";
1900
+ return /* @__PURE__ */ jsxRuntime.jsx(
1901
+ CardLayout,
1902
+ {
1903
+ align: "center",
1904
+ footer: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles10.footer, { gap: t.spacing.sm }], children: [
1905
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { title: primaryTitle, onPress: handlePrimary, variant: "primary", disabled: busy }),
1906
+ showSecondary ? /* @__PURE__ */ jsxRuntime.jsx(
1907
+ Button,
1908
+ {
1909
+ title: secondaryLabel,
1910
+ onPress: handleSecondary,
1911
+ variant: "outline",
1912
+ disabled: busy
1913
+ }
1914
+ ) : null
1915
+ ] }),
1916
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles10.center, { gap: t.spacing.md }], children: [
1917
+ art ? /* @__PURE__ */ jsxRuntime.jsx(
1918
+ reactNative.Animated.View,
1919
+ {
1920
+ style: [styles10.art, { opacity: popT, transform: [{ scale: popScale }] }],
1921
+ children: art
1922
+ }
1923
+ ) : null,
1924
+ /* @__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: shownTitle }) }),
1925
+ /* @__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: shownMessage }) })
1926
+ ] })
1927
+ }
1928
+ );
1929
+ };
1930
+ var PermissionCardView = React19__default.default.memo(_PermissionCard);
1931
+ var PermissionCard = {
1932
+ name: PERMISSION_CARD_NAME,
1933
+ description: "A priming screen shown BEFORE an OS permission dialog: it explains why the app wants the permission and asks only on the primary tap. Never first, never last. The user is never blocked by it: the secondary advances the flow with the OS prompt unspent.",
1934
+ component: PermissionCardView,
1935
+ propsSchema: schema
1936
+ };
1937
+ var styles10 = reactNative.StyleSheet.create({
1938
+ center: { width: "100%", alignItems: "center", justifyContent: "center" },
1939
+ art: { alignItems: "center", justifyContent: "center" },
1940
+ footer: { width: "100%", alignItems: "center" }
1941
+ });
1942
+
1943
+ // src/permissions/placement.ts
1944
+ var DEFAULT_PERMISSION_PLACEMENT = "beforeEnd";
1945
+ var normalizeAfterCard = (value) => Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
1946
+ var targetIndex = (placement) => {
1947
+ if (placement === "beforeEnd") return void 0;
1948
+ if (placement === "start") return 0;
1949
+ return normalizeAfterCard(placement.afterCard);
1950
+ };
1951
+ var isPermissionDue = (placement, position) => {
1952
+ if (position.cardIndex < 0) return false;
1953
+ const target = targetIndex(placement);
1954
+ if (target === void 0) return position.isTerminal;
1955
+ return position.cardIndex >= target || position.isTerminal;
1956
+ };
1957
+ var permissionScreenId = (screen, index) => {
1958
+ var _a2;
1959
+ return (_a2 = screen.id) != null ? _a2 : `${screen.permission}:${index}`;
1960
+ };
1961
+ var selectDuePermissionScreen = (screens, settled, position) => {
1962
+ var _a2;
1963
+ if (!screens || screens.length === 0) return void 0;
1964
+ for (let index = 0; index < screens.length; index++) {
1965
+ const screen = screens[index];
1966
+ if (!screen) continue;
1967
+ const id = permissionScreenId(screen, index);
1968
+ if (settled.includes(id)) continue;
1969
+ if (isPermissionDue((_a2 = screen.placement) != null ? _a2 : DEFAULT_PERMISSION_PLACEMENT, position)) {
1970
+ return { screen, id };
1971
+ }
1972
+ }
1973
+ return void 0;
1974
+ };
1975
+
1976
+ // src/permissions/permissionCopy.ts
1977
+ var NOTIFICATIONS_PERMISSION_COPY = {
1978
+ title: "Want a nudge at the right moment?",
1979
+ message: "Turn notifications on and we will remind you when it actually helps. No daily noise, and you can turn them off any time.",
1980
+ primaryLabel: "Enable notifications",
1981
+ secondaryLabel: "Maybe later",
1982
+ blockedTitle: "Notifications are switched off",
1983
+ blockedMessage: "Notifications are turned off for this app, so we cannot ask from here. You can switch them back on in Settings.",
1984
+ settingsLabel: "Open settings",
1985
+ continueLabel: "Continue"
1986
+ };
1987
+ var GENERIC_PERMISSION_COPY = {
1988
+ title: "One quick permission",
1989
+ message: "We need your permission for this part to work. You can change it any time.",
1990
+ primaryLabel: "Allow",
1991
+ secondaryLabel: "Maybe later",
1992
+ blockedTitle: "Permission is switched off",
1993
+ blockedMessage: "This permission is turned off for this app, so we cannot ask from here. You can switch it back on in Settings.",
1994
+ settingsLabel: "Open settings",
1995
+ continueLabel: "Continue"
1996
+ };
1997
+ var DEFAULT_PERMISSION_COPY = {
1998
+ notifications: NOTIFICATIONS_PERMISSION_COPY
1999
+ };
2000
+ var resolvePermissionCopy = (permission, overrides) => {
2001
+ var _a2;
2002
+ const base = (_a2 = DEFAULT_PERMISSION_COPY[permission]) != null ? _a2 : GENERIC_PERMISSION_COPY;
2003
+ if (!overrides) return base;
2004
+ const merged = { ...base };
2005
+ for (const key of Object.keys(overrides)) {
2006
+ const value = overrides[key];
2007
+ if (typeof value === "string" && value.length > 0) merged[key] = value;
2008
+ }
2009
+ return merged;
2010
+ };
1600
2011
  var SKIP_ONE_SENTINEL = "__wireai_skip__";
1601
2012
  var ANSWER_CALLBACKS = ["onSubmit", "onSelect", "onConfirm", "onPress"];
1602
2013
  var DEFAULT_COPY = {
@@ -1625,53 +2036,60 @@ var OnboardingFlow = ({
1625
2036
  reportTarget,
1626
2037
  sessionId,
1627
2038
  resumed = false,
1628
- clientContext
2039
+ clientContext,
2040
+ permissionScreens,
2041
+ settledPermissions,
2042
+ permissionsPending = false,
2043
+ onPermissionSettled
1629
2044
  }) => {
1630
2045
  var _a2, _b, _c, _d, _e, _f;
1631
2046
  const { messages, error, isLoading, sendMessage, reset } = wireaiRn.useWireAIThread();
1632
2047
  const makeActions = wireaiRn.useWireAIAction(sendMessage);
1633
- const c = React18.useMemo(() => ({ ...DEFAULT_COPY, ...copy }), [copy]);
1634
- const onEventRef = React18.useRef(onEvent);
2048
+ const c = React19.useMemo(() => ({ ...DEFAULT_COPY, ...copy }), [copy]);
2049
+ const onEventRef = React19.useRef(onEvent);
1635
2050
  onEventRef.current = onEvent;
1636
- const onErrorRef = React18.useRef(onError);
2051
+ const onErrorRef = React19.useRef(onError);
1637
2052
  onErrorRef.current = onError;
1638
- const onCompleteRef = React18.useRef(onComplete);
2053
+ const onCompleteRef = React19.useRef(onComplete);
1639
2054
  onCompleteRef.current = onComplete;
1640
- const validatorsRef = React18.useRef(validators);
2055
+ const validatorsRef = React19.useRef(validators);
1641
2056
  validatorsRef.current = validators;
1642
- const reportTargetRef = React18.useRef(reportTarget);
2057
+ const reportTargetRef = React19.useRef(reportTarget);
1643
2058
  reportTargetRef.current = reportTarget;
1644
- const sessionIdRef = React18.useRef(sessionId);
2059
+ const sessionIdRef = React19.useRef(sessionId);
1645
2060
  sessionIdRef.current = sessionId;
1646
- const clientContextRef = React18.useRef(clientContext);
2061
+ const clientContextRef = React19.useRef(clientContext);
1647
2062
  clientContextRef.current = clientContext;
1648
- const lastScreenIndexRef = React18.useRef(-1);
1649
- const previewTimer = React18.useRef(null);
1650
- const started = React18.useRef(false);
1651
- const finished = React18.useRef(false);
1652
- const errored = React18.useRef(false);
1653
- const attempts = React18.useRef(0);
1654
- const degradedRef = React18.useRef(false);
1655
- const [degraded, setDegraded] = React18.useState(false);
1656
- const [timedOut, setTimedOut] = React18.useState(false);
1657
- const [validationError, setValidationError] = React18.useState();
1658
- const [validating, setValidating] = React18.useState(false);
1659
- const lastCard = React18.useMemo(
2063
+ const lastScreenIndexRef = React19.useRef(-1);
2064
+ const previewTimer = React19.useRef(null);
2065
+ const started = React19.useRef(false);
2066
+ const finished = React19.useRef(false);
2067
+ const errored = React19.useRef(false);
2068
+ const attempts = React19.useRef(0);
2069
+ const degradedRef = React19.useRef(false);
2070
+ const [degraded, setDegraded] = React19.useState(false);
2071
+ const [timedOut, setTimedOut] = React19.useState(false);
2072
+ const [validationError, setValidationError] = React19.useState();
2073
+ const [validating, setValidating] = React19.useState(false);
2074
+ const [locallySettledPermissions, setLocallySettledPermissions] = React19.useState([]);
2075
+ const onPermissionSettledRef = React19.useRef(onPermissionSettled);
2076
+ onPermissionSettledRef.current = onPermissionSettled;
2077
+ const lastCard = React19.useMemo(
1660
2078
  () => [...messages].reverse().find((m) => {
1661
2079
  var _a3;
1662
2080
  return m.role === "assistant" && ((_a3 = m.response) == null ? void 0 : _a3.action) === "render";
1663
2081
  }),
1664
2082
  [messages]
1665
2083
  );
1666
- const renderedCount = React18.useMemo(
2084
+ const renderedCount = React19.useMemo(
1667
2085
  () => messages.filter((m) => {
1668
2086
  var _a3;
1669
2087
  return m.role === "assistant" && ((_a3 = m.response) == null ? void 0 : _a3.action) === "render";
1670
2088
  }).length,
1671
2089
  [messages]
1672
2090
  );
1673
- const progress = React18.useMemo(() => readProgress(lastCard == null ? void 0 : lastCard.response), [lastCard]);
1674
- React18.useEffect(() => {
2091
+ const progress = React19.useMemo(() => readProgress(lastCard == null ? void 0 : lastCard.response), [lastCard]);
2092
+ React19.useEffect(() => {
1675
2093
  var _a3;
1676
2094
  if (started.current) return;
1677
2095
  started.current = true;
@@ -1681,12 +2099,12 @@ var OnboardingFlow = ({
1681
2099
  resumed ? { type: "resumed", contextId: sessionId } : { type: "started", contextId: sessionId }
1682
2100
  );
1683
2101
  }, [sendMessage, startMessage, resumed, sessionId]);
1684
- React18.useEffect(() => {
2102
+ React19.useEffect(() => {
1685
2103
  if (lastCard) return;
1686
2104
  const timer = setTimeout(() => setTimedOut(true), startTimeoutMs);
1687
2105
  return () => clearTimeout(timer);
1688
2106
  }, [lastCard, startTimeoutMs]);
1689
- React18.useEffect(() => {
2107
+ React19.useEffect(() => {
1690
2108
  var _a3, _b2, _c2, _d2;
1691
2109
  setValidationError(void 0);
1692
2110
  if (!(lastCard == null ? void 0 : lastCard.id)) return;
@@ -1704,7 +2122,7 @@ var OnboardingFlow = ({
1704
2122
  });
1705
2123
  }
1706
2124
  }, [lastCard == null ? void 0 : lastCard.id]);
1707
- React18.useEffect(() => {
2125
+ React19.useEffect(() => {
1708
2126
  return () => {
1709
2127
  var _a3, _b2, _c2;
1710
2128
  if (finished.current || degradedRef.current) return;
@@ -1718,13 +2136,13 @@ var OnboardingFlow = ({
1718
2136
  });
1719
2137
  };
1720
2138
  }, []);
1721
- const restartThread = React18.useCallback(() => {
2139
+ const restartThread = React19.useCallback(() => {
1722
2140
  setTimedOut(false);
1723
2141
  started.current = false;
1724
2142
  errored.current = false;
1725
2143
  reset();
1726
2144
  }, [reset]);
1727
- React18.useEffect(() => {
2145
+ React19.useEffect(() => {
1728
2146
  var _a3, _b2, _c2, _d2, _e2, _f2, _g;
1729
2147
  if (errored.current) return;
1730
2148
  if (!error && !timedOut) return;
@@ -1751,22 +2169,52 @@ var OnboardingFlow = ({
1751
2169
  (_g = onErrorRef.current) == null ? void 0 : _g.call(onErrorRef, error != null ? error : new Error("Onboarding timed out"));
1752
2170
  }
1753
2171
  }, [error, timedOut, maxRetries]);
1754
- const handleFinish = React18.useCallback(() => {
2172
+ const handleFinish = React19.useCallback(() => {
1755
2173
  if (finished.current) return;
1756
2174
  finished.current = true;
1757
2175
  onCompleteRef.current({ answers: deriveAnswers(messages), raw: messages });
1758
2176
  }, [messages]);
1759
- const handleRetry = React18.useCallback(() => {
2177
+ const handleRetry = React19.useCallback(() => {
1760
2178
  attempts.current = 0;
1761
2179
  degradedRef.current = false;
1762
2180
  setDegraded(false);
1763
2181
  restartThread();
1764
2182
  }, [restartThread]);
1765
- const skipQuestion = React18.useCallback(() => {
2183
+ const skipQuestion = React19.useCallback(() => {
1766
2184
  if (isLoading) return;
1767
2185
  sendMessage(SKIP_ONE_SENTINEL);
1768
2186
  }, [isLoading, sendMessage]);
1769
- const previewOnSelect = React18.useCallback((value) => {
2187
+ const emitPermissionStage = React19.useCallback(
2188
+ (permission, stage, status) => {
2189
+ var _a3, _b2, _c2, _d2;
2190
+ (_a3 = onEventRef.current) == null ? void 0 : _a3.call(onEventRef, { type: "permission", permission, stage, status });
2191
+ reportClientEvent(reportTargetRef.current, {
2192
+ event_type: "app_event",
2193
+ session_id: sessionIdRef.current,
2194
+ question_key: permissionEventName(stage),
2195
+ component: PERMISSION_CARD_NAME,
2196
+ screen_index: lastScreenIndexRef.current >= 0 ? lastScreenIndexRef.current : void 0,
2197
+ meta: JSON.stringify(permissionEventProps(permission, status)),
2198
+ device: (_b2 = clientContextRef.current) == null ? void 0 : _b2.device,
2199
+ user_context: (_c2 = clientContextRef.current) == null ? void 0 : _c2.userContext,
2200
+ user_id: (_d2 = clientContextRef.current) == null ? void 0 : _d2.userId
2201
+ });
2202
+ },
2203
+ []
2204
+ );
2205
+ const settlePermission = React19.useCallback(
2206
+ (id, screen, outcome) => {
2207
+ var _a3, _b2;
2208
+ setLocallySettledPermissions((prev) => prev.includes(id) ? prev : [...prev, id]);
2209
+ (_a3 = onPermissionSettledRef.current) == null ? void 0 : _a3.call(onPermissionSettledRef, id, outcome);
2210
+ try {
2211
+ (_b2 = screen.onResult) == null ? void 0 : _b2.call(screen, screen.permission, outcome);
2212
+ } catch {
2213
+ }
2214
+ },
2215
+ []
2216
+ );
2217
+ const previewOnSelect = React19.useCallback((value) => {
1770
2218
  const target = reportTargetRef.current;
1771
2219
  if (!(target == null ? void 0 : target.serverUrl)) return;
1772
2220
  if (Array.isArray(value) ? value.length === 0 : !value) return;
@@ -1778,10 +2226,10 @@ var OnboardingFlow = ({
1778
2226
  });
1779
2227
  }, 300);
1780
2228
  }, []);
1781
- React18.useEffect(() => () => {
2229
+ React19.useEffect(() => () => {
1782
2230
  if (previewTimer.current) clearTimeout(previewTimer.current);
1783
2231
  }, []);
1784
- const wrappedActions = React18.useCallback(
2232
+ const wrappedActions = React19.useCallback(
1785
2233
  (messageId) => {
1786
2234
  var _a3;
1787
2235
  const base = makeActions(messageId);
@@ -1836,6 +2284,36 @@ var OnboardingFlow = ({
1836
2284
  const step = (_a2 = progress.step) != null ? _a2 : renderedCount;
1837
2285
  const total = (_b = progress.total) != null ? _b : approxScreens;
1838
2286
  const isTerminal = response.action === "render" && response.component === "StatusCard";
2287
+ const duePermission = permissionsPending ? void 0 : selectDuePermissionScreen(
2288
+ permissionScreens,
2289
+ settledPermissions ? [...settledPermissions, ...locallySettledPermissions] : locallySettledPermissions,
2290
+ { cardIndex: renderedCount - 1, isTerminal }
2291
+ );
2292
+ if (duePermission) {
2293
+ const { screen, id } = duePermission;
2294
+ const copy2 = resolvePermissionCopy(screen.permission, screen.copy);
2295
+ return /* @__PURE__ */ jsxRuntime.jsx(OnboardingScaffold, { step, approxScreens: total, children: /* @__PURE__ */ jsxRuntime.jsx(CardHandoff, { transitionKey: `permission:${id}`, variant: "spring", children: /* @__PURE__ */ jsxRuntime.jsx(
2296
+ PermissionCardView,
2297
+ {
2298
+ permission: screen.permission,
2299
+ title: copy2.title,
2300
+ message: copy2.message,
2301
+ primaryLabel: copy2.primaryLabel,
2302
+ secondaryLabel: copy2.secondaryLabel,
2303
+ blockedTitle: copy2.blockedTitle,
2304
+ blockedMessage: copy2.blockedMessage,
2305
+ settingsLabel: copy2.settingsLabel,
2306
+ continueLabel: copy2.continueLabel,
2307
+ illustration: screen.illustration,
2308
+ request: screen.request,
2309
+ getStatus: screen.getStatus,
2310
+ openSettings: screen.openSettings,
2311
+ onStage: (stage, status) => emitPermissionStage(screen.permission, stage, status),
2312
+ onSettled: (outcome) => settlePermission(id, screen, outcome)
2313
+ },
2314
+ id
2315
+ ) }) });
2316
+ }
1839
2317
  if (isTerminal) {
1840
2318
  const props = (_c = response.props) != null ? _c : {};
1841
2319
  return /* @__PURE__ */ jsxRuntime.jsx(OnboardingScaffold, { step, complete: true, approxScreens: total, children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -1894,8 +2372,8 @@ var OnboardingFlow = ({
1894
2372
  }
1895
2373
  );
1896
2374
  };
1897
- var easeWire4 = reactNative.Easing.bezier(...WIRE_BEZIER);
1898
- var schema = zod.z.object({
2375
+ var easeWire5 = reactNative.Easing.bezier(...WIRE_BEZIER);
2376
+ var schema2 = zod.z.object({
1899
2377
  title: zod.z.string().describe("Question or section header, e.g. 'Which season?'"),
1900
2378
  chips: zod.z.array(zod.z.string()).min(1).describe("List of option labels shown as chips, e.g. ['Spring','Summer','Autumn','Winter']"),
1901
2379
  multiSelect: zod.z.boolean().optional().describe("Allow multiple chips selected at once, default true"),
@@ -1905,9 +2383,9 @@ var schema = zod.z.object({
1905
2383
  var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
1906
2384
  const t = useOnboardingTheme();
1907
2385
  const reduced = useReducedMotion();
1908
- const handlePress = React18.useCallback(() => onToggle(chip), [chip, onToggle]);
1909
- const pressScale = React18.useRef(new reactNative.Animated.Value(1)).current;
1910
- const pressTo = React18.useCallback(
2386
+ const handlePress = React19.useCallback(() => onToggle(chip), [chip, onToggle]);
2387
+ const pressScale = React19.useRef(new reactNative.Animated.Value(1)).current;
2388
+ const pressTo = React19.useCallback(
1911
2389
  (to) => {
1912
2390
  if (reduced) return;
1913
2391
  reactNative.Animated.spring(pressScale, {
@@ -1919,21 +2397,21 @@ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
1919
2397
  },
1920
2398
  [pressScale, reduced]
1921
2399
  );
1922
- const handlePressIn = React18.useCallback(() => pressTo(CHIP_PRESS_SCALE), [pressTo]);
1923
- const handlePressOut = React18.useCallback(() => pressTo(1), [pressTo]);
1924
- const fillT = React18.useRef(new reactNative.Animated.Value(isSelected ? 1 : 0)).current;
1925
- React18.useEffect(() => {
2400
+ const handlePressIn = React19.useCallback(() => pressTo(CHIP_PRESS_SCALE), [pressTo]);
2401
+ const handlePressOut = React19.useCallback(() => pressTo(1), [pressTo]);
2402
+ const fillT = React19.useRef(new reactNative.Animated.Value(isSelected ? 1 : 0)).current;
2403
+ React19.useEffect(() => {
1926
2404
  const anim = reactNative.Animated.timing(fillT, {
1927
2405
  toValue: isSelected ? 1 : 0,
1928
2406
  duration: scaledMs(stateChangeDuration(reduced, CHIP_FILL_MS)),
1929
- easing: easeWire4,
2407
+ easing: easeWire5,
1930
2408
  useNativeDriver: true
1931
2409
  });
1932
2410
  anim.start();
1933
2411
  return () => anim.stop();
1934
2412
  }, [isSelected, reduced, fillT]);
1935
- const checkT = React18.useRef(new reactNative.Animated.Value(isSelected ? 1 : 0)).current;
1936
- React18.useEffect(() => {
2413
+ const checkT = React19.useRef(new reactNative.Animated.Value(isSelected ? 1 : 0)).current;
2414
+ React19.useEffect(() => {
1937
2415
  if (!isSelected) {
1938
2416
  checkT.setValue(0);
1939
2417
  return;
@@ -1966,7 +2444,7 @@ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
1966
2444
  reactNative.Animated.View,
1967
2445
  {
1968
2446
  style: [
1969
- styles10.chip,
2447
+ styles11.chip,
1970
2448
  {
1971
2449
  borderRadius: t.radius.full,
1972
2450
  paddingHorizontal: t.spacing.md,
@@ -1983,7 +2461,7 @@ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
1983
2461
  pointerEvents: "none",
1984
2462
  style: [
1985
2463
  reactNative.StyleSheet.absoluteFill,
1986
- styles10.fillOverlay,
2464
+ styles11.fillOverlay,
1987
2465
  {
1988
2466
  borderRadius: t.radius.full,
1989
2467
  borderColor: t.colors.primary,
@@ -1997,7 +2475,7 @@ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
1997
2475
  reactNative.Animated.Text,
1998
2476
  {
1999
2477
  style: [
2000
- styles10.check,
2478
+ styles11.check,
2001
2479
  { color: t.colors.onPrimary, opacity: checkT, transform: [{ scale: checkScale }] }
2002
2480
  ],
2003
2481
  children: "\u2713"
@@ -2008,7 +2486,7 @@ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
2008
2486
  {
2009
2487
  style: [
2010
2488
  captionStyle(t.fonts),
2011
- styles10.chipLabel,
2489
+ styles11.chipLabel,
2012
2490
  { color: isSelected ? t.colors.onPrimary : t.colors.textMuted }
2013
2491
  ],
2014
2492
  children: chip
@@ -2020,7 +2498,7 @@ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
2020
2498
  }
2021
2499
  );
2022
2500
  };
2023
- var ChipItem = React18__default.default.memo(_ChipItem);
2501
+ var ChipItem = React19__default.default.memo(_ChipItem);
2024
2502
  var _ChipSelectCard = ({
2025
2503
  title,
2026
2504
  chips,
@@ -2031,12 +2509,12 @@ var _ChipSelectCard = ({
2031
2509
  onSelectionChange
2032
2510
  }) => {
2033
2511
  const t = useOnboardingTheme();
2034
- const [selected, setSelected] = React18.useState([]);
2035
- const [submitted, setSubmitted] = React18.useState(false);
2036
- React18.useEffect(() => {
2512
+ const [selected, setSelected] = React19.useState([]);
2513
+ const [submitted, setSubmitted] = React19.useState(false);
2514
+ React19.useEffect(() => {
2037
2515
  if (!submitted && selected.length) onSelectionChange == null ? void 0 : onSelectionChange(multiSelect ? selected : selected[0]);
2038
2516
  }, [selected, submitted, multiSelect, onSelectionChange]);
2039
- const toggle = React18.useCallback(
2517
+ const toggle = React19.useCallback(
2040
2518
  (chip) => {
2041
2519
  if (submitted) return;
2042
2520
  if (multiSelect) {
@@ -2051,7 +2529,7 @@ var _ChipSelectCard = ({
2051
2529
  },
2052
2530
  [submitted, multiSelect, maxSelections]
2053
2531
  );
2054
- const handleSubmit = React18.useCallback(() => {
2532
+ const handleSubmit = React19.useCallback(() => {
2055
2533
  if (!selected.length || !onSelect || submitted) return;
2056
2534
  setSubmitted(true);
2057
2535
  onSelect(multiSelect ? selected : selected[0]);
@@ -2070,7 +2548,7 @@ var _ChipSelectCard = ({
2070
2548
  disabled: selected.length === 0 || submitted
2071
2549
  }
2072
2550
  ),
2073
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles10.chipsRow, { gap: t.spacing.sm + t.spacing.xs }], children: chips.map((chip) => /* @__PURE__ */ jsxRuntime.jsx(
2551
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles11.chipsRow, { gap: t.spacing.sm + t.spacing.xs }], children: chips.map((chip) => /* @__PURE__ */ jsxRuntime.jsx(
2074
2552
  ChipItem,
2075
2553
  {
2076
2554
  chip,
@@ -2086,18 +2564,18 @@ var _ChipSelectCard = ({
2086
2564
  var ChipSelectCard = {
2087
2565
  name: "ChipSelectCard",
2088
2566
  description: "Use for compact short labels: seasons, activities, travel styles, moods, categories. Ideal for 4\u201312 single-word or two-word options. Use SelectionCard instead when options need descriptions; use CardGridSelectCard for a smaller set (2\u20136) of visual choices that each deserve an icon.",
2089
- component: React18__default.default.memo(_ChipSelectCard),
2090
- propsSchema: schema,
2567
+ component: React19__default.default.memo(_ChipSelectCard),
2568
+ propsSchema: schema2,
2091
2569
  defaultProps: { multiSelect: true, submitLabel: "Continue" }
2092
2570
  };
2093
- var styles10 = reactNative.StyleSheet.create({
2571
+ var styles11 = reactNative.StyleSheet.create({
2094
2572
  chipsRow: { flexDirection: "row", flexWrap: "wrap" },
2095
2573
  chip: { borderWidth: 1.5, flexDirection: "row", alignItems: "center", gap: 6, overflow: "hidden" },
2096
2574
  fillOverlay: { borderWidth: 1.5 },
2097
2575
  chipLabel: { fontWeight: "600" },
2098
2576
  check: { fontSize: 13, fontWeight: "800", lineHeight: 15 }
2099
2577
  });
2100
- var schema2 = zod.z.object({
2578
+ var schema3 = zod.z.object({
2101
2579
  label: zod.z.string().describe("Input field label / the question being asked"),
2102
2580
  hint: zod.z.string().optional().describe(
2103
2581
  "One short line under the question giving context: what the answer is used for or why you ask. Add it whenever the question alone might be unclear."
@@ -2122,14 +2600,14 @@ var _TextInputCard = ({
2122
2600
  lines = 6
2123
2601
  }) => {
2124
2602
  const t = useOnboardingTheme();
2125
- const styles17 = React18.useMemo(() => makeStyles3(t), [t]);
2603
+ const styles18 = React19.useMemo(() => makeStyles3(t), [t]);
2126
2604
  const { height: screenHeight } = reactNative.useWindowDimensions();
2127
- const [value, setValue] = React18.useState("");
2128
- const [submitted, setSubmitted] = React18.useState(false);
2605
+ const [value, setValue] = React19.useState("");
2606
+ const [submitted, setSubmitted] = React19.useState(false);
2129
2607
  const locked = submitted && !validationError;
2130
2608
  const APPROX_ROW_HEIGHT = 26;
2131
2609
  const minHeight = multiline ? Math.min(Math.max(lines, 4) * APPROX_ROW_HEIGHT + t.spacing.md * 2, screenHeight * 0.4) : 52;
2132
- const handleSubmit = React18.useCallback(() => {
2610
+ const handleSubmit = React19.useCallback(() => {
2133
2611
  if (value.trim() && onSubmit && !locked) {
2134
2612
  setSubmitted(true);
2135
2613
  onSubmit(value.trim());
@@ -2149,7 +2627,7 @@ var _TextInputCard = ({
2149
2627
  disabled: !value.trim() || locked
2150
2628
  }
2151
2629
  ),
2152
- children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles17.row, children: [
2630
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles18.row, children: [
2153
2631
  /* @__PURE__ */ jsxRuntime.jsx(
2154
2632
  reactNative.TextInput,
2155
2633
  {
@@ -2163,7 +2641,7 @@ var _TextInputCard = ({
2163
2641
  textAlignVertical: multiline ? "top" : "center",
2164
2642
  style: [
2165
2643
  bodyStyle(t.fonts),
2166
- styles17.input,
2644
+ styles18.input,
2167
2645
  {
2168
2646
  color: t.colors.text,
2169
2647
  backgroundColor: t.colors.surface,
@@ -2183,8 +2661,8 @@ var _TextInputCard = ({
2183
2661
  var TextInputCard = {
2184
2662
  name: "TextInputCard",
2185
2663
  description: "Use when the user needs to type a free-text answer: an open-ended goal, notes, feedback, a name, a destination, a search query. Multi-line by default (great for open-ended goals); set multiline:false for a short single-line field like a name. Use NumberStepperCard for numeric ranges with known bounds.",
2186
- component: React18__default.default.memo(_TextInputCard),
2187
- propsSchema: schema2,
2664
+ component: React19__default.default.memo(_TextInputCard),
2665
+ propsSchema: schema3,
2188
2666
  defaultProps: { submitLabel: "Submit", multiline: true, lines: 6 }
2189
2667
  };
2190
2668
  function makeStyles3(t) {
@@ -2339,7 +2817,7 @@ var _WireIcon = ({ name: name2, size = 20, color, requireModule }) => {
2339
2817
  return /* @__PURE__ */ jsxRuntime.jsx(
2340
2818
  reactNative.View,
2341
2819
  {
2342
- style: styles11.decorative,
2820
+ style: styles12.decorative,
2343
2821
  accessible: false,
2344
2822
  importantForAccessibility: "no-hide-descendants",
2345
2823
  children: hostIcon
@@ -2353,7 +2831,7 @@ var _WireIcon = ({ name: name2, size = 20, color, requireModule }) => {
2353
2831
  return /* @__PURE__ */ jsxRuntime.jsx(
2354
2832
  reactNative.View,
2355
2833
  {
2356
- style: styles11.decorative,
2834
+ style: styles12.decorative,
2357
2835
  accessible: false,
2358
2836
  importantForAccessibility: "no-hide-descendants",
2359
2837
  children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -2368,9 +2846,9 @@ var _WireIcon = ({ name: name2, size = 20, color, requireModule }) => {
2368
2846
  }
2369
2847
  );
2370
2848
  };
2371
- var WireIcon = React18__default.default.memo(_WireIcon);
2849
+ var WireIcon = React19__default.default.memo(_WireIcon);
2372
2850
  WireIcon.displayName = "WireIcon";
2373
- var styles11 = reactNative.StyleSheet.create({
2851
+ var styles12 = reactNative.StyleSheet.create({
2374
2852
  // No size/margin of its own: the glyph decides its footprint, so an absent icon and a
2375
2853
  // present one differ by exactly the glyph — never by a wrapper.
2376
2854
  decorative: {}
@@ -2384,8 +2862,8 @@ var optionObjectSchema = zod.z.object({
2384
2862
  var coerceOptions = (arr) => Array.isArray(arr) ? arr.map((item) => typeof item === "string" ? { value: item, label: item } : item) : arr;
2385
2863
  var optionsField = (description) => zod.z.preprocess(coerceOptions, zod.z.array(optionObjectSchema).min(1)).describe(description);
2386
2864
  var normalizeOptions = (options) => options.map((opt) => typeof opt === "string" ? { value: opt, label: opt } : opt);
2387
- var easeWire5 = reactNative.Easing.bezier(...WIRE_BEZIER);
2388
- var schema3 = zod.z.object({
2865
+ var easeWire6 = reactNative.Easing.bezier(...WIRE_BEZIER);
2866
+ var schema4 = zod.z.object({
2389
2867
  title: zod.z.string().describe("Question or prompt for the user"),
2390
2868
  options: optionsField("List of choices"),
2391
2869
  submitLabel: zod.z.string().optional().describe("Submit button label, default: Continue"),
@@ -2394,9 +2872,9 @@ var schema3 = zod.z.object({
2394
2872
  var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
2395
2873
  const t = useOnboardingTheme();
2396
2874
  const reduced = useReducedMotion();
2397
- const handlePress = React18.useCallback(() => onToggle(opt.value), [opt.value, onToggle]);
2398
- const pressScale = React18.useRef(new reactNative.Animated.Value(1)).current;
2399
- const pressTo = React18.useCallback(
2875
+ const handlePress = React19.useCallback(() => onToggle(opt.value), [opt.value, onToggle]);
2876
+ const pressScale = React19.useRef(new reactNative.Animated.Value(1)).current;
2877
+ const pressTo = React19.useCallback(
2400
2878
  (to) => {
2401
2879
  if (reduced) return;
2402
2880
  reactNative.Animated.spring(pressScale, {
@@ -2408,21 +2886,21 @@ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
2408
2886
  },
2409
2887
  [pressScale, reduced]
2410
2888
  );
2411
- const handlePressIn = React18.useCallback(() => pressTo(ROW_PRESS_SCALE), [pressTo]);
2412
- const handlePressOut = React18.useCallback(() => pressTo(1), [pressTo]);
2413
- const selectT = React18.useRef(new reactNative.Animated.Value(isSelected ? 1 : 0)).current;
2414
- React18.useEffect(() => {
2889
+ const handlePressIn = React19.useCallback(() => pressTo(ROW_PRESS_SCALE), [pressTo]);
2890
+ const handlePressOut = React19.useCallback(() => pressTo(1), [pressTo]);
2891
+ const selectT = React19.useRef(new reactNative.Animated.Value(isSelected ? 1 : 0)).current;
2892
+ React19.useEffect(() => {
2415
2893
  const anim = reactNative.Animated.timing(selectT, {
2416
2894
  toValue: isSelected ? 1 : 0,
2417
2895
  duration: scaledMs(stateChangeDuration(reduced, SELECT_FILL_MS)),
2418
- easing: easeWire5,
2896
+ easing: easeWire6,
2419
2897
  useNativeDriver: true
2420
2898
  });
2421
2899
  anim.start();
2422
2900
  return () => anim.stop();
2423
2901
  }, [isSelected, reduced, selectT]);
2424
- const checkT = React18.useRef(new reactNative.Animated.Value(isSelected ? 1 : 0)).current;
2425
- React18.useEffect(() => {
2902
+ const checkT = React19.useRef(new reactNative.Animated.Value(isSelected ? 1 : 0)).current;
2903
+ React19.useEffect(() => {
2426
2904
  if (!isSelected) {
2427
2905
  checkT.setValue(0);
2428
2906
  return;
@@ -2455,7 +2933,7 @@ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
2455
2933
  reactNative.Animated.View,
2456
2934
  {
2457
2935
  style: [
2458
- styles12.option,
2936
+ styles13.option,
2459
2937
  {
2460
2938
  padding: t.spacing.md,
2461
2939
  borderRadius: t.radius.md,
@@ -2472,7 +2950,7 @@ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
2472
2950
  pointerEvents: "none",
2473
2951
  style: [
2474
2952
  reactNative.StyleSheet.absoluteFill,
2475
- styles12.selectedOverlay,
2953
+ styles13.selectedOverlay,
2476
2954
  {
2477
2955
  borderRadius: t.radius.md,
2478
2956
  borderColor: t.colors.primary,
@@ -2486,7 +2964,7 @@ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
2486
2964
  reactNative.View,
2487
2965
  {
2488
2966
  style: [
2489
- styles12.indicator,
2967
+ styles13.indicator,
2490
2968
  { borderRadius: t.radius.full },
2491
2969
  isSelected ? { borderColor: t.colors.primary, backgroundColor: t.colors.primary } : { borderColor: t.colors.borderStrong, backgroundColor: "transparent" }
2492
2970
  ],
@@ -2494,7 +2972,7 @@ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
2494
2972
  reactNative.Animated.Text,
2495
2973
  {
2496
2974
  style: [
2497
- styles12.check,
2975
+ styles13.check,
2498
2976
  { color: t.colors.onPrimary, opacity: checkT, transform: [{ scale: checkScale }] }
2499
2977
  ],
2500
2978
  children: "\u2713"
@@ -2510,7 +2988,7 @@ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
2510
2988
  }
2511
2989
  );
2512
2990
  };
2513
- var OptionRow = React18__default.default.memo(_OptionRow);
2991
+ var OptionRow = React19__default.default.memo(_OptionRow);
2514
2992
  var _SelectionCard = ({
2515
2993
  title,
2516
2994
  options,
@@ -2520,13 +2998,13 @@ var _SelectionCard = ({
2520
2998
  onSelectionChange
2521
2999
  }) => {
2522
3000
  const t = useOnboardingTheme();
2523
- const opts = React18__default.default.useMemo(() => normalizeOptions(options), [options]);
2524
- const [selected, setSelected] = React18.useState([]);
2525
- const [submitted, setSubmitted] = React18.useState(false);
2526
- React18.useEffect(() => {
3001
+ const opts = React19__default.default.useMemo(() => normalizeOptions(options), [options]);
3002
+ const [selected, setSelected] = React19.useState([]);
3003
+ const [submitted, setSubmitted] = React19.useState(false);
3004
+ React19.useEffect(() => {
2527
3005
  if (!submitted && selected.length) onSelectionChange == null ? void 0 : onSelectionChange(multiSelect ? selected : selected[0]);
2528
3006
  }, [selected, submitted, multiSelect, onSelectionChange]);
2529
- const toggle = React18.useCallback(
3007
+ const toggle = React19.useCallback(
2530
3008
  (value) => {
2531
3009
  if (submitted) return;
2532
3010
  if (multiSelect) {
@@ -2537,7 +3015,7 @@ var _SelectionCard = ({
2537
3015
  },
2538
3016
  [submitted, multiSelect]
2539
3017
  );
2540
- const handleSubmit = React18.useCallback(() => {
3018
+ const handleSubmit = React19.useCallback(() => {
2541
3019
  if (!selected.length || !onSelect || submitted) return;
2542
3020
  setSubmitted(true);
2543
3021
  onSelect(multiSelect ? selected : selected[0]);
@@ -2571,18 +3049,18 @@ var _SelectionCard = ({
2571
3049
  var SelectionCard = {
2572
3050
  name: "SelectionCard",
2573
3051
  description: "Use for mutually exclusive (radio) or multi-select (checkbox) choices from 2\u20138 options, as a vertical list of rows. Best when options are wordy or need reading. Options may carry an optional `icon` from the vocabulary to make the list scannable. Use ChipSelectCard instead when options are short single words; use CardGridSelectCard when the options are visual and each has an obvious icon (occasions, channels, categories).",
2574
- component: React18__default.default.memo(_SelectionCard),
2575
- propsSchema: schema3,
3052
+ component: React19__default.default.memo(_SelectionCard),
3053
+ propsSchema: schema4,
2576
3054
  defaultProps: { submitLabel: "Continue", multiSelect: false }
2577
3055
  };
2578
- var styles12 = reactNative.StyleSheet.create({
3056
+ var styles13 = reactNative.StyleSheet.create({
2579
3057
  option: { borderWidth: 1.5, flexDirection: "row", alignItems: "center", overflow: "hidden" },
2580
3058
  selectedOverlay: { borderWidth: 1.5 },
2581
3059
  indicator: { width: 24, height: 24, borderWidth: 2, alignItems: "center", justifyContent: "center" },
2582
3060
  check: { fontSize: 13, fontWeight: "800", lineHeight: 15 }
2583
3061
  });
2584
- var easeWire6 = reactNative.Easing.bezier(...WIRE_BEZIER);
2585
- var schema4 = zod.z.object({
3062
+ var easeWire7 = reactNative.Easing.bezier(...WIRE_BEZIER);
3063
+ var schema5 = zod.z.object({
2586
3064
  title: zod.z.string().describe("Question or prompt for the user, e.g. 'What brings you here?'"),
2587
3065
  options: optionsField(
2588
3066
  "The choices, 2-6 of them. Give EVERY option an `icon` from the vocabulary \u2014 the icon is the point of this card, and an option without one renders as a bare label next to icon-bearing neighbours. Keep labels short (1-3 words) so they fit a half-width card."
@@ -2593,9 +3071,9 @@ var schema4 = zod.z.object({
2593
3071
  var _OptionCard = ({ opt, isSelected, multiSelect, onToggle }) => {
2594
3072
  const t = useOnboardingTheme();
2595
3073
  const reduced = useReducedMotion();
2596
- const handlePress = React18.useCallback(() => onToggle(opt.value), [opt.value, onToggle]);
2597
- const pressScale = React18.useRef(new reactNative.Animated.Value(1)).current;
2598
- const pressTo = React18.useCallback(
3074
+ const handlePress = React19.useCallback(() => onToggle(opt.value), [opt.value, onToggle]);
3075
+ const pressScale = React19.useRef(new reactNative.Animated.Value(1)).current;
3076
+ const pressTo = React19.useCallback(
2599
3077
  (to) => {
2600
3078
  if (reduced) return;
2601
3079
  reactNative.Animated.spring(pressScale, {
@@ -2607,14 +3085,14 @@ var _OptionCard = ({ opt, isSelected, multiSelect, onToggle }) => {
2607
3085
  },
2608
3086
  [pressScale, reduced]
2609
3087
  );
2610
- const handlePressIn = React18.useCallback(() => pressTo(ROW_PRESS_SCALE), [pressTo]);
2611
- const handlePressOut = React18.useCallback(() => pressTo(1), [pressTo]);
2612
- const selectT = React18.useRef(new reactNative.Animated.Value(isSelected ? 1 : 0)).current;
2613
- React18.useEffect(() => {
3088
+ const handlePressIn = React19.useCallback(() => pressTo(ROW_PRESS_SCALE), [pressTo]);
3089
+ const handlePressOut = React19.useCallback(() => pressTo(1), [pressTo]);
3090
+ const selectT = React19.useRef(new reactNative.Animated.Value(isSelected ? 1 : 0)).current;
3091
+ React19.useEffect(() => {
2614
3092
  const anim = reactNative.Animated.timing(selectT, {
2615
3093
  toValue: isSelected ? 1 : 0,
2616
3094
  duration: scaledMs(stateChangeDuration(reduced, SELECT_FILL_MS)),
2617
- easing: easeWire6,
3095
+ easing: easeWire7,
2618
3096
  useNativeDriver: true
2619
3097
  });
2620
3098
  anim.start();
@@ -2629,12 +3107,12 @@ var _OptionCard = ({ opt, isSelected, multiSelect, onToggle }) => {
2629
3107
  accessibilityRole: multiSelect ? "checkbox" : "radio",
2630
3108
  accessibilityState: multiSelect ? { checked: isSelected } : { selected: isSelected },
2631
3109
  accessibilityLabel: opt.label,
2632
- style: styles13.cell,
3110
+ style: styles14.cell,
2633
3111
  children: /* @__PURE__ */ jsxRuntime.jsxs(
2634
3112
  reactNative.Animated.View,
2635
3113
  {
2636
3114
  style: [
2637
- styles13.card,
3115
+ styles14.card,
2638
3116
  {
2639
3117
  padding: t.spacing.md,
2640
3118
  borderRadius: t.radius.lg,
@@ -2651,7 +3129,7 @@ var _OptionCard = ({ opt, isSelected, multiSelect, onToggle }) => {
2651
3129
  pointerEvents: "none",
2652
3130
  style: [
2653
3131
  reactNative.StyleSheet.absoluteFill,
2654
- styles13.selectedOverlay,
3132
+ styles14.selectedOverlay,
2655
3133
  {
2656
3134
  borderRadius: t.radius.lg,
2657
3135
  borderColor: t.colors.primary,
@@ -2669,7 +3147,7 @@ var _OptionCard = ({ opt, isSelected, multiSelect, onToggle }) => {
2669
3147
  }
2670
3148
  );
2671
3149
  };
2672
- var OptionCard = React18__default.default.memo(_OptionCard);
3150
+ var OptionCard = React19__default.default.memo(_OptionCard);
2673
3151
  var _CardGridSelectCard = ({
2674
3152
  title,
2675
3153
  options,
@@ -2679,13 +3157,13 @@ var _CardGridSelectCard = ({
2679
3157
  onSelectionChange
2680
3158
  }) => {
2681
3159
  const t = useOnboardingTheme();
2682
- const opts = React18__default.default.useMemo(() => normalizeOptions(options), [options]);
2683
- const [selected, setSelected] = React18.useState([]);
2684
- const [submitted, setSubmitted] = React18.useState(false);
2685
- React18.useEffect(() => {
3160
+ const opts = React19__default.default.useMemo(() => normalizeOptions(options), [options]);
3161
+ const [selected, setSelected] = React19.useState([]);
3162
+ const [submitted, setSubmitted] = React19.useState(false);
3163
+ React19.useEffect(() => {
2686
3164
  if (!submitted && selected.length) onSelectionChange == null ? void 0 : onSelectionChange(multiSelect ? selected : selected[0]);
2687
3165
  }, [selected, submitted, multiSelect, onSelectionChange]);
2688
- const toggle = React18.useCallback(
3166
+ const toggle = React19.useCallback(
2689
3167
  (value) => {
2690
3168
  if (submitted) return;
2691
3169
  if (multiSelect) {
@@ -2698,7 +3176,7 @@ var _CardGridSelectCard = ({
2698
3176
  },
2699
3177
  [submitted, multiSelect]
2700
3178
  );
2701
- const handleSubmit = React18.useCallback(() => {
3179
+ const handleSubmit = React19.useCallback(() => {
2702
3180
  if (!selected.length || !onSelect || submitted) return;
2703
3181
  setSubmitted(true);
2704
3182
  onSelect(multiSelect ? selected : selected[0]);
@@ -2716,7 +3194,7 @@ var _CardGridSelectCard = ({
2716
3194
  disabled: selected.length === 0 || submitted
2717
3195
  }
2718
3196
  ),
2719
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles13.grid, { gap: t.spacing.sm }], children: opts.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(
3197
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles14.grid, { gap: t.spacing.sm }], children: opts.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(
2720
3198
  OptionCard,
2721
3199
  {
2722
3200
  opt,
@@ -2732,19 +3210,19 @@ var _CardGridSelectCard = ({
2732
3210
  var CardGridSelectCard = {
2733
3211
  name: "CardGridSelectCard",
2734
3212
  description: "Use for VISUAL, icon-led choices shown as a 2-column grid of tappable cards (icon on top, short label beneath). Ideal for 2\u20136 concrete options that each have an obvious icon \u2014 occasions, channels, goals, categories \u2014 e.g. 'What brings you here?' or 'Where did you hear about us?'. Multi-select by default. Every option should carry an `icon`. Use SelectionCard instead when options need descriptions or have no natural icon; use ChipSelectCard for many short word-like tags.",
2735
- component: React18__default.default.memo(_CardGridSelectCard),
2736
- propsSchema: schema4,
3213
+ component: React19__default.default.memo(_CardGridSelectCard),
3214
+ propsSchema: schema5,
2737
3215
  defaultProps: { submitLabel: "Continue", multiSelect: true }
2738
3216
  };
2739
- var styles13 = reactNative.StyleSheet.create({
3217
+ var styles14 = reactNative.StyleSheet.create({
2740
3218
  grid: { flexDirection: "row", flexWrap: "wrap" },
2741
3219
  // Two per row: ~47% basis leaves room for the gap, flexGrow fills the remainder exactly.
2742
3220
  cell: { flexBasis: "47%", flexGrow: 1 },
2743
3221
  card: { borderWidth: 1.5, alignItems: "flex-start", overflow: "hidden" },
2744
3222
  selectedOverlay: { borderWidth: 1.5 }
2745
3223
  });
2746
- var easeWire7 = reactNative.Easing.bezier(...WIRE_BEZIER);
2747
- var schema5 = zod.z.object({
3224
+ var easeWire8 = reactNative.Easing.bezier(...WIRE_BEZIER);
3225
+ var schema6 = zod.z.object({
2748
3226
  status: zod.z.enum(["success", "warning", "error", "info"]).describe("Status type"),
2749
3227
  title: zod.z.string().describe("Status title"),
2750
3228
  message: zod.z.string().optional().describe("Supporting message"),
@@ -2762,8 +3240,8 @@ var _StatusCard = ({ status, title, message, ctaLabel, onContinue, onPress, onSu
2762
3240
  var _a2;
2763
3241
  const t = useOnboardingTheme();
2764
3242
  const reduced = useReducedMotion();
2765
- const [submitted, setSubmitted] = React18.useState(false);
2766
- const color = React18.useMemo(() => {
3243
+ const [submitted, setSubmitted] = React19.useState(false);
3244
+ const color = React19.useMemo(() => {
2767
3245
  switch (status) {
2768
3246
  case "success":
2769
3247
  return t.colors.success;
@@ -2775,9 +3253,9 @@ var _StatusCard = ({ status, title, message, ctaLabel, onContinue, onPress, onSu
2775
3253
  return t.colors.info;
2776
3254
  }
2777
3255
  }, [status, t.colors]);
2778
- const popT = React18.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
2779
- const copyT = React18.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
2780
- React18.useEffect(() => {
3256
+ const popT = React19.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
3257
+ const copyT = React19.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
3258
+ React19.useEffect(() => {
2781
3259
  if (reduced) {
2782
3260
  popT.setValue(1);
2783
3261
  copyT.setValue(1);
@@ -2794,7 +3272,7 @@ var _StatusCard = ({ status, title, message, ctaLabel, onContinue, onPress, onSu
2794
3272
  toValue: 1,
2795
3273
  duration: scaledMs(STATUS_COPY_MS),
2796
3274
  delay: scaledMs(STATUS_COPY_STAGGER_MS),
2797
- easing: easeWire7,
3275
+ easing: easeWire8,
2798
3276
  useNativeDriver: true
2799
3277
  })
2800
3278
  ];
@@ -2803,7 +3281,7 @@ var _StatusCard = ({ status, title, message, ctaLabel, onContinue, onPress, onSu
2803
3281
  }, [reduced, popT, copyT]);
2804
3282
  const popScale = popT.interpolate({ inputRange: [0, 1], outputRange: [STATUS_POP_SCALE_FROM, 1] });
2805
3283
  const copyRise = copyT.interpolate({ inputRange: [0, 1], outputRange: [10, 0] });
2806
- const handleContinue = React18.useCallback(() => {
3284
+ const handleContinue = React19.useCallback(() => {
2807
3285
  if (!submitted) {
2808
3286
  setSubmitted(true);
2809
3287
  if (onContinue) onContinue();
@@ -2816,8 +3294,8 @@ var _StatusCard = ({ status, title, message, ctaLabel, onContinue, onPress, onSu
2816
3294
  {
2817
3295
  align: "center",
2818
3296
  footer: ctaLabel ? /* @__PURE__ */ jsxRuntime.jsx(Button, { title: ctaLabel, onPress: handleContinue, variant: "primary", disabled: submitted }) : void 0,
2819
- children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles14.center, { gap: t.spacing.sm }], children: [
2820
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles14.titleRow, { gap: t.spacing.sm }], children: [
3297
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles15.center, { gap: t.spacing.sm }], children: [
3298
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles15.titleRow, { gap: t.spacing.sm }], children: [
2821
3299
  /* @__PURE__ */ jsxRuntime.jsx(
2822
3300
  reactNative.Animated.Text,
2823
3301
  {
@@ -2861,14 +3339,14 @@ var _StatusCard = ({ status, title, message, ctaLabel, onContinue, onPress, onSu
2861
3339
  var StatusCard = {
2862
3340
  name: "StatusCard",
2863
3341
  description: "Use ONLY for real outcomes: booking confirmed, data saved, action completed. NEVER for 'you just viewed X' acknowledgments. Always include ctaLabel so the user can continue after seeing the status.",
2864
- component: React18__default.default.memo(_StatusCard),
2865
- propsSchema: schema5
3342
+ component: React19__default.default.memo(_StatusCard),
3343
+ propsSchema: schema6
2866
3344
  };
2867
- var styles14 = reactNative.StyleSheet.create({
3345
+ var styles15 = reactNative.StyleSheet.create({
2868
3346
  center: { width: "100%", alignItems: "center", justifyContent: "center" },
2869
3347
  titleRow: { flexDirection: "row", alignItems: "center", justifyContent: "center" }
2870
3348
  });
2871
- var schema6 = zod.z.object({
3349
+ var schema7 = zod.z.object({
2872
3350
  label: zod.z.string().describe("What is being counted, e.g. 'Number of days' or 'Travellers'"),
2873
3351
  min: zod.z.number().optional().describe("Minimum value, default 1"),
2874
3352
  max: zod.z.number().optional().describe("Maximum value, default 30"),
@@ -2889,22 +3367,22 @@ var _NumberStepperCard = ({
2889
3367
  onSubmit
2890
3368
  }) => {
2891
3369
  const t = useOnboardingTheme();
2892
- const styles17 = React18.useMemo(() => makeStyles4(t), [t]);
3370
+ const styles18 = React19.useMemo(() => makeStyles4(t), [t]);
2893
3371
  const safeMin = safeNum(min, 1);
2894
3372
  const safeMaxRaw = safeNum(max, 30);
2895
3373
  const safeMax = safeMaxRaw < safeMin ? safeMin : safeMaxRaw;
2896
3374
  const safeStepRaw = safeNum(step, 1);
2897
3375
  const safeStep = safeStepRaw > 0 ? safeStepRaw : 1;
2898
3376
  const safeDefault = safeNum(defaultValue, safeMin);
2899
- const [value, setValue] = React18.useState(Math.min(Math.max(safeDefault, safeMin), safeMax));
2900
- const [submitted, setSubmitted] = React18.useState(false);
2901
- const handleDecrement = React18.useCallback(() => {
3377
+ const [value, setValue] = React19.useState(Math.min(Math.max(safeDefault, safeMin), safeMax));
3378
+ const [submitted, setSubmitted] = React19.useState(false);
3379
+ const handleDecrement = React19.useCallback(() => {
2902
3380
  if (!submitted) setValue((v) => Math.max(v - safeStep, safeMin));
2903
3381
  }, [submitted, safeStep, safeMin]);
2904
- const handleIncrement = React18.useCallback(() => {
3382
+ const handleIncrement = React19.useCallback(() => {
2905
3383
  if (!submitted) setValue((v) => Math.min(v + safeStep, safeMax));
2906
3384
  }, [submitted, safeStep, safeMax]);
2907
- const handleSubmit = React18.useCallback(() => {
3385
+ const handleSubmit = React19.useCallback(() => {
2908
3386
  if (!submitted) {
2909
3387
  setSubmitted(true);
2910
3388
  onSubmit == null ? void 0 : onSubmit(value);
@@ -2913,7 +3391,7 @@ var _NumberStepperCard = ({
2913
3391
  const atMin = value <= safeMin;
2914
3392
  const atMax = value >= safeMax;
2915
3393
  const stepBtnStyle = (disabled) => [
2916
- styles17.stepBtn,
3394
+ styles18.stepBtn,
2917
3395
  { borderRadius: t.radius.full, backgroundColor: t.colors.background, borderColor: disabled ? t.colors.border : t.colors.primary }
2918
3396
  ];
2919
3397
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -2922,10 +3400,10 @@ var _NumberStepperCard = ({
2922
3400
  title: label,
2923
3401
  align: "center",
2924
3402
  footer: /* @__PURE__ */ jsxRuntime.jsx(Button, { title: submitLabel, onPress: handleSubmit, variant: "primary", disabled: submitted }),
2925
- children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles17.wrap, children: [
2926
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles17.stepper, { gap: t.spacing.lg }], children: [
3403
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles18.wrap, children: [
3404
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles18.stepper, { gap: t.spacing.lg }], children: [
2927
3405
  /* @__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" }) }),
2928
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles17.valueBox, children: [
3406
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles18.valueBox, children: [
2929
3407
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [headingStyle(t.fonts), { color: t.colors.primary }], children: String(value) }),
2930
3408
  unit ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [captionStyle(t.fonts), { color: t.colors.textMuted }], children: unit }) : null
2931
3409
  ] }),
@@ -2939,8 +3417,8 @@ var _NumberStepperCard = ({
2939
3417
  var NumberStepperCard = {
2940
3418
  name: "NumberStepperCard",
2941
3419
  description: "Use for any bounded numeric input: days, people, portions, budget tiers, ratings. Best for ranges 1\u201399. Set unit (e.g. 'days', 'people') for clarity.",
2942
- component: React18__default.default.memo(_NumberStepperCard),
2943
- propsSchema: schema6,
3420
+ component: React19__default.default.memo(_NumberStepperCard),
3421
+ propsSchema: schema7,
2944
3422
  defaultProps: { min: 1, max: 30, defaultValue: 1, step: 1, submitLabel: "Confirm" }
2945
3423
  };
2946
3424
  function makeStyles4(t) {
@@ -2951,9 +3429,9 @@ function makeStyles4(t) {
2951
3429
  valueBox: { alignItems: "center", minWidth: 80 }
2952
3430
  });
2953
3431
  }
2954
- var easeWire8 = reactNative.Easing.bezier(...WIRE_BEZIER);
2955
- var RISE_PX = 10;
2956
- var schema7 = zod.z.object({
3432
+ var easeWire9 = reactNative.Easing.bezier(...WIRE_BEZIER);
3433
+ var RISE_PX2 = 10;
3434
+ var schema8 = zod.z.object({
2957
3435
  title: zod.z.string().describe("Short, warm headline for the value/momentum moment"),
2958
3436
  body: zod.z.string().optional().describe("One or two supporting lines that reflect the user's answers back and build momentum"),
2959
3437
  items: zod.z.array(zod.z.string()).max(4).optional().describe(
@@ -2977,9 +3455,9 @@ var _WorkItem = ({
2977
3455
  reduced
2978
3456
  }) => {
2979
3457
  const t = useOnboardingTheme();
2980
- const rowT = React18.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
2981
- const checkT = React18.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
2982
- React18.useEffect(() => {
3458
+ const rowT = React19.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
3459
+ const checkT = React19.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
3460
+ React19.useEffect(() => {
2983
3461
  if (reduced) {
2984
3462
  rowT.setValue(1);
2985
3463
  checkT.setValue(1);
@@ -2990,7 +3468,7 @@ var _WorkItem = ({
2990
3468
  toValue: 1,
2991
3469
  duration: scaledMs(INTERSTITIAL_ITEM_MS),
2992
3470
  delay: scaledMs(interstitialItemDelay(index)),
2993
- easing: easeWire8,
3471
+ easing: easeWire9,
2994
3472
  useNativeDriver: true
2995
3473
  }),
2996
3474
  reactNative.Animated.spring(checkT, {
@@ -3004,7 +3482,7 @@ var _WorkItem = ({
3004
3482
  anims.forEach((a) => a.start());
3005
3483
  return () => anims.forEach((a) => a.stop());
3006
3484
  }, [reduced, index, rowT, checkT]);
3007
- const rise = rowT.interpolate({ inputRange: [0, 1], outputRange: [RISE_PX, 0] });
3485
+ const rise = rowT.interpolate({ inputRange: [0, 1], outputRange: [RISE_PX2, 0] });
3008
3486
  const checkScale = checkT.interpolate({
3009
3487
  inputRange: [0, 1],
3010
3488
  outputRange: [INTERSTITIAL_CHECK_SCALE_FROM, 1]
@@ -3012,20 +3490,20 @@ var _WorkItem = ({
3012
3490
  return /* @__PURE__ */ jsxRuntime.jsxs(
3013
3491
  reactNative.Animated.View,
3014
3492
  {
3015
- style: [styles15.itemRow, { gap: t.spacing.sm + t.spacing.xs, opacity: rowT, transform: [{ translateY: rise }] }],
3493
+ style: [styles16.itemRow, { gap: t.spacing.sm + t.spacing.xs, opacity: rowT, transform: [{ translateY: rise }] }],
3016
3494
  children: [
3017
3495
  /* @__PURE__ */ jsxRuntime.jsx(
3018
3496
  reactNative.View,
3019
3497
  {
3020
3498
  style: [
3021
- styles15.itemBadge,
3499
+ styles16.itemBadge,
3022
3500
  { backgroundColor: t.colors.primarySoft, borderRadius: t.radius.full }
3023
3501
  ],
3024
3502
  children: /* @__PURE__ */ jsxRuntime.jsx(
3025
3503
  reactNative.Animated.Text,
3026
3504
  {
3027
3505
  style: [
3028
- styles15.itemCheck,
3506
+ styles16.itemCheck,
3029
3507
  { color: t.colors.primary, opacity: checkT, transform: [{ scale: checkScale }] }
3030
3508
  ],
3031
3509
  children: "\u2713"
@@ -3033,12 +3511,12 @@ var _WorkItem = ({
3033
3511
  )
3034
3512
  }
3035
3513
  ),
3036
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), styles15.itemText, { color: t.colors.text }], children: text })
3514
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), styles16.itemText, { color: t.colors.text }], children: text })
3037
3515
  ]
3038
3516
  }
3039
3517
  );
3040
3518
  };
3041
- var WorkItem = React18__default.default.memo(_WorkItem);
3519
+ var WorkItem = React19__default.default.memo(_WorkItem);
3042
3520
  var _InterstitialCard = ({
3043
3521
  title,
3044
3522
  body,
@@ -3054,17 +3532,17 @@ var _InterstitialCard = ({
3054
3532
  const reduced = useReducedMotion();
3055
3533
  const node = useIllustration(illustration);
3056
3534
  const fallbackNode = useIllustration(illustrationFallback);
3057
- const [submitted, setSubmitted] = React18.useState(false);
3058
- const [imgFailed, setImgFailed] = React18.useState(false);
3059
- const [canAdvance, setCanAdvance] = React18.useState(false);
3060
- React18.useEffect(() => {
3535
+ const [submitted, setSubmitted] = React19.useState(false);
3536
+ const [imgFailed, setImgFailed] = React19.useState(false);
3537
+ const [canAdvance, setCanAdvance] = React19.useState(false);
3538
+ React19.useEffect(() => {
3061
3539
  const timer = setTimeout(() => setCanAdvance(true), MIN_DISPLAY_MS);
3062
3540
  return () => clearTimeout(timer);
3063
3541
  }, []);
3064
- const artT = React18.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
3065
- const titleT = React18.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
3066
- const bodyT = React18.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
3067
- React18.useEffect(() => {
3542
+ const artT = React19.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
3543
+ const titleT = React19.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
3544
+ const bodyT = React19.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
3545
+ React19.useEffect(() => {
3068
3546
  if (reduced) {
3069
3547
  artT.setValue(1);
3070
3548
  titleT.setValue(1);
@@ -3075,7 +3553,7 @@ var _InterstitialCard = ({
3075
3553
  toValue: 1,
3076
3554
  duration: scaledMs(INTERSTITIAL_HEAD_MS),
3077
3555
  delay,
3078
- easing: easeWire8,
3556
+ easing: easeWire9,
3079
3557
  useNativeDriver: true
3080
3558
  });
3081
3559
  const anims = [
@@ -3088,9 +3566,9 @@ var _InterstitialCard = ({
3088
3566
  }, [reduced, artT, titleT, bodyT]);
3089
3567
  const riseStyle = (value) => ({
3090
3568
  opacity: value,
3091
- transform: [{ translateY: value.interpolate({ inputRange: [0, 1], outputRange: [RISE_PX, 0] }) }]
3569
+ transform: [{ translateY: value.interpolate({ inputRange: [0, 1], outputRange: [RISE_PX2, 0] }) }]
3092
3570
  });
3093
- const handleContinue = React18.useCallback(() => {
3571
+ const handleContinue = React19.useCallback(() => {
3094
3572
  if (submitted || !canAdvance) return;
3095
3573
  setSubmitted(true);
3096
3574
  if (onSubmit) onSubmit("continue");
@@ -3111,18 +3589,18 @@ var _InterstitialCard = ({
3111
3589
  disabled: submitted || !canAdvance
3112
3590
  }
3113
3591
  ),
3114
- children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles15.center, { gap: t.spacing.md }], children: [
3115
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: [styles15.fullWidth, riseStyle(artT)], children: showImage ? /* @__PURE__ */ jsxRuntime.jsx(
3592
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles16.center, { gap: t.spacing.md }], children: [
3593
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: [styles16.fullWidth, riseStyle(artT)], children: showImage ? /* @__PURE__ */ jsxRuntime.jsx(
3116
3594
  reactNative.Image,
3117
3595
  {
3118
3596
  source: { uri: imageUrl },
3119
- style: styles15.image,
3597
+ style: styles16.image,
3120
3598
  resizeMode: "contain",
3121
3599
  onError: () => setImgFailed(true)
3122
3600
  }
3123
- ) : art ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles15.art, children: art }) : null }),
3601
+ ) : art ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles16.art, children: art }) : null }),
3124
3602
  /* @__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 }) }),
3125
- (items == null ? void 0 : items.length) ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles15.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
3603
+ (items == null ? void 0 : items.length) ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles16.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
3126
3604
  ] })
3127
3605
  }
3128
3606
  );
@@ -3130,11 +3608,11 @@ var _InterstitialCard = ({
3130
3608
  var InterstitialCard = {
3131
3609
  name: "InterstitialCard",
3132
3610
  description: "Use ONCE mid-flow (never first, never last) as a momentum/value screen between questions \u2014 an illustration + a warm line that reflects answers back, like Duolingo/Cal-AI. NOT a question; the user just taps Continue. Set `illustration` to an app-registered artwork name. Prefer `items` (2-3 short ticked lines, each reflecting a real answer) when you have the signal. Skip it if you don't have enough signal to make the line specific.",
3133
- component: React18__default.default.memo(_InterstitialCard),
3134
- propsSchema: schema7,
3611
+ component: React19__default.default.memo(_InterstitialCard),
3612
+ propsSchema: schema8,
3135
3613
  defaultProps: { ctaLabel: "Continue", illustrationFallback: "momentum" }
3136
3614
  };
3137
- var styles15 = reactNative.StyleSheet.create({
3615
+ var styles16 = reactNative.StyleSheet.create({
3138
3616
  center: { width: "100%", alignItems: "center", justifyContent: "center" },
3139
3617
  fullWidth: { width: "100%", alignItems: "center" },
3140
3618
  art: { alignItems: "center", justifyContent: "center" },
@@ -3454,15 +3932,6 @@ var resetAutoDeviceKeys = () => {
3454
3932
  (_a2 = registry.pending) == null ? void 0 : _a2.clear();
3455
3933
  };
3456
3934
 
3457
- // src/utils/warnInDev.ts
3458
- var warnInDev = (message) => {
3459
- if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
3460
- console.warn(message);
3461
- return true;
3462
- }
3463
- return false;
3464
- };
3465
-
3466
3935
  // src/analytics/currentSession.ts
3467
3936
  var CURRENT_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
3468
3937
  "@wireai/activation:currentSessionId"
@@ -3667,6 +4136,43 @@ var resolveUserContext = (ctx = {}, opts = {}) => {
3667
4136
  if (Object.keys(bucket).length > 0) result.userContext = bucket;
3668
4137
  return result;
3669
4138
  };
4139
+
4140
+ // src/permissions/permissionMemory.ts
4141
+ var permissionStorageKey = (appId) => `wireai:permissions:${appId}`;
4142
+ var readSettledPermissions = (raw, sessionId) => {
4143
+ if (!raw || !sessionId) return [];
4144
+ try {
4145
+ const parsed = JSON.parse(raw);
4146
+ if (typeof parsed !== "object" || parsed === null) return [];
4147
+ const record = parsed;
4148
+ if (record.sessionId !== sessionId || !Array.isArray(record.ids)) return [];
4149
+ return record.ids.filter((id) => typeof id === "string" && id.length > 0);
4150
+ } catch {
4151
+ return [];
4152
+ }
4153
+ };
4154
+ var loadSettledPermissions = async (storage, key, sessionId) => {
4155
+ try {
4156
+ return readSettledPermissions(await withTimeout(storage.getItem(key), READ_TIMEOUT_MS), sessionId);
4157
+ } catch {
4158
+ return [];
4159
+ }
4160
+ };
4161
+ var saveSettledPermissions = (storage, key, sessionId, ids) => {
4162
+ try {
4163
+ const record = { sessionId, ids: [...ids] };
4164
+ void storage.setItem(key, JSON.stringify(record)).catch(() => {
4165
+ });
4166
+ } catch {
4167
+ }
4168
+ };
4169
+ var clearSettledPermissions = (storage, key) => {
4170
+ try {
4171
+ void storage.removeItem(key).catch(() => {
4172
+ });
4173
+ } catch {
4174
+ }
4175
+ };
3670
4176
  var AUTO_JOIN_HYDRATION_TIMEOUT_MS = 1500;
3671
4177
  var WireOnboarding = ({
3672
4178
  config,
@@ -3691,20 +4197,21 @@ var WireOnboarding = ({
3691
4197
  retainSessionOnComplete,
3692
4198
  userContext,
3693
4199
  userId,
3694
- autoJoinKey = true
4200
+ autoJoinKey = true,
4201
+ permissionScreens
3695
4202
  }) => {
3696
- var _a2, _b, _c, _d, _e;
3697
- const boundUserId = React18.useMemo(() => sanitizeUserId(userId), [userId]);
3698
- const device = React18.useMemo(() => {
4203
+ var _a2, _b, _c, _d, _e, _f;
4204
+ const boundUserId = React19.useMemo(() => sanitizeUserId(userId), [userId]);
4205
+ const device = React19.useMemo(() => {
3699
4206
  const collected = collectDeviceContext();
3700
4207
  return config.appVersion ? { ...collected, appVersion: config.appVersion } : collected;
3701
4208
  }, [config.appVersion]);
3702
4209
  const userContextKey = userContext ? JSON.stringify(userContext) : "";
3703
- const userContextStable = React18.useMemo(
4210
+ const userContextStable = React19.useMemo(
3704
4211
  () => userContextKey ? JSON.parse(userContextKey) : void 0,
3705
4212
  [userContextKey]
3706
4213
  );
3707
- const hostJoinIdentity = React18.useMemo(
4214
+ const hostJoinIdentity = React19.useMemo(
3708
4215
  () => resolveIdentity({
3709
4216
  value: userContextStable == null ? void 0 : userContextStable.device_key,
3710
4217
  space: "device",
@@ -3716,8 +4223,8 @@ var WireOnboarding = ({
3716
4223
  const missingJoinKey = !hostJoinIdentity;
3717
4224
  const autoJoinPossible = Boolean(storage) && autoJoinKey !== false;
3718
4225
  const wantsAutoJoin = missingJoinKey && autoJoinPossible;
3719
- const [autoJoinOutcome, setAutoJoinOutcome] = React18.useState(void 0);
3720
- React18.useEffect(() => {
4226
+ const [autoJoinOutcome, setAutoJoinOutcome] = React19.useState(void 0);
4227
+ React19.useEffect(() => {
3721
4228
  if (!wantsAutoJoin || autoJoinOutcome !== void 0 || !storage) return;
3722
4229
  let cancelled = false;
3723
4230
  const timer = setTimeout(() => {
@@ -3737,22 +4244,22 @@ var WireOnboarding = ({
3737
4244
  }, [wantsAutoJoin, autoJoinOutcome, config.appId, storage]);
3738
4245
  const injectedJoinKey = wantsAutoJoin ? (_a2 = autoJoinOutcome == null ? void 0 : autoJoinOutcome.key) != null ? _a2 : void 0 : void 0;
3739
4246
  const autoJoinPending = wantsAutoJoin && autoJoinOutcome === void 0;
3740
- const effectiveUserContext = React18.useMemo(
4247
+ const effectiveUserContext = React19.useMemo(
3741
4248
  () => injectedJoinKey ? { ...userContextStable, ...activationJoinContext(injectedJoinKey) } : userContextStable,
3742
4249
  [userContextStable, injectedJoinKey]
3743
4250
  );
3744
- const frozenWireContextRef = React18.useRef(null);
4251
+ const frozenWireContextRef = React19.useRef(null);
3745
4252
  if (!autoJoinPending && frozenWireContextRef.current === null) {
3746
4253
  frozenWireContextRef.current = effectiveUserContext ? JSON.stringify(effectiveUserContext) : "";
3747
4254
  }
3748
4255
  const startupUserContextKey = (_b = frozenWireContextRef.current) != null ? _b : "";
3749
- const startupUserContext = React18.useMemo(
4256
+ const startupUserContext = React19.useMemo(
3750
4257
  () => startupUserContextKey ? JSON.parse(startupUserContextKey) : void 0,
3751
4258
  [startupUserContextKey]
3752
4259
  );
3753
4260
  const warnMissingJoinKey = missingJoinKey && !injectedJoinKey && !autoJoinPending;
3754
4261
  const autoJoinReason = autoJoinKey === false ? "you passed autoJoinKey={false}." : !storage ? "it got no `storage` prop, and without persistence its key would be different on every launch, which corrupts min_sessions instead of merely leaving the join empty." : (autoJoinOutcome == null ? void 0 : autoJoinOutcome.key) === null && autoJoinOutcome.reason === "not-durable" ? "your `storage` adapter answered but could not persist the key (the read or the write failed), so the key would be different on every launch, which corrupts min_sessions instead of merely leaving the join empty. Check the adapter you passed as `storage`." : "your `storage` adapter did not answer in time.";
3755
- React18.useEffect(() => {
4262
+ React19.useEffect(() => {
3756
4263
  if (!warnMissingJoinKey) return;
3757
4264
  warnInDev(
3758
4265
  "[wireai] <WireOnboarding> got no user_context.device_key, so this onboarding session can never be joined to the app's later events and the `activated` funnel will read zero. Pass userContext={activationJoinContext(deviceKey)} \u2014 and if your app owns no device id, userContext={activationJoinContext(resolveAutoDeviceKey({ appId, storage }))} returns the SAME id the analytics side stamps. Never hand-write userContext={{ deviceKey }}. The kit did NOT auto-inject its own key here because " + autoJoinReason
@@ -3760,28 +4267,29 @@ var WireOnboarding = ({
3760
4267
  }, [warnMissingJoinKey, autoJoinReason]);
3761
4268
  const hostKeyElsewhere = missingJoinKey ? hostIdentity("device", config.appId) : void 0;
3762
4269
  const warnHostKeyElsewhere = Boolean(hostKeyElsewhere) && Boolean(injectedJoinKey);
3763
- React18.useEffect(() => {
4270
+ React19.useEffect(() => {
3764
4271
  if (!warnHostKeyElsewhere) return;
3765
4272
  warnInDev(
3766
4273
  `[wireai] <WireOnboarding> got no user_context.device_key, so the kit injected its own \u2014 but another surface in this app was constructed WITH a host-supplied device key ("${hostKeyElsewhere}"). Your app events carry that id and this onboarding session carries the kit's, so the two land in disjoint id spaces and the \`activated\` funnel still reads zero. Pass the SAME id here: userContext={activationJoinContext(deviceKey)}.`
3767
4274
  );
3768
4275
  }, [warnHostKeyElsewhere, hostKeyElsewhere]);
3769
4276
  const misspelledJoinKey = missingJoinKey && typeof (userContextStable == null ? void 0 : userContextStable.deviceKey) === "string" && Boolean(userContextStable.deviceKey.trim());
3770
- React18.useEffect(() => {
4277
+ React19.useEffect(() => {
3771
4278
  if (!misspelledJoinKey || !injectedJoinKey) return;
3772
4279
  warnInDev(
3773
4280
  "[wireai] <WireOnboarding> got userContext={{ deviceKey }}, which is NOT the wire key \u2014 the server's device lookup reads `device_key`. The kit auto-injected its own device_key so this session is at least self-consistent, but your app's own events carry YOUR id, so the two still will not join and the `activated` funnel stays zero. Pass userContext={activationJoinContext(deviceKey)} instead."
3774
4281
  );
3775
4282
  }, [misspelledJoinKey, injectedJoinKey]);
3776
- const clientContext = React18.useMemo(
4283
+ const clientContext = React19.useMemo(
3777
4284
  () => ({ device, userContext: effectiveUserContext, userId: boundUserId }),
3778
4285
  [device, effectiveUserContext, boundUserId]
3779
4286
  );
3780
- const [session, setSession] = React18.useState(
4287
+ const [session, setSession] = React19.useState(
3781
4288
  () => storage ? null : { id: makeSessionId(), resumed: false }
3782
4289
  );
3783
4290
  const storageKey = persistKey != null ? persistKey : sessionStorageKey(config.appId);
3784
- React18.useEffect(() => {
4291
+ const permissionsKey = permissionStorageKey(config.appId);
4292
+ React19.useEffect(() => {
3785
4293
  if (!storage || session) return;
3786
4294
  let cancelled = false;
3787
4295
  void loadPersistedSession(storage, storageKey, sessionTtlMs).then((loaded) => {
@@ -3791,20 +4299,51 @@ var WireOnboarding = ({
3791
4299
  cancelled = true;
3792
4300
  };
3793
4301
  }, [storage, storageKey, sessionTtlMs, session]);
3794
- const handleComplete = React18.useCallback(
4302
+ const handleComplete = React19.useCallback(
3795
4303
  (result) => {
3796
- if (storage && shouldClearOnComplete(retainSessionOnComplete)) clearPersistedSession(storage, storageKey);
4304
+ if (storage && shouldClearOnComplete(retainSessionOnComplete)) {
4305
+ clearPersistedSession(storage, storageKey);
4306
+ clearSettledPermissions(storage, permissionsKey);
4307
+ }
3797
4308
  onComplete(result);
3798
4309
  },
3799
- [storage, storageKey, retainSessionOnComplete, onComplete]
4310
+ [storage, storageKey, permissionsKey, retainSessionOnComplete, onComplete]
3800
4311
  );
3801
4312
  const sessionId = (_c = session == null ? void 0 : session.id) != null ? _c : "";
3802
- const startupUserIdRef = React18.useRef(boundUserId);
4313
+ const wantsPermissionMemory = Boolean(storage) && ((_d = permissionScreens == null ? void 0 : permissionScreens.length) != null ? _d : 0) > 0;
4314
+ const [settledPermissions, setSettledPermissions] = React19.useState(void 0);
4315
+ React19.useEffect(() => {
4316
+ if (!wantsPermissionMemory || !storage || !sessionId || settledPermissions !== void 0) return;
4317
+ let cancelled = false;
4318
+ void loadSettledPermissions(storage, permissionsKey, sessionId).then((ids) => {
4319
+ if (!cancelled) setSettledPermissions(ids);
4320
+ });
4321
+ return () => {
4322
+ cancelled = true;
4323
+ };
4324
+ }, [wantsPermissionMemory, storage, permissionsKey, sessionId, settledPermissions]);
4325
+ const settledPermissionsRef = React19.useRef(void 0);
4326
+ settledPermissionsRef.current = settledPermissions;
4327
+ const handlePermissionSettled = React19.useCallback(
4328
+ (id) => {
4329
+ var _a3;
4330
+ const current = (_a3 = settledPermissionsRef.current) != null ? _a3 : [];
4331
+ if (current.includes(id)) return;
4332
+ const next = [...current, id];
4333
+ settledPermissionsRef.current = next;
4334
+ setSettledPermissions(next);
4335
+ if (storage && wantsPermissionMemory && sessionId) {
4336
+ saveSettledPermissions(storage, permissionsKey, sessionId, next);
4337
+ }
4338
+ },
4339
+ [storage, wantsPermissionMemory, permissionsKey, sessionId]
4340
+ );
4341
+ const startupUserIdRef = React19.useRef(boundUserId);
3803
4342
  const metadataKey = config.metadata ? JSON.stringify(config.metadata) : "";
3804
- const metadataStable = React18.useMemo(() => metadataKey ? JSON.parse(metadataKey) : void 0, [metadataKey]);
4343
+ const metadataStable = React19.useMemo(() => metadataKey ? JSON.parse(metadataKey) : void 0, [metadataKey]);
3805
4344
  const componentNamesKey = (components != null ? components : onboardingComponents).map((c) => c.name).join(",");
3806
- const componentsStable = React18.useMemo(() => components != null ? components : onboardingComponents, [componentNamesKey]);
3807
- const llm = React18.useMemo(() => {
4345
+ const componentsStable = React19.useMemo(() => components != null ? components : onboardingComponents, [componentNamesKey]);
4346
+ const llm = React19.useMemo(() => {
3808
4347
  const baseUrl = config.serverUrl.replace(/\/$/, "");
3809
4348
  return {
3810
4349
  provider: "a2a",
@@ -3834,13 +4373,13 @@ var WireOnboarding = ({
3834
4373
  timeoutMs: 6e4
3835
4374
  };
3836
4375
  }, [config.serverUrl, config.appId, config.apiKey, metadataStable, sessionId, componentsStable, device, startupUserContext]);
3837
- const reportTarget = React18.useMemo(
4376
+ const reportTarget = React19.useMemo(
3838
4377
  () => ({ serverUrl: config.serverUrl, apiKey: config.apiKey }),
3839
4378
  [config.serverUrl, config.apiKey]
3840
4379
  );
3841
- const identifiedRef = React18.useRef(void 0);
3842
- const identifySeededRef = React18.useRef(false);
3843
- React18.useEffect(() => {
4380
+ const identifiedRef = React19.useRef(void 0);
4381
+ const identifySeededRef = React19.useRef(false);
4382
+ React19.useEffect(() => {
3844
4383
  if (!session || !sessionId) return;
3845
4384
  if (!identifySeededRef.current) {
3846
4385
  identifySeededRef.current = true;
@@ -3859,8 +4398,8 @@ var WireOnboarding = ({
3859
4398
  return /* @__PURE__ */ jsxRuntime.jsx(OnboardingThemeProvider, { theme, children: /* @__PURE__ */ jsxRuntime.jsx(
3860
4399
  LoadingScreen,
3861
4400
  {
3862
- title: (_d = copy == null ? void 0 : copy.restoringTitle) != null ? _d : DEFAULT_COPY.restoringTitle,
3863
- hint: (_e = copy == null ? void 0 : copy.restoringHint) != null ? _e : DEFAULT_COPY.restoringHint
4401
+ title: (_e = copy == null ? void 0 : copy.restoringTitle) != null ? _e : DEFAULT_COPY.restoringTitle,
4402
+ hint: (_f = copy == null ? void 0 : copy.restoringHint) != null ? _f : DEFAULT_COPY.restoringHint
3864
4403
  }
3865
4404
  ) });
3866
4405
  }
@@ -3881,7 +4420,11 @@ var WireOnboarding = ({
3881
4420
  reportTarget,
3882
4421
  sessionId,
3883
4422
  resumed: session.resumed,
3884
- clientContext
4423
+ clientContext,
4424
+ permissionScreens,
4425
+ settledPermissions,
4426
+ permissionsPending: wantsPermissionMemory && settledPermissions === void 0,
4427
+ onPermissionSettled: handlePermissionSettled
3885
4428
  }
3886
4429
  ) }) }) }) });
3887
4430
  };
@@ -3895,16 +4438,16 @@ function DemoOnboarding({
3895
4438
  onComplete,
3896
4439
  renderTrigger
3897
4440
  }) {
3898
- const [open, setOpen] = React18.useState(false);
3899
- const [runId, setRunId] = React18.useState(0);
3900
- const t = React18.useMemo(() => mergeTheme(theme != null ? theme : {}), [theme]);
3901
- const styles17 = React18.useMemo(() => makeStyles5(t), [t]);
3902
- const openDemo = React18.useCallback(() => {
4441
+ const [open, setOpen] = React19.useState(false);
4442
+ const [runId, setRunId] = React19.useState(0);
4443
+ const t = React19.useMemo(() => mergeTheme(theme != null ? theme : {}), [theme]);
4444
+ const styles18 = React19.useMemo(() => makeStyles5(t), [t]);
4445
+ const openDemo = React19.useCallback(() => {
3903
4446
  setRunId((n) => n + 1);
3904
4447
  setOpen(true);
3905
4448
  }, []);
3906
- const close = React18.useCallback(() => setOpen(false), []);
3907
- const finish = React18.useCallback(
4449
+ const close = React19.useCallback(() => setOpen(false), []);
4450
+ const finish = React19.useCallback(
3908
4451
  (result) => {
3909
4452
  onComplete == null ? void 0 : onComplete(result);
3910
4453
  setOpen(false);
@@ -3912,7 +4455,7 @@ function DemoOnboarding({
3912
4455
  [onComplete]
3913
4456
  );
3914
4457
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3915
- renderTrigger ? renderTrigger(openDemo) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: openDemo, style: styles17.trigger, accessibilityRole: "button", children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles17.triggerText, children: label }) }),
4458
+ renderTrigger ? renderTrigger(openDemo) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: openDemo, style: styles18.trigger, accessibilityRole: "button", children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles18.triggerText, children: label }) }),
3916
4459
  /* @__PURE__ */ jsxRuntime.jsx(
3917
4460
  reactNative.Modal,
3918
4461
  {
@@ -3920,7 +4463,7 @@ function DemoOnboarding({
3920
4463
  animationType: "slide",
3921
4464
  presentationStyle: "fullScreen",
3922
4465
  onRequestClose: close,
3923
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles17.modal, children: config ? /* @__PURE__ */ jsxRuntime.jsx(
4466
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles18.modal, children: config ? /* @__PURE__ */ jsxRuntime.jsx(
3924
4467
  WireOnboarding,
3925
4468
  {
3926
4469
  config,
@@ -3933,10 +4476,10 @@ function DemoOnboarding({
3933
4476
  onError: close
3934
4477
  },
3935
4478
  runId
3936
- ) : /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles17.notice, children: [
3937
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles17.noticeTitle, children: "Wire AI not configured" }),
3938
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles17.noticeBody, children: "Set the Wire AI key + server URL (e.g. via wireConfigFromEnv) to demo the AI onboarding here." }),
3939
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: close, style: styles17.trigger, accessibilityRole: "button", children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles17.triggerText, children: "Close" }) })
4479
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles18.notice, children: [
4480
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles18.noticeTitle, children: "Wire AI not configured" }),
4481
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles18.noticeBody, children: "Set the Wire AI key + server URL (e.g. via wireConfigFromEnv) to demo the AI onboarding here." }),
4482
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: close, style: styles18.trigger, accessibilityRole: "button", children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles18.triggerText, children: "Close" }) })
3940
4483
  ] }) })
3941
4484
  }
3942
4485
  )
@@ -4022,13 +4565,13 @@ var CENTERED_MODAL_ANIM = {
4022
4565
  /** Peak dim of the backdrop at progress 1. */
4023
4566
  backdropOpacity: 0.55
4024
4567
  };
4025
- var _CenteredModal = React18.forwardRef(
4568
+ var _CenteredModal = React19.forwardRef(
4026
4569
  ({ theme, dismissible = true, onDismiss, children }, ref) => {
4027
- const progress = React18.useRef(new reactNative.Animated.Value(0)).current;
4028
- const [open, setOpen] = React18.useState(true);
4029
- const closingRef = React18.useRef(false);
4570
+ const progress = React19.useRef(new reactNative.Animated.Value(0)).current;
4571
+ const [open, setOpen] = React19.useState(true);
4572
+ const closingRef = React19.useRef(false);
4030
4573
  const reduced = useReducedMotion();
4031
- React18.useEffect(() => {
4574
+ React19.useEffect(() => {
4032
4575
  const anim = reduced ? reactNative.Animated.timing(progress, {
4033
4576
  toValue: 1,
4034
4577
  duration: REDUCED_FADE_MS,
@@ -4042,7 +4585,7 @@ var _CenteredModal = React18.forwardRef(
4042
4585
  anim.start();
4043
4586
  return () => anim.stop();
4044
4587
  }, [progress, reduced]);
4045
- const close = React18.useCallback(() => {
4588
+ const close = React19.useCallback(() => {
4046
4589
  if (closingRef.current) return;
4047
4590
  closingRef.current = true;
4048
4591
  reactNative.Animated.timing(progress, {
@@ -4055,15 +4598,15 @@ var _CenteredModal = React18.forwardRef(
4055
4598
  onDismiss();
4056
4599
  });
4057
4600
  }, [progress, onDismiss, reduced]);
4058
- React18.useImperativeHandle(ref, () => ({ close }), [close]);
4059
- React18.useEffect(() => {
4601
+ React19.useImperativeHandle(ref, () => ({ close }), [close]);
4602
+ React19.useEffect(() => {
4060
4603
  const sub = reactNative.BackHandler.addEventListener("hardwareBackPress", () => {
4061
4604
  if (dismissible) close();
4062
4605
  return true;
4063
4606
  });
4064
4607
  return () => sub.remove();
4065
4608
  }, [dismissible, close]);
4066
- const onBackdrop = React18.useCallback(() => {
4609
+ const onBackdrop = React19.useCallback(() => {
4067
4610
  if (dismissible) close();
4068
4611
  }, [dismissible, close]);
4069
4612
  const backdropOpacity = progress.interpolate({
@@ -4086,25 +4629,25 @@ var _CenteredModal = React18.forwardRef(
4086
4629
  /* @__PURE__ */ jsxRuntime.jsx(
4087
4630
  reactNative.Animated.View,
4088
4631
  {
4089
- style: [styles16.backdrop, { opacity: backdropOpacity }],
4632
+ style: [styles17.backdrop, { opacity: backdropOpacity }],
4090
4633
  pointerEvents: "none"
4091
4634
  }
4092
4635
  ),
4093
4636
  /* @__PURE__ */ jsxRuntime.jsx(
4094
4637
  reactNative.Pressable,
4095
4638
  {
4096
- style: styles16.fill,
4639
+ style: styles17.fill,
4097
4640
  onPress: onBackdrop,
4098
4641
  accessibilityRole: "button",
4099
4642
  accessibilityLabel: "Dismiss",
4100
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNativeSafeAreaContext.SafeAreaView, { style: styles16.center, pointerEvents: "box-none", children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => {
4101
- }, style: styles16.cardWrap, children: /* @__PURE__ */ jsxRuntime.jsx(
4643
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNativeSafeAreaContext.SafeAreaView, { style: styles17.center, pointerEvents: "box-none", children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => {
4644
+ }, style: styles17.cardWrap, children: /* @__PURE__ */ jsxRuntime.jsx(
4102
4645
  reactNative.Animated.View,
4103
4646
  {
4104
4647
  accessibilityViewIsModal: true,
4105
4648
  accessibilityRole: "alert",
4106
4649
  style: [
4107
- styles16.card,
4650
+ styles17.card,
4108
4651
  {
4109
4652
  backgroundColor: theme.colors.surface,
4110
4653
  borderRadius: theme.radius.lg,
@@ -4125,8 +4668,8 @@ var _CenteredModal = React18.forwardRef(
4125
4668
  }
4126
4669
  );
4127
4670
  _CenteredModal.displayName = "CenteredModal";
4128
- var CenteredModal = React18__default.default.memo(_CenteredModal);
4129
- var styles16 = reactNative.StyleSheet.create({
4671
+ var CenteredModal = React19__default.default.memo(_CenteredModal);
4672
+ var styles17 = reactNative.StyleSheet.create({
4130
4673
  fill: { flex: 1 },
4131
4674
  backdrop: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "#000" },
4132
4675
  center: { flex: 1, alignItems: "center", justifyContent: "center", padding: 24 },
@@ -4255,16 +4798,16 @@ var fetchWireFeatures = async (config) => {
4255
4798
  }
4256
4799
  };
4257
4800
  var useStableValue = (value, isEqual) => {
4258
- const ref = React18.useRef(value);
4801
+ const ref = React19.useRef(value);
4259
4802
  if (!isEqual(ref.current, value)) ref.current = value;
4260
4803
  return ref.current;
4261
4804
  };
4262
4805
  var sameFetchKey = (a, b) => (a == null ? void 0 : a.serverUrl) === (b == null ? void 0 : b.serverUrl) && (a == null ? void 0 : a.apiKey) === (b == null ? void 0 : b.apiKey) && (a == null ? void 0 : a.appId) === (b == null ? void 0 : b.appId);
4263
4806
  var useWireFeatures = (config) => {
4264
4807
  const stableConfig = useStableValue(config, sameFetchKey);
4265
- const [flags, setFlags] = React18.useState(defaultWireFeatures);
4808
+ const [flags, setFlags] = React19.useState(defaultWireFeatures);
4266
4809
  const canFetch = !!(stableConfig == null ? void 0 : stableConfig.serverUrl) && !!(stableConfig == null ? void 0 : stableConfig.apiKey);
4267
- React18.useEffect(() => {
4810
+ React19.useEffect(() => {
4268
4811
  if (!canFetch) {
4269
4812
  setFlags((prev) => featuresEqual(prev, defaultWireFeatures) ? prev : defaultWireFeatures);
4270
4813
  return;
@@ -4283,7 +4826,7 @@ var useWireFeatures = (config) => {
4283
4826
  var CONTEXT_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.context");
4284
4827
  var globalObj = global;
4285
4828
  if (!globalObj[CONTEXT_SYMBOL]) {
4286
- globalObj[CONTEXT_SYMBOL] = React18.createContext(null);
4829
+ globalObj[CONTEXT_SYMBOL] = React19.createContext(null);
4287
4830
  }
4288
4831
  var WireFeaturesContext = globalObj[CONTEXT_SYMBOL];
4289
4832
  var WireFeaturesProvider = ({
@@ -4293,11 +4836,11 @@ var WireFeaturesProvider = ({
4293
4836
  }) => {
4294
4837
  const fetched = useWireFeatures(flags ? void 0 : config);
4295
4838
  const value = flags != null ? flags : fetched;
4296
- const stable = React18.useMemo(() => value, [value]);
4839
+ const stable = React19.useMemo(() => value, [value]);
4297
4840
  return /* @__PURE__ */ jsxRuntime.jsx(WireFeaturesContext.Provider, { value: stable, children });
4298
4841
  };
4299
4842
  WireFeaturesProvider.displayName = "WireFeaturesProvider";
4300
- var useWireFeaturesContext = () => React18.useContext(WireFeaturesContext);
4843
+ var useWireFeaturesContext = () => React19.useContext(WireFeaturesContext);
4301
4844
  var useResolvedFeatures = (options) => {
4302
4845
  var _a2, _b, _c;
4303
4846
  const ctx = useWireFeaturesContext();
@@ -4377,6 +4920,11 @@ var toAnalyticsEvent = (event) => {
4377
4920
  };
4378
4921
  case "fallback":
4379
4922
  return { name: WIRE_ONBOARDING_EVENTS.fallback, params: { reason: event.reason } };
4923
+ case "permission":
4924
+ return {
4925
+ name: permissionEventName(event.stage),
4926
+ params: permissionEventProps(event.permission, event.status)
4927
+ };
4380
4928
  default: {
4381
4929
  const _exhaustive = event;
4382
4930
  return _exhaustive;
@@ -4486,15 +5034,15 @@ var createWireActivation = (config) => {
4486
5034
  getRevalidationVersion: getActivationRevalidationVersion
4487
5035
  };
4488
5036
  };
4489
- var useActivationRevalidation = () => React18.useSyncExternalStore(
5037
+ var useActivationRevalidation = () => React19.useSyncExternalStore(
4490
5038
  subscribeActivationRevalidation,
4491
5039
  getActivationRevalidationVersion,
4492
5040
  getActivationRevalidationVersion
4493
5041
  );
4494
5042
  var useWireActivation = (config) => {
4495
5043
  var _a2;
4496
- const ref = React18.useRef(void 0);
4497
- const prevKeys = React18.useRef("");
5044
+ const ref = React19.useRef(void 0);
5045
+ const prevKeys = React19.useRef("");
4498
5046
  const currentKeys = [
4499
5047
  config.serverUrl,
4500
5048
  config.apiKey,
@@ -4719,9 +5267,9 @@ var reportSessionStart = (opts) => {
4719
5267
  };
4720
5268
  var BACKGROUND_SESSION_MS = 30 * 60 * 1e3;
4721
5269
  var useSessionStart = (config, options = {}) => {
4722
- const latest = React18.useRef({ config, options });
5270
+ const latest = React19.useRef({ config, options });
4723
5271
  latest.current = { config, options };
4724
- React18.useEffect(() => {
5272
+ React19.useEffect(() => {
4725
5273
  const resolveDeviceKey = (cfg, opts) => {
4726
5274
  const host = typeof opts.deviceKey === "string" && opts.deviceKey.trim() ? opts.deviceKey : void 0;
4727
5275
  if (host) return host;
@@ -5095,10 +5643,10 @@ var createEventQueue = (options) => {
5095
5643
 
5096
5644
  // src/session-analytics/useLifecycleEvents.ts
5097
5645
  var useLifecycleEvents = (config, options = {}) => {
5098
- const latest = React18.useRef({ config, options });
5646
+ const latest = React19.useRef({ config, options });
5099
5647
  latest.current = { config, options };
5100
- const queueRef = React18.useRef(void 0);
5101
- React18.useEffect(() => {
5648
+ const queueRef = React19.useRef(void 0);
5649
+ React19.useEffect(() => {
5102
5650
  const resolveSink = () => {
5103
5651
  var _a2, _b;
5104
5652
  const { config: cfg, options: opts } = latest.current;
@@ -5213,22 +5761,29 @@ exports.CenteredModal = CenteredModal;
5213
5761
  exports.ChipSelectCard = ChipSelectCard;
5214
5762
  exports.CompletionView = CompletionView;
5215
5763
  exports.DEFAULT_FEATURES_TTL_MS = DEFAULT_FEATURES_TTL_MS;
5764
+ exports.DEFAULT_PERMISSION_COPY = DEFAULT_PERMISSION_COPY;
5765
+ exports.DEFAULT_PERMISSION_PLACEMENT = DEFAULT_PERMISSION_PLACEMENT;
5216
5766
  exports.DEFAULT_SESSION_TTL_MS = DEFAULT_SESSION_TTL_MS;
5217
5767
  exports.DemoOnboarding = DemoOnboarding;
5218
5768
  exports.EXTRA_KEY_PREFIX = EXTRA_KEY_PREFIX;
5219
5769
  exports.ErrorBlock = ErrorBlock;
5220
5770
  exports.FIRST_OPEN_EVENT = FIRST_OPEN_EVENT;
5771
+ exports.GENERIC_PERMISSION_COPY = GENERIC_PERMISSION_COPY;
5221
5772
  exports.IconRegistryProvider = IconRegistryProvider;
5222
5773
  exports.IllustrationProvider = IllustrationProvider;
5223
5774
  exports.InterstitialCard = InterstitialCard;
5224
5775
  exports.LoadingBlock = LoadingBlock;
5225
5776
  exports.LoadingScreen = LoadingScreen;
5777
+ exports.NOTIFICATIONS_PERMISSION_COPY = NOTIFICATIONS_PERMISSION_COPY;
5226
5778
  exports.NumberStepperCard = NumberStepperCard;
5227
5779
  exports.OnboardingButton = Button;
5228
5780
  exports.OnboardingFlow = OnboardingFlow;
5229
5781
  exports.OnboardingScaffold = OnboardingScaffold;
5230
5782
  exports.OnboardingThemeProvider = OnboardingThemeProvider;
5783
+ exports.PERMISSION_CARD_NAME = PERMISSION_CARD_NAME;
5231
5784
  exports.PLAN_TIER_CONTEXT_KEY = PLAN_TIER_CONTEXT_KEY;
5785
+ exports.PermissionCard = PermissionCard;
5786
+ exports.PermissionCardView = PermissionCardView;
5232
5787
  exports.SESSION_STARTED_EVENT = SESSION_STARTED_EVENT;
5233
5788
  exports.SelectionCard = SelectionCard;
5234
5789
  exports.StatusCard = StatusCard;
@@ -5239,6 +5794,7 @@ exports.WIRE_ENV_VARS = WIRE_ENV_VARS;
5239
5794
  exports.WIRE_ICON_GLYPHS = WIRE_ICON_GLYPHS;
5240
5795
  exports.WIRE_ICON_NAMES = WIRE_ICON_NAMES;
5241
5796
  exports.WIRE_ONBOARDING_EVENTS = WIRE_ONBOARDING_EVENTS;
5797
+ exports.WIRE_PERMISSION_EVENTS = WIRE_PERMISSION_EVENTS;
5242
5798
  exports.WIRE_PURCHASE_EVENTS = WIRE_PURCHASE_EVENTS;
5243
5799
  exports.WireFeaturesProvider = WireFeaturesProvider;
5244
5800
  exports.WireIcon = WireIcon;
@@ -5250,6 +5806,7 @@ exports.attributionMetadata = attributionMetadata;
5250
5806
  exports.bumpActivationRevalidation = bumpActivationRevalidation;
5251
5807
  exports.clearPersistedSession = clearPersistedSession;
5252
5808
  exports.clearPiiFromContext = clearPiiFromContext;
5809
+ exports.clearSettledPermissions = clearSettledPermissions;
5253
5810
  exports.clearUserContext = clearUserContext;
5254
5811
  exports.collectDeviceContext = collectDeviceContext;
5255
5812
  exports.createRevenueCatBridge = createRevenueCatBridge;
@@ -5278,9 +5835,11 @@ exports.hydrateDeviceIdentity = hydrateDeviceIdentity;
5278
5835
  exports.identifyOnboarding = identifyOnboarding;
5279
5836
  exports.isFeaturesFresh = isFeaturesFresh;
5280
5837
  exports.isOnboardingEnabled = isOnboardingEnabled;
5838
+ exports.isPermissionDue = isPermissionDue;
5281
5839
  exports.isUserCancelled = isUserCancelled;
5282
5840
  exports.isWireScalar = isWireScalar;
5283
5841
  exports.loadPersistedSession = loadPersistedSession;
5842
+ exports.loadSettledPermissions = loadSettledPermissions;
5284
5843
  exports.looksLikeEmail = looksLikeEmail;
5285
5844
  exports.lookupIconGlyph = lookupIconGlyph;
5286
5845
  exports.makeSessionId = makeSessionId;
@@ -5288,11 +5847,18 @@ exports.mergeTheme = mergeTheme;
5288
5847
  exports.mintDeviceId = mintDeviceId;
5289
5848
  exports.motionSpec = motionSpec_exports;
5290
5849
  exports.namespaceExtra = namespaceExtra;
5850
+ exports.normalizeAfterCard = normalizeAfterCard;
5851
+ exports.normalizePermissionStatus = normalizePermissionStatus;
5291
5852
  exports.onboardingComponents = onboardingComponents;
5292
5853
  exports.parseWireFeatures = parseWireFeatures;
5293
5854
  exports.peekPersistedSession = peekPersistedSession;
5855
+ exports.permissionEventName = permissionEventName;
5856
+ exports.permissionEventProps = permissionEventProps;
5857
+ exports.permissionScreenId = permissionScreenId;
5858
+ exports.permissionStorageKey = permissionStorageKey;
5294
5859
  exports.readCachedFeatures = readCachedFeatures;
5295
5860
  exports.readProgress = readProgress;
5861
+ exports.readSettledPermissions = readSettledPermissions;
5296
5862
  exports.reportClientEvent = reportClientEvent;
5297
5863
  exports.reportClientEventAwait = reportClientEventAwait;
5298
5864
  exports.reportClientEvents = reportClientEvents;
@@ -5307,10 +5873,13 @@ exports.resetIdentityProvenance = resetIdentityProvenance;
5307
5873
  exports.resetSessionStartGuard = resetSessionStartGuard;
5308
5874
  exports.resolveAutoDeviceKey = resolveAutoDeviceKey;
5309
5875
  exports.resolveIdentity = resolveIdentity;
5876
+ exports.resolvePermissionCopy = resolvePermissionCopy;
5310
5877
  exports.resolvePlanTier = resolvePlanTier;
5311
5878
  exports.resolveUserContext = resolveUserContext;
5312
5879
  exports.sanitizeUserId = sanitizeUserId;
5313
5880
  exports.savePersistedSession = savePersistedSession;
5881
+ exports.saveSettledPermissions = saveSettledPermissions;
5882
+ exports.selectDuePermissionScreen = selectDuePermissionScreen;
5314
5883
  exports.sessionStorageKey = sessionStorageKey;
5315
5884
  exports.setCurrentSessionId = setCurrentSessionId;
5316
5885
  exports.subscribeActivationRevalidation = subscribeActivationRevalidation;