@wireai/activation 0.12.2 → 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 (55) hide show
  1. package/AGENTS.md +3 -1
  2. package/CHANGELOG.md +259 -1
  3. package/README.md +87 -3
  4. package/dist/analytics/index.d.mts +2 -2
  5. package/dist/analytics/index.d.ts +2 -2
  6. package/dist/analytics/index.js +174 -36
  7. package/dist/analytics/index.js.map +1 -1
  8. package/dist/analytics/index.mjs +174 -37
  9. package/dist/analytics/index.mjs.map +1 -1
  10. package/dist/{currentSession-BlCeDP0f.d.mts → currentSession-ClkLjcJ0.d.mts} +456 -19
  11. package/dist/{currentSession-BxEB37xt.d.ts → currentSession-DOVZEWJl.d.ts} +456 -19
  12. package/dist/index.d.mts +197 -16
  13. package/dist/index.d.ts +197 -16
  14. package/dist/index.js +1056 -390
  15. package/dist/index.js.map +1 -1
  16. package/dist/index.mjs +836 -193
  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 +20 -7
  21. package/dist/reviews/index.js.map +1 -1
  22. package/dist/reviews/index.mjs +20 -7
  23. package/dist/reviews/index.mjs.map +1 -1
  24. package/package.json +1 -1
  25. package/src/OnboardingFlow.tsx +141 -3
  26. package/src/WireOnboarding.tsx +178 -34
  27. package/src/activation/wireActivation.ts +13 -7
  28. package/src/analytics/analyticsEvent.ts +16 -1
  29. package/src/analytics/analyticsFacade.ts +11 -10
  30. package/src/analytics/currentSession.ts +6 -20
  31. package/src/analytics/eventQueue.ts +71 -1
  32. package/src/analytics/index.ts +1 -1
  33. package/src/analytics/reportClientEvent.ts +157 -38
  34. package/src/cards/PermissionCard.tsx +438 -0
  35. package/src/cards/index.ts +7 -0
  36. package/src/config/wireConfigFromEnv.ts +1 -10
  37. package/src/context/deviceId.ts +77 -16
  38. package/src/context/userContext.ts +4 -15
  39. package/src/identity/identityRecord.ts +123 -0
  40. package/src/identity/userIdentity.ts +45 -9
  41. package/src/illustrations/defaultIllustrations.tsx +44 -3
  42. package/src/index.ts +44 -4
  43. package/src/permissions/index.ts +64 -0
  44. package/src/permissions/permissionCopy.ts +87 -0
  45. package/src/permissions/permissionEvents.ts +76 -0
  46. package/src/permissions/permissionMemory.ts +88 -0
  47. package/src/permissions/placement.ts +88 -0
  48. package/src/permissions/types.ts +131 -0
  49. package/src/session/persistedSession.ts +10 -3
  50. package/src/session-analytics/useLifecycleEvents.ts +10 -1
  51. package/src/types.ts +77 -1
  52. package/src/utils/deriveAnswers.ts +6 -2
  53. package/src/utils/readProgress.ts +4 -0
  54. package/src/utils/warnInDev.ts +33 -0
  55. package/src/components/DoneBlock.tsx +0 -37
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,7 +1030,7 @@ var _OnboardingScaffold = ({
1012
1030
  children
1013
1031
  }) => {
1014
1032
  const t = useOnboardingTheme();
1015
- const styles18 = 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,
@@ -1036,7 +1054,7 @@ var _OnboardingScaffold = ({
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,7 +1186,7 @@ var _CardLayout = ({
1168
1186
  titleNode
1169
1187
  }) => {
1170
1188
  const t = useOnboardingTheme();
1171
- const styles18 = React18.useMemo(() => makeStyles2(t), [t]);
1189
+ const styles18 = React19.useMemo(() => makeStyles2(t), [t]);
1172
1190
  const hasHeader = !!title || !!subtitle || !!titleNode;
1173
1191
  return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.KeyboardAvoidingView, { style: styles18.fill, behavior: "padding", children: [
1174
1192
  hasHeader ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles18.header, children: [
@@ -1201,7 +1219,7 @@ var _CardLayout = ({
1201
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
  });
@@ -1480,7 +1498,7 @@ var deriveAnswers = (messages) => {
1480
1498
  const props = (_a2 = r.props) != null ? _a2 : {};
1481
1499
  const progress = props.progress;
1482
1500
  const questionText = (_c = (_b = props.title) != null ? _b : props.label) != null ? _c : props.question;
1483
- const key = (progress == null ? void 0 : progress.key) || questionText;
1501
+ const key = (progress == null ? void 0 : progress.slot_id) || (progress == null ? void 0 : progress.key) || questionText;
1484
1502
  const reply = messages[i + 1];
1485
1503
  if (key && (reply == null ? void 0 : reply.role) === "user") {
1486
1504
  out[key] = tryParse(stripPrefix(reply.content));
@@ -1501,45 +1519,71 @@ var readProgress = (response) => {
1501
1519
  step: typeof p.step === "number" ? p.step : void 0,
1502
1520
  total: typeof p.total === "number" ? p.total : void 0,
1503
1521
  key: typeof p.key === "string" ? p.key : void 0,
1522
+ // Whitelisted the same way as every other field: an old backend simply omits it.
1523
+ slot_id: typeof p.slot_id === "string" ? p.slot_id : void 0,
1504
1524
  skippable: typeof p.skippable === "boolean" ? p.skippable : void 0
1505
1525
  };
1506
1526
  };
1507
1527
 
1508
1528
  // src/analytics/reportClientEvent.ts
1509
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
+ });
1510
1540
  var buildEventsRequest = (target, events) => {
1511
1541
  if (!(target == null ? void 0 : target.serverUrl) || events.length === 0) return null;
1512
1542
  try {
1513
1543
  const url = `${target.serverUrl.replace(/\/$/, "")}/v1/events`;
1514
1544
  const headers = { "Content-Type": "application/json" };
1515
1545
  if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
1516
- 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 } };
1517
1548
  } catch {
1518
1549
  return null;
1519
1550
  }
1520
1551
  };
1521
- var warnOnSkippedEvents = (res) => {
1522
- if (typeof __DEV__ === "undefined" || !__DEV__) return;
1523
- if (typeof console === "undefined" || !console.warn) return;
1552
+ var readEventsAck = async (res) => {
1524
1553
  try {
1525
1554
  const json = res == null ? void 0 : res.json;
1526
- if (typeof json !== "function") return;
1527
- void Promise.resolve(json.call(res)).then((body) => {
1528
- const skipped = body == null ? void 0 : body.skipped;
1529
- if (typeof skipped !== "number" || skipped <= 0) return;
1530
- console.warn(
1531
- `[wireai] the server ACCEPTED the /v1/events POST but DISCARDED ${skipped} event(s) (skipped in the response body) \u2014 they are gone, not retried. The usual cause is an event with a missing or empty session_id.`
1532
- );
1533
- }).catch(() => {
1534
- });
1555
+ if (typeof json !== "function") return void 0;
1556
+ const body = await Promise.resolve(json.call(res));
1557
+ const skipped = body == null ? void 0 : body.skipped;
1558
+ if (typeof skipped !== "number" || !Number.isFinite(skipped)) return void 0;
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") : [];
1566
+ return { written: typeof (body == null ? void 0 : body.written) === "number" ? body.written : void 0, skipped, reasons };
1535
1567
  } catch {
1568
+ return void 0;
1536
1569
  }
1537
1570
  };
1571
+ var describeDiscarded = (ack) => `[wireai] the server ACCEPTED the /v1/events POST but DISCARDED ${ack.skipped} event(s) (skipped in the response body) \u2014 they are gone, not retried. The server reported: skipped=${ack.skipped}${ack.written !== void 0 ? `, written=${ack.written}` : ""}` + (ack.reasons.length > 0 ? `, reasons: ${ack.reasons.join(", ")}.` : ". It gave no reason (the endpoint folds every rejection into one count), so check the server's ingest log for this request rather than guessing.");
1572
+ var warnOnSkippedEvents = (res) => {
1573
+ if (typeof __DEV__ === "undefined" || !__DEV__) return;
1574
+ if (typeof console === "undefined" || !console.warn) return;
1575
+ void readEventsAck(res).then((ack) => {
1576
+ if (!ack || ack.skipped <= 0) return;
1577
+ console.warn(describeDiscarded(ack));
1578
+ });
1579
+ };
1538
1580
  var reportClientEvents = (target, events) => {
1539
1581
  try {
1540
1582
  const req = buildEventsRequest(target, events);
1541
1583
  if (!req) return;
1542
- void fetch(req.url, req.init).catch(() => {
1584
+ void fetch(req.url, req.init).then((res) => {
1585
+ warnOnSkippedEvents(res);
1586
+ }).catch(() => {
1543
1587
  });
1544
1588
  } catch {
1545
1589
  }
@@ -1550,7 +1594,13 @@ var reportClientEventsAwait = async (target, events) => {
1550
1594
  const req = buildEventsRequest(target, events);
1551
1595
  if (!req) return false;
1552
1596
  const res = await fetch(req.url, req.init);
1553
- return Boolean(res && res.ok);
1597
+ if (!res || !res.ok) return false;
1598
+ const ack = await readEventsAck(res);
1599
+ if (!ack || ack.skipped <= 0) return true;
1600
+ if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
1601
+ console.warn(describeDiscarded(ack));
1602
+ }
1603
+ return false;
1554
1604
  } catch {
1555
1605
  return false;
1556
1606
  }
@@ -1582,6 +1632,382 @@ var sendPreview = (target, { sessionId, userMessage }) => {
1582
1632
  } catch {
1583
1633
  }
1584
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
+ };
1585
2011
  var SKIP_ONE_SENTINEL = "__wireai_skip__";
1586
2012
  var ANSWER_CALLBACKS = ["onSubmit", "onSelect", "onConfirm", "onPress"];
1587
2013
  var DEFAULT_COPY = {
@@ -1610,53 +2036,60 @@ var OnboardingFlow = ({
1610
2036
  reportTarget,
1611
2037
  sessionId,
1612
2038
  resumed = false,
1613
- clientContext
2039
+ clientContext,
2040
+ permissionScreens,
2041
+ settledPermissions,
2042
+ permissionsPending = false,
2043
+ onPermissionSettled
1614
2044
  }) => {
1615
2045
  var _a2, _b, _c, _d, _e, _f;
1616
2046
  const { messages, error, isLoading, sendMessage, reset } = wireaiRn.useWireAIThread();
1617
2047
  const makeActions = wireaiRn.useWireAIAction(sendMessage);
1618
- const c = React18.useMemo(() => ({ ...DEFAULT_COPY, ...copy }), [copy]);
1619
- const onEventRef = React18.useRef(onEvent);
2048
+ const c = React19.useMemo(() => ({ ...DEFAULT_COPY, ...copy }), [copy]);
2049
+ const onEventRef = React19.useRef(onEvent);
1620
2050
  onEventRef.current = onEvent;
1621
- const onErrorRef = React18.useRef(onError);
2051
+ const onErrorRef = React19.useRef(onError);
1622
2052
  onErrorRef.current = onError;
1623
- const onCompleteRef = React18.useRef(onComplete);
2053
+ const onCompleteRef = React19.useRef(onComplete);
1624
2054
  onCompleteRef.current = onComplete;
1625
- const validatorsRef = React18.useRef(validators);
2055
+ const validatorsRef = React19.useRef(validators);
1626
2056
  validatorsRef.current = validators;
1627
- const reportTargetRef = React18.useRef(reportTarget);
2057
+ const reportTargetRef = React19.useRef(reportTarget);
1628
2058
  reportTargetRef.current = reportTarget;
1629
- const sessionIdRef = React18.useRef(sessionId);
2059
+ const sessionIdRef = React19.useRef(sessionId);
1630
2060
  sessionIdRef.current = sessionId;
1631
- const clientContextRef = React18.useRef(clientContext);
2061
+ const clientContextRef = React19.useRef(clientContext);
1632
2062
  clientContextRef.current = clientContext;
1633
- const lastScreenIndexRef = React18.useRef(-1);
1634
- const previewTimer = React18.useRef(null);
1635
- const started = React18.useRef(false);
1636
- const finished = React18.useRef(false);
1637
- const errored = React18.useRef(false);
1638
- const attempts = React18.useRef(0);
1639
- const degradedRef = React18.useRef(false);
1640
- const [degraded, setDegraded] = React18.useState(false);
1641
- const [timedOut, setTimedOut] = React18.useState(false);
1642
- const [validationError, setValidationError] = React18.useState();
1643
- const [validating, setValidating] = React18.useState(false);
1644
- 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(
1645
2078
  () => [...messages].reverse().find((m) => {
1646
2079
  var _a3;
1647
2080
  return m.role === "assistant" && ((_a3 = m.response) == null ? void 0 : _a3.action) === "render";
1648
2081
  }),
1649
2082
  [messages]
1650
2083
  );
1651
- const renderedCount = React18.useMemo(
2084
+ const renderedCount = React19.useMemo(
1652
2085
  () => messages.filter((m) => {
1653
2086
  var _a3;
1654
2087
  return m.role === "assistant" && ((_a3 = m.response) == null ? void 0 : _a3.action) === "render";
1655
2088
  }).length,
1656
2089
  [messages]
1657
2090
  );
1658
- const progress = React18.useMemo(() => readProgress(lastCard == null ? void 0 : lastCard.response), [lastCard]);
1659
- React18.useEffect(() => {
2091
+ const progress = React19.useMemo(() => readProgress(lastCard == null ? void 0 : lastCard.response), [lastCard]);
2092
+ React19.useEffect(() => {
1660
2093
  var _a3;
1661
2094
  if (started.current) return;
1662
2095
  started.current = true;
@@ -1666,12 +2099,12 @@ var OnboardingFlow = ({
1666
2099
  resumed ? { type: "resumed", contextId: sessionId } : { type: "started", contextId: sessionId }
1667
2100
  );
1668
2101
  }, [sendMessage, startMessage, resumed, sessionId]);
1669
- React18.useEffect(() => {
2102
+ React19.useEffect(() => {
1670
2103
  if (lastCard) return;
1671
2104
  const timer = setTimeout(() => setTimedOut(true), startTimeoutMs);
1672
2105
  return () => clearTimeout(timer);
1673
2106
  }, [lastCard, startTimeoutMs]);
1674
- React18.useEffect(() => {
2107
+ React19.useEffect(() => {
1675
2108
  var _a3, _b2, _c2, _d2;
1676
2109
  setValidationError(void 0);
1677
2110
  if (!(lastCard == null ? void 0 : lastCard.id)) return;
@@ -1689,7 +2122,7 @@ var OnboardingFlow = ({
1689
2122
  });
1690
2123
  }
1691
2124
  }, [lastCard == null ? void 0 : lastCard.id]);
1692
- React18.useEffect(() => {
2125
+ React19.useEffect(() => {
1693
2126
  return () => {
1694
2127
  var _a3, _b2, _c2;
1695
2128
  if (finished.current || degradedRef.current) return;
@@ -1703,13 +2136,13 @@ var OnboardingFlow = ({
1703
2136
  });
1704
2137
  };
1705
2138
  }, []);
1706
- const restartThread = React18.useCallback(() => {
2139
+ const restartThread = React19.useCallback(() => {
1707
2140
  setTimedOut(false);
1708
2141
  started.current = false;
1709
2142
  errored.current = false;
1710
2143
  reset();
1711
2144
  }, [reset]);
1712
- React18.useEffect(() => {
2145
+ React19.useEffect(() => {
1713
2146
  var _a3, _b2, _c2, _d2, _e2, _f2, _g;
1714
2147
  if (errored.current) return;
1715
2148
  if (!error && !timedOut) return;
@@ -1736,22 +2169,52 @@ var OnboardingFlow = ({
1736
2169
  (_g = onErrorRef.current) == null ? void 0 : _g.call(onErrorRef, error != null ? error : new Error("Onboarding timed out"));
1737
2170
  }
1738
2171
  }, [error, timedOut, maxRetries]);
1739
- const handleFinish = React18.useCallback(() => {
2172
+ const handleFinish = React19.useCallback(() => {
1740
2173
  if (finished.current) return;
1741
2174
  finished.current = true;
1742
2175
  onCompleteRef.current({ answers: deriveAnswers(messages), raw: messages });
1743
2176
  }, [messages]);
1744
- const handleRetry = React18.useCallback(() => {
2177
+ const handleRetry = React19.useCallback(() => {
1745
2178
  attempts.current = 0;
1746
2179
  degradedRef.current = false;
1747
2180
  setDegraded(false);
1748
2181
  restartThread();
1749
2182
  }, [restartThread]);
1750
- const skipQuestion = React18.useCallback(() => {
2183
+ const skipQuestion = React19.useCallback(() => {
1751
2184
  if (isLoading) return;
1752
2185
  sendMessage(SKIP_ONE_SENTINEL);
1753
2186
  }, [isLoading, sendMessage]);
1754
- 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) => {
1755
2218
  const target = reportTargetRef.current;
1756
2219
  if (!(target == null ? void 0 : target.serverUrl)) return;
1757
2220
  if (Array.isArray(value) ? value.length === 0 : !value) return;
@@ -1763,15 +2226,15 @@ var OnboardingFlow = ({
1763
2226
  });
1764
2227
  }, 300);
1765
2228
  }, []);
1766
- React18.useEffect(() => () => {
2229
+ React19.useEffect(() => () => {
1767
2230
  if (previewTimer.current) clearTimeout(previewTimer.current);
1768
2231
  }, []);
1769
- const wrappedActions = React18.useCallback(
2232
+ const wrappedActions = React19.useCallback(
1770
2233
  (messageId) => {
1771
2234
  var _a3;
1772
2235
  const base = makeActions(messageId);
1773
- const key = progress.key;
1774
- const validator = key ? (_a3 = validatorsRef.current) == null ? void 0 : _a3[key] : void 0;
2236
+ const validators2 = validatorsRef.current;
2237
+ const validator = (_a3 = progress.slot_id ? validators2 == null ? void 0 : validators2[progress.slot_id] : void 0) != null ? _a3 : progress.key ? validators2 == null ? void 0 : validators2[progress.key] : void 0;
1775
2238
  if (!validator) return base;
1776
2239
  const out = { ...base };
1777
2240
  for (const name2 of ANSWER_CALLBACKS) {
@@ -1796,7 +2259,7 @@ var OnboardingFlow = ({
1796
2259
  }
1797
2260
  return out;
1798
2261
  },
1799
- [makeActions, progress.key]
2262
+ [makeActions, progress.key, progress.slot_id]
1800
2263
  );
1801
2264
  if (degraded) {
1802
2265
  if (fallbackFlow !== void 0) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallbackFlow });
@@ -1821,6 +2284,36 @@ var OnboardingFlow = ({
1821
2284
  const step = (_a2 = progress.step) != null ? _a2 : renderedCount;
1822
2285
  const total = (_b = progress.total) != null ? _b : approxScreens;
1823
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
+ }
1824
2317
  if (isTerminal) {
1825
2318
  const props = (_c = response.props) != null ? _c : {};
1826
2319
  return /* @__PURE__ */ jsxRuntime.jsx(OnboardingScaffold, { step, complete: true, approxScreens: total, children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -1879,8 +2372,8 @@ var OnboardingFlow = ({
1879
2372
  }
1880
2373
  );
1881
2374
  };
1882
- var easeWire4 = reactNative.Easing.bezier(...WIRE_BEZIER);
1883
- var schema = zod.z.object({
2375
+ var easeWire5 = reactNative.Easing.bezier(...WIRE_BEZIER);
2376
+ var schema2 = zod.z.object({
1884
2377
  title: zod.z.string().describe("Question or section header, e.g. 'Which season?'"),
1885
2378
  chips: zod.z.array(zod.z.string()).min(1).describe("List of option labels shown as chips, e.g. ['Spring','Summer','Autumn','Winter']"),
1886
2379
  multiSelect: zod.z.boolean().optional().describe("Allow multiple chips selected at once, default true"),
@@ -1890,9 +2383,9 @@ var schema = zod.z.object({
1890
2383
  var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
1891
2384
  const t = useOnboardingTheme();
1892
2385
  const reduced = useReducedMotion();
1893
- const handlePress = React18.useCallback(() => onToggle(chip), [chip, onToggle]);
1894
- const pressScale = React18.useRef(new reactNative.Animated.Value(1)).current;
1895
- 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(
1896
2389
  (to) => {
1897
2390
  if (reduced) return;
1898
2391
  reactNative.Animated.spring(pressScale, {
@@ -1904,21 +2397,21 @@ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
1904
2397
  },
1905
2398
  [pressScale, reduced]
1906
2399
  );
1907
- const handlePressIn = React18.useCallback(() => pressTo(CHIP_PRESS_SCALE), [pressTo]);
1908
- const handlePressOut = React18.useCallback(() => pressTo(1), [pressTo]);
1909
- const fillT = React18.useRef(new reactNative.Animated.Value(isSelected ? 1 : 0)).current;
1910
- 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(() => {
1911
2404
  const anim = reactNative.Animated.timing(fillT, {
1912
2405
  toValue: isSelected ? 1 : 0,
1913
2406
  duration: scaledMs(stateChangeDuration(reduced, CHIP_FILL_MS)),
1914
- easing: easeWire4,
2407
+ easing: easeWire5,
1915
2408
  useNativeDriver: true
1916
2409
  });
1917
2410
  anim.start();
1918
2411
  return () => anim.stop();
1919
2412
  }, [isSelected, reduced, fillT]);
1920
- const checkT = React18.useRef(new reactNative.Animated.Value(isSelected ? 1 : 0)).current;
1921
- React18.useEffect(() => {
2413
+ const checkT = React19.useRef(new reactNative.Animated.Value(isSelected ? 1 : 0)).current;
2414
+ React19.useEffect(() => {
1922
2415
  if (!isSelected) {
1923
2416
  checkT.setValue(0);
1924
2417
  return;
@@ -1951,7 +2444,7 @@ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
1951
2444
  reactNative.Animated.View,
1952
2445
  {
1953
2446
  style: [
1954
- styles10.chip,
2447
+ styles11.chip,
1955
2448
  {
1956
2449
  borderRadius: t.radius.full,
1957
2450
  paddingHorizontal: t.spacing.md,
@@ -1968,7 +2461,7 @@ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
1968
2461
  pointerEvents: "none",
1969
2462
  style: [
1970
2463
  reactNative.StyleSheet.absoluteFill,
1971
- styles10.fillOverlay,
2464
+ styles11.fillOverlay,
1972
2465
  {
1973
2466
  borderRadius: t.radius.full,
1974
2467
  borderColor: t.colors.primary,
@@ -1982,7 +2475,7 @@ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
1982
2475
  reactNative.Animated.Text,
1983
2476
  {
1984
2477
  style: [
1985
- styles10.check,
2478
+ styles11.check,
1986
2479
  { color: t.colors.onPrimary, opacity: checkT, transform: [{ scale: checkScale }] }
1987
2480
  ],
1988
2481
  children: "\u2713"
@@ -1993,7 +2486,7 @@ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
1993
2486
  {
1994
2487
  style: [
1995
2488
  captionStyle(t.fonts),
1996
- styles10.chipLabel,
2489
+ styles11.chipLabel,
1997
2490
  { color: isSelected ? t.colors.onPrimary : t.colors.textMuted }
1998
2491
  ],
1999
2492
  children: chip
@@ -2005,7 +2498,7 @@ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
2005
2498
  }
2006
2499
  );
2007
2500
  };
2008
- var ChipItem = React18__default.default.memo(_ChipItem);
2501
+ var ChipItem = React19__default.default.memo(_ChipItem);
2009
2502
  var _ChipSelectCard = ({
2010
2503
  title,
2011
2504
  chips,
@@ -2016,12 +2509,12 @@ var _ChipSelectCard = ({
2016
2509
  onSelectionChange
2017
2510
  }) => {
2018
2511
  const t = useOnboardingTheme();
2019
- const [selected, setSelected] = React18.useState([]);
2020
- const [submitted, setSubmitted] = React18.useState(false);
2021
- React18.useEffect(() => {
2512
+ const [selected, setSelected] = React19.useState([]);
2513
+ const [submitted, setSubmitted] = React19.useState(false);
2514
+ React19.useEffect(() => {
2022
2515
  if (!submitted && selected.length) onSelectionChange == null ? void 0 : onSelectionChange(multiSelect ? selected : selected[0]);
2023
2516
  }, [selected, submitted, multiSelect, onSelectionChange]);
2024
- const toggle = React18.useCallback(
2517
+ const toggle = React19.useCallback(
2025
2518
  (chip) => {
2026
2519
  if (submitted) return;
2027
2520
  if (multiSelect) {
@@ -2036,7 +2529,7 @@ var _ChipSelectCard = ({
2036
2529
  },
2037
2530
  [submitted, multiSelect, maxSelections]
2038
2531
  );
2039
- const handleSubmit = React18.useCallback(() => {
2532
+ const handleSubmit = React19.useCallback(() => {
2040
2533
  if (!selected.length || !onSelect || submitted) return;
2041
2534
  setSubmitted(true);
2042
2535
  onSelect(multiSelect ? selected : selected[0]);
@@ -2055,7 +2548,7 @@ var _ChipSelectCard = ({
2055
2548
  disabled: selected.length === 0 || submitted
2056
2549
  }
2057
2550
  ),
2058
- 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(
2059
2552
  ChipItem,
2060
2553
  {
2061
2554
  chip,
@@ -2071,18 +2564,18 @@ var _ChipSelectCard = ({
2071
2564
  var ChipSelectCard = {
2072
2565
  name: "ChipSelectCard",
2073
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.",
2074
- component: React18__default.default.memo(_ChipSelectCard),
2075
- propsSchema: schema,
2567
+ component: React19__default.default.memo(_ChipSelectCard),
2568
+ propsSchema: schema2,
2076
2569
  defaultProps: { multiSelect: true, submitLabel: "Continue" }
2077
2570
  };
2078
- var styles10 = reactNative.StyleSheet.create({
2571
+ var styles11 = reactNative.StyleSheet.create({
2079
2572
  chipsRow: { flexDirection: "row", flexWrap: "wrap" },
2080
2573
  chip: { borderWidth: 1.5, flexDirection: "row", alignItems: "center", gap: 6, overflow: "hidden" },
2081
2574
  fillOverlay: { borderWidth: 1.5 },
2082
2575
  chipLabel: { fontWeight: "600" },
2083
2576
  check: { fontSize: 13, fontWeight: "800", lineHeight: 15 }
2084
2577
  });
2085
- var schema2 = zod.z.object({
2578
+ var schema3 = zod.z.object({
2086
2579
  label: zod.z.string().describe("Input field label / the question being asked"),
2087
2580
  hint: zod.z.string().optional().describe(
2088
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."
@@ -2107,14 +2600,14 @@ var _TextInputCard = ({
2107
2600
  lines = 6
2108
2601
  }) => {
2109
2602
  const t = useOnboardingTheme();
2110
- const styles18 = React18.useMemo(() => makeStyles3(t), [t]);
2603
+ const styles18 = React19.useMemo(() => makeStyles3(t), [t]);
2111
2604
  const { height: screenHeight } = reactNative.useWindowDimensions();
2112
- const [value, setValue] = React18.useState("");
2113
- const [submitted, setSubmitted] = React18.useState(false);
2605
+ const [value, setValue] = React19.useState("");
2606
+ const [submitted, setSubmitted] = React19.useState(false);
2114
2607
  const locked = submitted && !validationError;
2115
2608
  const APPROX_ROW_HEIGHT = 26;
2116
2609
  const minHeight = multiline ? Math.min(Math.max(lines, 4) * APPROX_ROW_HEIGHT + t.spacing.md * 2, screenHeight * 0.4) : 52;
2117
- const handleSubmit = React18.useCallback(() => {
2610
+ const handleSubmit = React19.useCallback(() => {
2118
2611
  if (value.trim() && onSubmit && !locked) {
2119
2612
  setSubmitted(true);
2120
2613
  onSubmit(value.trim());
@@ -2168,8 +2661,8 @@ var _TextInputCard = ({
2168
2661
  var TextInputCard = {
2169
2662
  name: "TextInputCard",
2170
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.",
2171
- component: React18__default.default.memo(_TextInputCard),
2172
- propsSchema: schema2,
2664
+ component: React19__default.default.memo(_TextInputCard),
2665
+ propsSchema: schema3,
2173
2666
  defaultProps: { submitLabel: "Submit", multiline: true, lines: 6 }
2174
2667
  };
2175
2668
  function makeStyles3(t) {
@@ -2324,7 +2817,7 @@ var _WireIcon = ({ name: name2, size = 20, color, requireModule }) => {
2324
2817
  return /* @__PURE__ */ jsxRuntime.jsx(
2325
2818
  reactNative.View,
2326
2819
  {
2327
- style: styles11.decorative,
2820
+ style: styles12.decorative,
2328
2821
  accessible: false,
2329
2822
  importantForAccessibility: "no-hide-descendants",
2330
2823
  children: hostIcon
@@ -2338,7 +2831,7 @@ var _WireIcon = ({ name: name2, size = 20, color, requireModule }) => {
2338
2831
  return /* @__PURE__ */ jsxRuntime.jsx(
2339
2832
  reactNative.View,
2340
2833
  {
2341
- style: styles11.decorative,
2834
+ style: styles12.decorative,
2342
2835
  accessible: false,
2343
2836
  importantForAccessibility: "no-hide-descendants",
2344
2837
  children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -2353,9 +2846,9 @@ var _WireIcon = ({ name: name2, size = 20, color, requireModule }) => {
2353
2846
  }
2354
2847
  );
2355
2848
  };
2356
- var WireIcon = React18__default.default.memo(_WireIcon);
2849
+ var WireIcon = React19__default.default.memo(_WireIcon);
2357
2850
  WireIcon.displayName = "WireIcon";
2358
- var styles11 = reactNative.StyleSheet.create({
2851
+ var styles12 = reactNative.StyleSheet.create({
2359
2852
  // No size/margin of its own: the glyph decides its footprint, so an absent icon and a
2360
2853
  // present one differ by exactly the glyph — never by a wrapper.
2361
2854
  decorative: {}
@@ -2369,8 +2862,8 @@ var optionObjectSchema = zod.z.object({
2369
2862
  var coerceOptions = (arr) => Array.isArray(arr) ? arr.map((item) => typeof item === "string" ? { value: item, label: item } : item) : arr;
2370
2863
  var optionsField = (description) => zod.z.preprocess(coerceOptions, zod.z.array(optionObjectSchema).min(1)).describe(description);
2371
2864
  var normalizeOptions = (options) => options.map((opt) => typeof opt === "string" ? { value: opt, label: opt } : opt);
2372
- var easeWire5 = reactNative.Easing.bezier(...WIRE_BEZIER);
2373
- var schema3 = zod.z.object({
2865
+ var easeWire6 = reactNative.Easing.bezier(...WIRE_BEZIER);
2866
+ var schema4 = zod.z.object({
2374
2867
  title: zod.z.string().describe("Question or prompt for the user"),
2375
2868
  options: optionsField("List of choices"),
2376
2869
  submitLabel: zod.z.string().optional().describe("Submit button label, default: Continue"),
@@ -2379,9 +2872,9 @@ var schema3 = zod.z.object({
2379
2872
  var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
2380
2873
  const t = useOnboardingTheme();
2381
2874
  const reduced = useReducedMotion();
2382
- const handlePress = React18.useCallback(() => onToggle(opt.value), [opt.value, onToggle]);
2383
- const pressScale = React18.useRef(new reactNative.Animated.Value(1)).current;
2384
- 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(
2385
2878
  (to) => {
2386
2879
  if (reduced) return;
2387
2880
  reactNative.Animated.spring(pressScale, {
@@ -2393,21 +2886,21 @@ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
2393
2886
  },
2394
2887
  [pressScale, reduced]
2395
2888
  );
2396
- const handlePressIn = React18.useCallback(() => pressTo(ROW_PRESS_SCALE), [pressTo]);
2397
- const handlePressOut = React18.useCallback(() => pressTo(1), [pressTo]);
2398
- const selectT = React18.useRef(new reactNative.Animated.Value(isSelected ? 1 : 0)).current;
2399
- 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(() => {
2400
2893
  const anim = reactNative.Animated.timing(selectT, {
2401
2894
  toValue: isSelected ? 1 : 0,
2402
2895
  duration: scaledMs(stateChangeDuration(reduced, SELECT_FILL_MS)),
2403
- easing: easeWire5,
2896
+ easing: easeWire6,
2404
2897
  useNativeDriver: true
2405
2898
  });
2406
2899
  anim.start();
2407
2900
  return () => anim.stop();
2408
2901
  }, [isSelected, reduced, selectT]);
2409
- const checkT = React18.useRef(new reactNative.Animated.Value(isSelected ? 1 : 0)).current;
2410
- React18.useEffect(() => {
2902
+ const checkT = React19.useRef(new reactNative.Animated.Value(isSelected ? 1 : 0)).current;
2903
+ React19.useEffect(() => {
2411
2904
  if (!isSelected) {
2412
2905
  checkT.setValue(0);
2413
2906
  return;
@@ -2440,7 +2933,7 @@ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
2440
2933
  reactNative.Animated.View,
2441
2934
  {
2442
2935
  style: [
2443
- styles12.option,
2936
+ styles13.option,
2444
2937
  {
2445
2938
  padding: t.spacing.md,
2446
2939
  borderRadius: t.radius.md,
@@ -2457,7 +2950,7 @@ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
2457
2950
  pointerEvents: "none",
2458
2951
  style: [
2459
2952
  reactNative.StyleSheet.absoluteFill,
2460
- styles12.selectedOverlay,
2953
+ styles13.selectedOverlay,
2461
2954
  {
2462
2955
  borderRadius: t.radius.md,
2463
2956
  borderColor: t.colors.primary,
@@ -2471,7 +2964,7 @@ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
2471
2964
  reactNative.View,
2472
2965
  {
2473
2966
  style: [
2474
- styles12.indicator,
2967
+ styles13.indicator,
2475
2968
  { borderRadius: t.radius.full },
2476
2969
  isSelected ? { borderColor: t.colors.primary, backgroundColor: t.colors.primary } : { borderColor: t.colors.borderStrong, backgroundColor: "transparent" }
2477
2970
  ],
@@ -2479,7 +2972,7 @@ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
2479
2972
  reactNative.Animated.Text,
2480
2973
  {
2481
2974
  style: [
2482
- styles12.check,
2975
+ styles13.check,
2483
2976
  { color: t.colors.onPrimary, opacity: checkT, transform: [{ scale: checkScale }] }
2484
2977
  ],
2485
2978
  children: "\u2713"
@@ -2495,7 +2988,7 @@ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
2495
2988
  }
2496
2989
  );
2497
2990
  };
2498
- var OptionRow = React18__default.default.memo(_OptionRow);
2991
+ var OptionRow = React19__default.default.memo(_OptionRow);
2499
2992
  var _SelectionCard = ({
2500
2993
  title,
2501
2994
  options,
@@ -2505,13 +2998,13 @@ var _SelectionCard = ({
2505
2998
  onSelectionChange
2506
2999
  }) => {
2507
3000
  const t = useOnboardingTheme();
2508
- const opts = React18__default.default.useMemo(() => normalizeOptions(options), [options]);
2509
- const [selected, setSelected] = React18.useState([]);
2510
- const [submitted, setSubmitted] = React18.useState(false);
2511
- 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(() => {
2512
3005
  if (!submitted && selected.length) onSelectionChange == null ? void 0 : onSelectionChange(multiSelect ? selected : selected[0]);
2513
3006
  }, [selected, submitted, multiSelect, onSelectionChange]);
2514
- const toggle = React18.useCallback(
3007
+ const toggle = React19.useCallback(
2515
3008
  (value) => {
2516
3009
  if (submitted) return;
2517
3010
  if (multiSelect) {
@@ -2522,7 +3015,7 @@ var _SelectionCard = ({
2522
3015
  },
2523
3016
  [submitted, multiSelect]
2524
3017
  );
2525
- const handleSubmit = React18.useCallback(() => {
3018
+ const handleSubmit = React19.useCallback(() => {
2526
3019
  if (!selected.length || !onSelect || submitted) return;
2527
3020
  setSubmitted(true);
2528
3021
  onSelect(multiSelect ? selected : selected[0]);
@@ -2556,18 +3049,18 @@ var _SelectionCard = ({
2556
3049
  var SelectionCard = {
2557
3050
  name: "SelectionCard",
2558
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).",
2559
- component: React18__default.default.memo(_SelectionCard),
2560
- propsSchema: schema3,
3052
+ component: React19__default.default.memo(_SelectionCard),
3053
+ propsSchema: schema4,
2561
3054
  defaultProps: { submitLabel: "Continue", multiSelect: false }
2562
3055
  };
2563
- var styles12 = reactNative.StyleSheet.create({
3056
+ var styles13 = reactNative.StyleSheet.create({
2564
3057
  option: { borderWidth: 1.5, flexDirection: "row", alignItems: "center", overflow: "hidden" },
2565
3058
  selectedOverlay: { borderWidth: 1.5 },
2566
3059
  indicator: { width: 24, height: 24, borderWidth: 2, alignItems: "center", justifyContent: "center" },
2567
3060
  check: { fontSize: 13, fontWeight: "800", lineHeight: 15 }
2568
3061
  });
2569
- var easeWire6 = reactNative.Easing.bezier(...WIRE_BEZIER);
2570
- var schema4 = zod.z.object({
3062
+ var easeWire7 = reactNative.Easing.bezier(...WIRE_BEZIER);
3063
+ var schema5 = zod.z.object({
2571
3064
  title: zod.z.string().describe("Question or prompt for the user, e.g. 'What brings you here?'"),
2572
3065
  options: optionsField(
2573
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."
@@ -2578,9 +3071,9 @@ var schema4 = zod.z.object({
2578
3071
  var _OptionCard = ({ opt, isSelected, multiSelect, onToggle }) => {
2579
3072
  const t = useOnboardingTheme();
2580
3073
  const reduced = useReducedMotion();
2581
- const handlePress = React18.useCallback(() => onToggle(opt.value), [opt.value, onToggle]);
2582
- const pressScale = React18.useRef(new reactNative.Animated.Value(1)).current;
2583
- 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(
2584
3077
  (to) => {
2585
3078
  if (reduced) return;
2586
3079
  reactNative.Animated.spring(pressScale, {
@@ -2592,14 +3085,14 @@ var _OptionCard = ({ opt, isSelected, multiSelect, onToggle }) => {
2592
3085
  },
2593
3086
  [pressScale, reduced]
2594
3087
  );
2595
- const handlePressIn = React18.useCallback(() => pressTo(ROW_PRESS_SCALE), [pressTo]);
2596
- const handlePressOut = React18.useCallback(() => pressTo(1), [pressTo]);
2597
- const selectT = React18.useRef(new reactNative.Animated.Value(isSelected ? 1 : 0)).current;
2598
- 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(() => {
2599
3092
  const anim = reactNative.Animated.timing(selectT, {
2600
3093
  toValue: isSelected ? 1 : 0,
2601
3094
  duration: scaledMs(stateChangeDuration(reduced, SELECT_FILL_MS)),
2602
- easing: easeWire6,
3095
+ easing: easeWire7,
2603
3096
  useNativeDriver: true
2604
3097
  });
2605
3098
  anim.start();
@@ -2614,12 +3107,12 @@ var _OptionCard = ({ opt, isSelected, multiSelect, onToggle }) => {
2614
3107
  accessibilityRole: multiSelect ? "checkbox" : "radio",
2615
3108
  accessibilityState: multiSelect ? { checked: isSelected } : { selected: isSelected },
2616
3109
  accessibilityLabel: opt.label,
2617
- style: styles13.cell,
3110
+ style: styles14.cell,
2618
3111
  children: /* @__PURE__ */ jsxRuntime.jsxs(
2619
3112
  reactNative.Animated.View,
2620
3113
  {
2621
3114
  style: [
2622
- styles13.card,
3115
+ styles14.card,
2623
3116
  {
2624
3117
  padding: t.spacing.md,
2625
3118
  borderRadius: t.radius.lg,
@@ -2636,7 +3129,7 @@ var _OptionCard = ({ opt, isSelected, multiSelect, onToggle }) => {
2636
3129
  pointerEvents: "none",
2637
3130
  style: [
2638
3131
  reactNative.StyleSheet.absoluteFill,
2639
- styles13.selectedOverlay,
3132
+ styles14.selectedOverlay,
2640
3133
  {
2641
3134
  borderRadius: t.radius.lg,
2642
3135
  borderColor: t.colors.primary,
@@ -2654,7 +3147,7 @@ var _OptionCard = ({ opt, isSelected, multiSelect, onToggle }) => {
2654
3147
  }
2655
3148
  );
2656
3149
  };
2657
- var OptionCard = React18__default.default.memo(_OptionCard);
3150
+ var OptionCard = React19__default.default.memo(_OptionCard);
2658
3151
  var _CardGridSelectCard = ({
2659
3152
  title,
2660
3153
  options,
@@ -2664,13 +3157,13 @@ var _CardGridSelectCard = ({
2664
3157
  onSelectionChange
2665
3158
  }) => {
2666
3159
  const t = useOnboardingTheme();
2667
- const opts = React18__default.default.useMemo(() => normalizeOptions(options), [options]);
2668
- const [selected, setSelected] = React18.useState([]);
2669
- const [submitted, setSubmitted] = React18.useState(false);
2670
- 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(() => {
2671
3164
  if (!submitted && selected.length) onSelectionChange == null ? void 0 : onSelectionChange(multiSelect ? selected : selected[0]);
2672
3165
  }, [selected, submitted, multiSelect, onSelectionChange]);
2673
- const toggle = React18.useCallback(
3166
+ const toggle = React19.useCallback(
2674
3167
  (value) => {
2675
3168
  if (submitted) return;
2676
3169
  if (multiSelect) {
@@ -2683,7 +3176,7 @@ var _CardGridSelectCard = ({
2683
3176
  },
2684
3177
  [submitted, multiSelect]
2685
3178
  );
2686
- const handleSubmit = React18.useCallback(() => {
3179
+ const handleSubmit = React19.useCallback(() => {
2687
3180
  if (!selected.length || !onSelect || submitted) return;
2688
3181
  setSubmitted(true);
2689
3182
  onSelect(multiSelect ? selected : selected[0]);
@@ -2701,7 +3194,7 @@ var _CardGridSelectCard = ({
2701
3194
  disabled: selected.length === 0 || submitted
2702
3195
  }
2703
3196
  ),
2704
- 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(
2705
3198
  OptionCard,
2706
3199
  {
2707
3200
  opt,
@@ -2717,19 +3210,19 @@ var _CardGridSelectCard = ({
2717
3210
  var CardGridSelectCard = {
2718
3211
  name: "CardGridSelectCard",
2719
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.",
2720
- component: React18__default.default.memo(_CardGridSelectCard),
2721
- propsSchema: schema4,
3213
+ component: React19__default.default.memo(_CardGridSelectCard),
3214
+ propsSchema: schema5,
2722
3215
  defaultProps: { submitLabel: "Continue", multiSelect: true }
2723
3216
  };
2724
- var styles13 = reactNative.StyleSheet.create({
3217
+ var styles14 = reactNative.StyleSheet.create({
2725
3218
  grid: { flexDirection: "row", flexWrap: "wrap" },
2726
3219
  // Two per row: ~47% basis leaves room for the gap, flexGrow fills the remainder exactly.
2727
3220
  cell: { flexBasis: "47%", flexGrow: 1 },
2728
3221
  card: { borderWidth: 1.5, alignItems: "flex-start", overflow: "hidden" },
2729
3222
  selectedOverlay: { borderWidth: 1.5 }
2730
3223
  });
2731
- var easeWire7 = reactNative.Easing.bezier(...WIRE_BEZIER);
2732
- var schema5 = zod.z.object({
3224
+ var easeWire8 = reactNative.Easing.bezier(...WIRE_BEZIER);
3225
+ var schema6 = zod.z.object({
2733
3226
  status: zod.z.enum(["success", "warning", "error", "info"]).describe("Status type"),
2734
3227
  title: zod.z.string().describe("Status title"),
2735
3228
  message: zod.z.string().optional().describe("Supporting message"),
@@ -2747,8 +3240,8 @@ var _StatusCard = ({ status, title, message, ctaLabel, onContinue, onPress, onSu
2747
3240
  var _a2;
2748
3241
  const t = useOnboardingTheme();
2749
3242
  const reduced = useReducedMotion();
2750
- const [submitted, setSubmitted] = React18.useState(false);
2751
- const color = React18.useMemo(() => {
3243
+ const [submitted, setSubmitted] = React19.useState(false);
3244
+ const color = React19.useMemo(() => {
2752
3245
  switch (status) {
2753
3246
  case "success":
2754
3247
  return t.colors.success;
@@ -2760,9 +3253,9 @@ var _StatusCard = ({ status, title, message, ctaLabel, onContinue, onPress, onSu
2760
3253
  return t.colors.info;
2761
3254
  }
2762
3255
  }, [status, t.colors]);
2763
- const popT = React18.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
2764
- const copyT = React18.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
2765
- 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(() => {
2766
3259
  if (reduced) {
2767
3260
  popT.setValue(1);
2768
3261
  copyT.setValue(1);
@@ -2779,7 +3272,7 @@ var _StatusCard = ({ status, title, message, ctaLabel, onContinue, onPress, onSu
2779
3272
  toValue: 1,
2780
3273
  duration: scaledMs(STATUS_COPY_MS),
2781
3274
  delay: scaledMs(STATUS_COPY_STAGGER_MS),
2782
- easing: easeWire7,
3275
+ easing: easeWire8,
2783
3276
  useNativeDriver: true
2784
3277
  })
2785
3278
  ];
@@ -2788,7 +3281,7 @@ var _StatusCard = ({ status, title, message, ctaLabel, onContinue, onPress, onSu
2788
3281
  }, [reduced, popT, copyT]);
2789
3282
  const popScale = popT.interpolate({ inputRange: [0, 1], outputRange: [STATUS_POP_SCALE_FROM, 1] });
2790
3283
  const copyRise = copyT.interpolate({ inputRange: [0, 1], outputRange: [10, 0] });
2791
- const handleContinue = React18.useCallback(() => {
3284
+ const handleContinue = React19.useCallback(() => {
2792
3285
  if (!submitted) {
2793
3286
  setSubmitted(true);
2794
3287
  if (onContinue) onContinue();
@@ -2801,8 +3294,8 @@ var _StatusCard = ({ status, title, message, ctaLabel, onContinue, onPress, onSu
2801
3294
  {
2802
3295
  align: "center",
2803
3296
  footer: ctaLabel ? /* @__PURE__ */ jsxRuntime.jsx(Button, { title: ctaLabel, onPress: handleContinue, variant: "primary", disabled: submitted }) : void 0,
2804
- children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles14.center, { gap: t.spacing.sm }], children: [
2805
- /* @__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: [
2806
3299
  /* @__PURE__ */ jsxRuntime.jsx(
2807
3300
  reactNative.Animated.Text,
2808
3301
  {
@@ -2846,14 +3339,14 @@ var _StatusCard = ({ status, title, message, ctaLabel, onContinue, onPress, onSu
2846
3339
  var StatusCard = {
2847
3340
  name: "StatusCard",
2848
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.",
2849
- component: React18__default.default.memo(_StatusCard),
2850
- propsSchema: schema5
3342
+ component: React19__default.default.memo(_StatusCard),
3343
+ propsSchema: schema6
2851
3344
  };
2852
- var styles14 = reactNative.StyleSheet.create({
3345
+ var styles15 = reactNative.StyleSheet.create({
2853
3346
  center: { width: "100%", alignItems: "center", justifyContent: "center" },
2854
3347
  titleRow: { flexDirection: "row", alignItems: "center", justifyContent: "center" }
2855
3348
  });
2856
- var schema6 = zod.z.object({
3349
+ var schema7 = zod.z.object({
2857
3350
  label: zod.z.string().describe("What is being counted, e.g. 'Number of days' or 'Travellers'"),
2858
3351
  min: zod.z.number().optional().describe("Minimum value, default 1"),
2859
3352
  max: zod.z.number().optional().describe("Maximum value, default 30"),
@@ -2874,22 +3367,22 @@ var _NumberStepperCard = ({
2874
3367
  onSubmit
2875
3368
  }) => {
2876
3369
  const t = useOnboardingTheme();
2877
- const styles18 = React18.useMemo(() => makeStyles4(t), [t]);
3370
+ const styles18 = React19.useMemo(() => makeStyles4(t), [t]);
2878
3371
  const safeMin = safeNum(min, 1);
2879
3372
  const safeMaxRaw = safeNum(max, 30);
2880
3373
  const safeMax = safeMaxRaw < safeMin ? safeMin : safeMaxRaw;
2881
3374
  const safeStepRaw = safeNum(step, 1);
2882
3375
  const safeStep = safeStepRaw > 0 ? safeStepRaw : 1;
2883
3376
  const safeDefault = safeNum(defaultValue, safeMin);
2884
- const [value, setValue] = React18.useState(Math.min(Math.max(safeDefault, safeMin), safeMax));
2885
- const [submitted, setSubmitted] = React18.useState(false);
2886
- 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(() => {
2887
3380
  if (!submitted) setValue((v) => Math.max(v - safeStep, safeMin));
2888
3381
  }, [submitted, safeStep, safeMin]);
2889
- const handleIncrement = React18.useCallback(() => {
3382
+ const handleIncrement = React19.useCallback(() => {
2890
3383
  if (!submitted) setValue((v) => Math.min(v + safeStep, safeMax));
2891
3384
  }, [submitted, safeStep, safeMax]);
2892
- const handleSubmit = React18.useCallback(() => {
3385
+ const handleSubmit = React19.useCallback(() => {
2893
3386
  if (!submitted) {
2894
3387
  setSubmitted(true);
2895
3388
  onSubmit == null ? void 0 : onSubmit(value);
@@ -2924,8 +3417,8 @@ var _NumberStepperCard = ({
2924
3417
  var NumberStepperCard = {
2925
3418
  name: "NumberStepperCard",
2926
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.",
2927
- component: React18__default.default.memo(_NumberStepperCard),
2928
- propsSchema: schema6,
3420
+ component: React19__default.default.memo(_NumberStepperCard),
3421
+ propsSchema: schema7,
2929
3422
  defaultProps: { min: 1, max: 30, defaultValue: 1, step: 1, submitLabel: "Confirm" }
2930
3423
  };
2931
3424
  function makeStyles4(t) {
@@ -2936,9 +3429,9 @@ function makeStyles4(t) {
2936
3429
  valueBox: { alignItems: "center", minWidth: 80 }
2937
3430
  });
2938
3431
  }
2939
- var easeWire8 = reactNative.Easing.bezier(...WIRE_BEZIER);
2940
- var RISE_PX = 10;
2941
- var schema7 = zod.z.object({
3432
+ var easeWire9 = reactNative.Easing.bezier(...WIRE_BEZIER);
3433
+ var RISE_PX2 = 10;
3434
+ var schema8 = zod.z.object({
2942
3435
  title: zod.z.string().describe("Short, warm headline for the value/momentum moment"),
2943
3436
  body: zod.z.string().optional().describe("One or two supporting lines that reflect the user's answers back and build momentum"),
2944
3437
  items: zod.z.array(zod.z.string()).max(4).optional().describe(
@@ -2962,9 +3455,9 @@ var _WorkItem = ({
2962
3455
  reduced
2963
3456
  }) => {
2964
3457
  const t = useOnboardingTheme();
2965
- const rowT = React18.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
2966
- const checkT = React18.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
2967
- 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(() => {
2968
3461
  if (reduced) {
2969
3462
  rowT.setValue(1);
2970
3463
  checkT.setValue(1);
@@ -2975,7 +3468,7 @@ var _WorkItem = ({
2975
3468
  toValue: 1,
2976
3469
  duration: scaledMs(INTERSTITIAL_ITEM_MS),
2977
3470
  delay: scaledMs(interstitialItemDelay(index)),
2978
- easing: easeWire8,
3471
+ easing: easeWire9,
2979
3472
  useNativeDriver: true
2980
3473
  }),
2981
3474
  reactNative.Animated.spring(checkT, {
@@ -2989,7 +3482,7 @@ var _WorkItem = ({
2989
3482
  anims.forEach((a) => a.start());
2990
3483
  return () => anims.forEach((a) => a.stop());
2991
3484
  }, [reduced, index, rowT, checkT]);
2992
- const rise = rowT.interpolate({ inputRange: [0, 1], outputRange: [RISE_PX, 0] });
3485
+ const rise = rowT.interpolate({ inputRange: [0, 1], outputRange: [RISE_PX2, 0] });
2993
3486
  const checkScale = checkT.interpolate({
2994
3487
  inputRange: [0, 1],
2995
3488
  outputRange: [INTERSTITIAL_CHECK_SCALE_FROM, 1]
@@ -2997,20 +3490,20 @@ var _WorkItem = ({
2997
3490
  return /* @__PURE__ */ jsxRuntime.jsxs(
2998
3491
  reactNative.Animated.View,
2999
3492
  {
3000
- 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 }] }],
3001
3494
  children: [
3002
3495
  /* @__PURE__ */ jsxRuntime.jsx(
3003
3496
  reactNative.View,
3004
3497
  {
3005
3498
  style: [
3006
- styles15.itemBadge,
3499
+ styles16.itemBadge,
3007
3500
  { backgroundColor: t.colors.primarySoft, borderRadius: t.radius.full }
3008
3501
  ],
3009
3502
  children: /* @__PURE__ */ jsxRuntime.jsx(
3010
3503
  reactNative.Animated.Text,
3011
3504
  {
3012
3505
  style: [
3013
- styles15.itemCheck,
3506
+ styles16.itemCheck,
3014
3507
  { color: t.colors.primary, opacity: checkT, transform: [{ scale: checkScale }] }
3015
3508
  ],
3016
3509
  children: "\u2713"
@@ -3018,12 +3511,12 @@ var _WorkItem = ({
3018
3511
  )
3019
3512
  }
3020
3513
  ),
3021
- /* @__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 })
3022
3515
  ]
3023
3516
  }
3024
3517
  );
3025
3518
  };
3026
- var WorkItem = React18__default.default.memo(_WorkItem);
3519
+ var WorkItem = React19__default.default.memo(_WorkItem);
3027
3520
  var _InterstitialCard = ({
3028
3521
  title,
3029
3522
  body,
@@ -3039,17 +3532,17 @@ var _InterstitialCard = ({
3039
3532
  const reduced = useReducedMotion();
3040
3533
  const node = useIllustration(illustration);
3041
3534
  const fallbackNode = useIllustration(illustrationFallback);
3042
- const [submitted, setSubmitted] = React18.useState(false);
3043
- const [imgFailed, setImgFailed] = React18.useState(false);
3044
- const [canAdvance, setCanAdvance] = React18.useState(false);
3045
- 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(() => {
3046
3539
  const timer = setTimeout(() => setCanAdvance(true), MIN_DISPLAY_MS);
3047
3540
  return () => clearTimeout(timer);
3048
3541
  }, []);
3049
- const artT = React18.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
3050
- const titleT = React18.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
3051
- const bodyT = React18.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
3052
- 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(() => {
3053
3546
  if (reduced) {
3054
3547
  artT.setValue(1);
3055
3548
  titleT.setValue(1);
@@ -3060,7 +3553,7 @@ var _InterstitialCard = ({
3060
3553
  toValue: 1,
3061
3554
  duration: scaledMs(INTERSTITIAL_HEAD_MS),
3062
3555
  delay,
3063
- easing: easeWire8,
3556
+ easing: easeWire9,
3064
3557
  useNativeDriver: true
3065
3558
  });
3066
3559
  const anims = [
@@ -3073,9 +3566,9 @@ var _InterstitialCard = ({
3073
3566
  }, [reduced, artT, titleT, bodyT]);
3074
3567
  const riseStyle = (value) => ({
3075
3568
  opacity: value,
3076
- transform: [{ translateY: value.interpolate({ inputRange: [0, 1], outputRange: [RISE_PX, 0] }) }]
3569
+ transform: [{ translateY: value.interpolate({ inputRange: [0, 1], outputRange: [RISE_PX2, 0] }) }]
3077
3570
  });
3078
- const handleContinue = React18.useCallback(() => {
3571
+ const handleContinue = React19.useCallback(() => {
3079
3572
  if (submitted || !canAdvance) return;
3080
3573
  setSubmitted(true);
3081
3574
  if (onSubmit) onSubmit("continue");
@@ -3096,18 +3589,18 @@ var _InterstitialCard = ({
3096
3589
  disabled: submitted || !canAdvance
3097
3590
  }
3098
3591
  ),
3099
- children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles15.center, { gap: t.spacing.md }], children: [
3100
- /* @__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(
3101
3594
  reactNative.Image,
3102
3595
  {
3103
3596
  source: { uri: imageUrl },
3104
- style: styles15.image,
3597
+ style: styles16.image,
3105
3598
  resizeMode: "contain",
3106
3599
  onError: () => setImgFailed(true)
3107
3600
  }
3108
- ) : 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 }),
3109
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 }) }),
3110
- (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
3111
3604
  ] })
3112
3605
  }
3113
3606
  );
@@ -3115,11 +3608,11 @@ var _InterstitialCard = ({
3115
3608
  var InterstitialCard = {
3116
3609
  name: "InterstitialCard",
3117
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.",
3118
- component: React18__default.default.memo(_InterstitialCard),
3119
- propsSchema: schema7,
3611
+ component: React19__default.default.memo(_InterstitialCard),
3612
+ propsSchema: schema8,
3120
3613
  defaultProps: { ctaLabel: "Continue", illustrationFallback: "momentum" }
3121
3614
  };
3122
- var styles15 = reactNative.StyleSheet.create({
3615
+ var styles16 = reactNative.StyleSheet.create({
3123
3616
  center: { width: "100%", alignItems: "center", justifyContent: "center" },
3124
3617
  fullWidth: { width: "100%", alignItems: "center" },
3125
3618
  art: { alignItems: "center", justifyContent: "center" },
@@ -3317,6 +3810,33 @@ var collectDeviceContext = () => {
3317
3810
  return ctx;
3318
3811
  };
3319
3812
 
3813
+ // src/identity/identityRecord.ts
3814
+ var IDENTITY_PROVENANCE_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:identityProvenance");
3815
+ var provenanceGlobal = globalThis;
3816
+ var provenanceRegistry = () => {
3817
+ const existing = provenanceGlobal[IDENTITY_PROVENANCE_SLOT];
3818
+ if (existing) return existing;
3819
+ const created = { host: /* @__PURE__ */ new Map() };
3820
+ provenanceGlobal[IDENTITY_PROVENANCE_SLOT] = created;
3821
+ return created;
3822
+ };
3823
+ var provenanceKey = (space, scope) => `${space}:${scope != null ? scope : "default"}`;
3824
+ var resolveIdentity = (input) => {
3825
+ var _a2;
3826
+ if (typeof input.value !== "string") return void 0;
3827
+ const value = input.value.trim();
3828
+ if (!value) return void 0;
3829
+ const durable = (_a2 = input.durable) != null ? _a2 : input.source === "host";
3830
+ if (input.source === "host") {
3831
+ provenanceRegistry().host.set(provenanceKey(input.space, input.scope), value);
3832
+ }
3833
+ return { value, space: input.space, source: input.source, durable };
3834
+ };
3835
+ var hostIdentity = (space, scope) => provenanceRegistry().host.get(provenanceKey(space, scope));
3836
+ var resetIdentityProvenance = () => {
3837
+ provenanceRegistry().host.clear();
3838
+ };
3839
+
3320
3840
  // src/context/deviceId.ts
3321
3841
  var AUTO_DEVICE_ID_PREFIX = "wdev_";
3322
3842
  var deviceIdStorageKey = (appId) => `wireai:analytics:deviceKey:${appId != null ? appId : "default"}`;
@@ -3339,24 +3859,37 @@ var startHydration = (registry, appId, storage, minted) => {
3339
3859
  const existing = registry.pending.get(appId);
3340
3860
  if (existing) return existing;
3341
3861
  const slot = deviceIdStorageKey(appId);
3342
- const settled = () => {
3862
+ const degraded = () => {
3343
3863
  var _a2;
3344
- return (_a2 = registry.keys.get(appId)) != null ? _a2 : minted;
3864
+ return { value: (_a2 = registry.keys.get(appId)) != null ? _a2 : minted, durable: false };
3345
3865
  };
3866
+ const adopted = (value) => ({ value, durable: true });
3346
3867
  let run;
3347
3868
  try {
3348
3869
  run = Promise.resolve(storage.getItem(slot)).then((saved) => {
3349
3870
  const persisted = typeof saved === "string" && saved.trim() ? saved.trim() : void 0;
3350
3871
  if (persisted) {
3351
3872
  registry.keys.set(appId, persisted);
3352
- return persisted;
3873
+ return adopted(persisted);
3353
3874
  }
3354
- return Promise.resolve(storage.setItem(slot, minted)).then(settled, settled);
3355
- }).catch(settled);
3875
+ return Promise.resolve(storage.setItem(slot, minted)).then(
3876
+ () => {
3877
+ var _a2;
3878
+ return adopted((_a2 = registry.keys.get(appId)) != null ? _a2 : minted);
3879
+ },
3880
+ degraded
3881
+ );
3882
+ }).catch(degraded);
3356
3883
  } catch {
3357
- run = Promise.resolve(settled());
3884
+ run = Promise.resolve(degraded());
3358
3885
  }
3359
3886
  registry.pending.set(appId, run);
3887
+ void run.then((outcome) => {
3888
+ var _a2;
3889
+ if (outcome.durable) return;
3890
+ (_a2 = registry.pending) == null ? void 0 : _a2.delete(appId);
3891
+ registry.hydrating.delete(appId);
3892
+ });
3360
3893
  return run;
3361
3894
  };
3362
3895
  var resolveAutoDeviceKey = (opts = {}) => {
@@ -3376,14 +3909,20 @@ var resolveAutoDeviceKey = (opts = {}) => {
3376
3909
  return (_b = registry.keys.get(appId)) != null ? _b : id;
3377
3910
  };
3378
3911
  var hydrateAutoDeviceKey = async (opts = {}) => {
3912
+ var _a2, _b;
3913
+ return (_b = (_a2 = await hydrateDeviceIdentity(opts)) == null ? void 0 : _a2.value) != null ? _b : resolveAutoDeviceKey(opts);
3914
+ };
3915
+ var hydrateDeviceIdentity = async (opts = {}) => {
3379
3916
  var _a2, _b, _c;
3380
3917
  const id = resolveAutoDeviceKey(opts);
3381
- if (!opts.storage) return id;
3382
- const registry = autoDeviceKeyRegistry();
3383
3918
  const appId = (_a2 = opts.appId) != null ? _a2 : "default";
3919
+ const record = (value, durable) => resolveIdentity({ value, space: "device", source: "auto", durable, scope: appId });
3920
+ if (!opts.storage) return record(id, false);
3921
+ const registry = autoDeviceKeyRegistry();
3384
3922
  const pending = (_b = registry.pending) == null ? void 0 : _b.get(appId);
3385
- if (pending) await pending;
3386
- return (_c = registry.keys.get(appId)) != null ? _c : id;
3923
+ if (!pending) return record((_c = registry.keys.get(appId)) != null ? _c : id, false);
3924
+ const outcome = await pending;
3925
+ return record(outcome.value, outcome.durable);
3387
3926
  };
3388
3927
  var resetAutoDeviceKeys = () => {
3389
3928
  var _a2;
@@ -3407,13 +3946,6 @@ var getCurrentSessionId = () => globalSlot[CURRENT_SESSION_ID_SLOT];
3407
3946
  var resetCurrentSessionId = () => {
3408
3947
  globalSlot[CURRENT_SESSION_ID_SLOT] = void 0;
3409
3948
  };
3410
- var warnInDev = (message) => {
3411
- if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
3412
- console.warn(message);
3413
- return true;
3414
- }
3415
- return false;
3416
- };
3417
3949
  var MINT_WARNING = "[wireai] No app-open session was registered, so a session id was minted for this event (the server drops an event that has no session_id, and still answers 200). Mount useLifecycleEvents at your app root so events correlate to a real app-open.";
3418
3950
  var MINT_WARNED_SLOT = /* @__PURE__ */ Symbol.for(
3419
3951
  "@wireai/activation:currentSessionIdMintWarned"
@@ -3510,27 +4042,20 @@ var identifyOnboarding = async (opts) => {
3510
4042
  contextId = stored == null ? void 0 : stored.id;
3511
4043
  }
3512
4044
  }
3513
- if (!contextId) contextId = getCurrentSessionId();
4045
+ let space = "onboarding";
4046
+ if (!contextId && opts.allowAppSessionFallback) {
4047
+ contextId = getCurrentSessionId();
4048
+ space = "app_session";
4049
+ }
3514
4050
  if (!contextId) return false;
3515
4051
  reportClientEvent(
3516
4052
  { serverUrl: opts.config.serverUrl, apiKey: opts.config.apiKey },
3517
4053
  { event_type: "identify", session_id: contextId, user_id: userId }
3518
4054
  );
3519
- return true;
4055
+ return space;
3520
4056
  };
3521
4057
 
3522
4058
  // src/context/userContext.ts
3523
- var RESERVED_USER_CONTEXT_KEYS = [
3524
- "device_key",
3525
- "app_version",
3526
- "app_build",
3527
- "network_type",
3528
- "session_count",
3529
- "returning",
3530
- "platform",
3531
- "user_email",
3532
- "user_email_hashed"
3533
- ];
3534
4059
  var EXTRA_KEY_PREFIX = "custom.";
3535
4060
  var isWireScalar = (value) => {
3536
4061
  const t = typeof value;
@@ -3611,9 +4136,41 @@ var resolveUserContext = (ctx = {}, opts = {}) => {
3611
4136
  if (Object.keys(bucket).length > 0) result.userContext = bucket;
3612
4137
  return result;
3613
4138
  };
3614
- var warnInDev2 = (message) => {
3615
- if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
3616
- console.warn(message);
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 {
3617
4174
  }
3618
4175
  };
3619
4176
  var AUTO_JOIN_HYDRATION_TIMEOUT_MS = 1500;
@@ -3640,69 +4197,99 @@ var WireOnboarding = ({
3640
4197
  retainSessionOnComplete,
3641
4198
  userContext,
3642
4199
  userId,
3643
- autoJoinKey = true
4200
+ autoJoinKey = true,
4201
+ permissionScreens
3644
4202
  }) => {
3645
- var _a2, _b, _c;
3646
- const boundUserId = React18.useMemo(() => sanitizeUserId(userId), [userId]);
3647
- 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(() => {
3648
4206
  const collected = collectDeviceContext();
3649
4207
  return config.appVersion ? { ...collected, appVersion: config.appVersion } : collected;
3650
4208
  }, [config.appVersion]);
3651
4209
  const userContextKey = userContext ? JSON.stringify(userContext) : "";
3652
- const userContextStable = React18.useMemo(
4210
+ const userContextStable = React19.useMemo(
3653
4211
  () => userContextKey ? JSON.parse(userContextKey) : void 0,
3654
4212
  [userContextKey]
3655
4213
  );
3656
- const missingJoinKey = typeof (userContextStable == null ? void 0 : userContextStable.device_key) !== "string" || !userContextStable.device_key.trim();
4214
+ const hostJoinIdentity = React19.useMemo(
4215
+ () => resolveIdentity({
4216
+ value: userContextStable == null ? void 0 : userContextStable.device_key,
4217
+ space: "device",
4218
+ source: "host",
4219
+ scope: config.appId
4220
+ }),
4221
+ [userContextStable, config.appId]
4222
+ );
4223
+ const missingJoinKey = !hostJoinIdentity;
3657
4224
  const autoJoinPossible = Boolean(storage) && autoJoinKey !== false;
3658
4225
  const wantsAutoJoin = missingJoinKey && autoJoinPossible;
3659
- const [autoJoinValue, setAutoJoinValue] = React18.useState(void 0);
3660
- React18.useEffect(() => {
3661
- if (!wantsAutoJoin || autoJoinValue !== void 0 || !storage) return;
4226
+ const [autoJoinOutcome, setAutoJoinOutcome] = React19.useState(void 0);
4227
+ React19.useEffect(() => {
4228
+ if (!wantsAutoJoin || autoJoinOutcome !== void 0 || !storage) return;
3662
4229
  let cancelled = false;
3663
4230
  const timer = setTimeout(() => {
3664
- if (!cancelled) setAutoJoinValue(null);
4231
+ if (!cancelled) setAutoJoinOutcome({ key: null, reason: "timeout" });
3665
4232
  }, AUTO_JOIN_HYDRATION_TIMEOUT_MS);
3666
- void hydrateAutoDeviceKey({ appId: config.appId, storage }).then((key) => {
4233
+ void hydrateDeviceIdentity({ appId: config.appId, storage }).then((identity) => {
3667
4234
  if (cancelled) return;
3668
4235
  clearTimeout(timer);
3669
- setAutoJoinValue(key || null);
4236
+ setAutoJoinOutcome(
4237
+ (identity == null ? void 0 : identity.durable) ? { key: identity.value } : { key: null, reason: "not-durable" }
4238
+ );
3670
4239
  });
3671
4240
  return () => {
3672
4241
  cancelled = true;
3673
4242
  clearTimeout(timer);
3674
4243
  };
3675
- }, [wantsAutoJoin, autoJoinValue, config.appId, storage]);
3676
- const injectedJoinKey = wantsAutoJoin && typeof autoJoinValue === "string" ? autoJoinValue : void 0;
3677
- const autoJoinPending = wantsAutoJoin && autoJoinValue === void 0;
3678
- const effectiveUserContext = React18.useMemo(
4244
+ }, [wantsAutoJoin, autoJoinOutcome, config.appId, storage]);
4245
+ const injectedJoinKey = wantsAutoJoin ? (_a2 = autoJoinOutcome == null ? void 0 : autoJoinOutcome.key) != null ? _a2 : void 0 : void 0;
4246
+ const autoJoinPending = wantsAutoJoin && autoJoinOutcome === void 0;
4247
+ const effectiveUserContext = React19.useMemo(
3679
4248
  () => injectedJoinKey ? { ...userContextStable, ...activationJoinContext(injectedJoinKey) } : userContextStable,
3680
4249
  [userContextStable, injectedJoinKey]
3681
4250
  );
4251
+ const frozenWireContextRef = React19.useRef(null);
4252
+ if (!autoJoinPending && frozenWireContextRef.current === null) {
4253
+ frozenWireContextRef.current = effectiveUserContext ? JSON.stringify(effectiveUserContext) : "";
4254
+ }
4255
+ const startupUserContextKey = (_b = frozenWireContextRef.current) != null ? _b : "";
4256
+ const startupUserContext = React19.useMemo(
4257
+ () => startupUserContextKey ? JSON.parse(startupUserContextKey) : void 0,
4258
+ [startupUserContextKey]
4259
+ );
3682
4260
  const warnMissingJoinKey = missingJoinKey && !injectedJoinKey && !autoJoinPending;
3683
- 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." : "your `storage` adapter did not answer in time.";
3684
- React18.useEffect(() => {
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.";
4262
+ React19.useEffect(() => {
3685
4263
  if (!warnMissingJoinKey) return;
3686
- warnInDev2(
4264
+ warnInDev(
3687
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
3688
4266
  );
3689
4267
  }, [warnMissingJoinKey, autoJoinReason]);
4268
+ const hostKeyElsewhere = missingJoinKey ? hostIdentity("device", config.appId) : void 0;
4269
+ const warnHostKeyElsewhere = Boolean(hostKeyElsewhere) && Boolean(injectedJoinKey);
4270
+ React19.useEffect(() => {
4271
+ if (!warnHostKeyElsewhere) return;
4272
+ warnInDev(
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)}.`
4274
+ );
4275
+ }, [warnHostKeyElsewhere, hostKeyElsewhere]);
3690
4276
  const misspelledJoinKey = missingJoinKey && typeof (userContextStable == null ? void 0 : userContextStable.deviceKey) === "string" && Boolean(userContextStable.deviceKey.trim());
3691
- React18.useEffect(() => {
4277
+ React19.useEffect(() => {
3692
4278
  if (!misspelledJoinKey || !injectedJoinKey) return;
3693
- warnInDev2(
4279
+ warnInDev(
3694
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."
3695
4281
  );
3696
4282
  }, [misspelledJoinKey, injectedJoinKey]);
3697
- const clientContext = React18.useMemo(
4283
+ const clientContext = React19.useMemo(
3698
4284
  () => ({ device, userContext: effectiveUserContext, userId: boundUserId }),
3699
4285
  [device, effectiveUserContext, boundUserId]
3700
4286
  );
3701
- const [session, setSession] = React18.useState(
4287
+ const [session, setSession] = React19.useState(
3702
4288
  () => storage ? null : { id: makeSessionId(), resumed: false }
3703
4289
  );
3704
4290
  const storageKey = persistKey != null ? persistKey : sessionStorageKey(config.appId);
3705
- React18.useEffect(() => {
4291
+ const permissionsKey = permissionStorageKey(config.appId);
4292
+ React19.useEffect(() => {
3706
4293
  if (!storage || session) return;
3707
4294
  let cancelled = false;
3708
4295
  void loadPersistedSession(storage, storageKey, sessionTtlMs).then((loaded) => {
@@ -3712,20 +4299,51 @@ var WireOnboarding = ({
3712
4299
  cancelled = true;
3713
4300
  };
3714
4301
  }, [storage, storageKey, sessionTtlMs, session]);
3715
- const handleComplete = React18.useCallback(
4302
+ const handleComplete = React19.useCallback(
3716
4303
  (result) => {
3717
- if (storage && shouldClearOnComplete(retainSessionOnComplete)) clearPersistedSession(storage, storageKey);
4304
+ if (storage && shouldClearOnComplete(retainSessionOnComplete)) {
4305
+ clearPersistedSession(storage, storageKey);
4306
+ clearSettledPermissions(storage, permissionsKey);
4307
+ }
3718
4308
  onComplete(result);
3719
4309
  },
3720
- [storage, storageKey, retainSessionOnComplete, onComplete]
4310
+ [storage, storageKey, permissionsKey, retainSessionOnComplete, onComplete]
4311
+ );
4312
+ const sessionId = (_c = session == null ? void 0 : session.id) != null ? _c : "";
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]
3721
4340
  );
3722
- const sessionId = (_a2 = session == null ? void 0 : session.id) != null ? _a2 : "";
3723
- const startupUserIdRef = React18.useRef(boundUserId);
4341
+ const startupUserIdRef = React19.useRef(boundUserId);
3724
4342
  const metadataKey = config.metadata ? JSON.stringify(config.metadata) : "";
3725
- const metadataStable = React18.useMemo(() => metadataKey ? JSON.parse(metadataKey) : void 0, [metadataKey]);
4343
+ const metadataStable = React19.useMemo(() => metadataKey ? JSON.parse(metadataKey) : void 0, [metadataKey]);
3726
4344
  const componentNamesKey = (components != null ? components : onboardingComponents).map((c) => c.name).join(",");
3727
- const componentsStable = React18.useMemo(() => components != null ? components : onboardingComponents, [componentNamesKey]);
3728
- const llm = React18.useMemo(() => {
4345
+ const componentsStable = React19.useMemo(() => components != null ? components : onboardingComponents, [componentNamesKey]);
4346
+ const llm = React19.useMemo(() => {
3729
4347
  const baseUrl = config.serverUrl.replace(/\/$/, "");
3730
4348
  return {
3731
4349
  provider: "a2a",
@@ -3743,7 +4361,9 @@ var WireOnboarding = ({
3743
4361
  // Device snapshot + host-injected user context ride the session-start metadata so the
3744
4362
  // backend can segment the funnel. Old servers ignore these unknown keys (backward compat).
3745
4363
  device,
3746
- ...effectiveUserContext ? { userContext: effectiveUserContext } : {},
4364
+ // The FROZEN wire context, never the live one — see the freeze note above. A change here
4365
+ // recreates the A2A adapter and drops `contextId`.
4366
+ ...startupUserContext ? { userContext: startupUserContext } : {},
3747
4367
  // The session-start user id (from the ref) rides the session-start metadata so the
3748
4368
  // server binds the session to a real user at creation. Reading the ref — not
3749
4369
  // `boundUserId` — keeps this memo off the userId dependency, so a mid-session change
@@ -3752,14 +4372,14 @@ var WireOnboarding = ({
3752
4372
  },
3753
4373
  timeoutMs: 6e4
3754
4374
  };
3755
- }, [config.serverUrl, config.appId, config.apiKey, metadataStable, sessionId, componentsStable, device, effectiveUserContext]);
3756
- const reportTarget = React18.useMemo(
4375
+ }, [config.serverUrl, config.appId, config.apiKey, metadataStable, sessionId, componentsStable, device, startupUserContext]);
4376
+ const reportTarget = React19.useMemo(
3757
4377
  () => ({ serverUrl: config.serverUrl, apiKey: config.apiKey }),
3758
4378
  [config.serverUrl, config.apiKey]
3759
4379
  );
3760
- const identifiedRef = React18.useRef(void 0);
3761
- const identifySeededRef = React18.useRef(false);
3762
- React18.useEffect(() => {
4380
+ const identifiedRef = React19.useRef(void 0);
4381
+ const identifySeededRef = React19.useRef(false);
4382
+ React19.useEffect(() => {
3763
4383
  if (!session || !sessionId) return;
3764
4384
  if (!identifySeededRef.current) {
3765
4385
  identifySeededRef.current = true;
@@ -3778,8 +4398,8 @@ var WireOnboarding = ({
3778
4398
  return /* @__PURE__ */ jsxRuntime.jsx(OnboardingThemeProvider, { theme, children: /* @__PURE__ */ jsxRuntime.jsx(
3779
4399
  LoadingScreen,
3780
4400
  {
3781
- title: (_b = copy == null ? void 0 : copy.restoringTitle) != null ? _b : DEFAULT_COPY.restoringTitle,
3782
- hint: (_c = copy == null ? void 0 : copy.restoringHint) != null ? _c : 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
3783
4403
  }
3784
4404
  ) });
3785
4405
  }
@@ -3800,7 +4420,11 @@ var WireOnboarding = ({
3800
4420
  reportTarget,
3801
4421
  sessionId,
3802
4422
  resumed: session.resumed,
3803
- clientContext
4423
+ clientContext,
4424
+ permissionScreens,
4425
+ settledPermissions,
4426
+ permissionsPending: wantsPermissionMemory && settledPermissions === void 0,
4427
+ onPermissionSettled: handlePermissionSettled
3804
4428
  }
3805
4429
  ) }) }) }) });
3806
4430
  };
@@ -3814,16 +4438,16 @@ function DemoOnboarding({
3814
4438
  onComplete,
3815
4439
  renderTrigger
3816
4440
  }) {
3817
- const [open, setOpen] = React18.useState(false);
3818
- const [runId, setRunId] = React18.useState(0);
3819
- const t = React18.useMemo(() => mergeTheme(theme != null ? theme : {}), [theme]);
3820
- const styles18 = React18.useMemo(() => makeStyles5(t), [t]);
3821
- 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(() => {
3822
4446
  setRunId((n) => n + 1);
3823
4447
  setOpen(true);
3824
4448
  }, []);
3825
- const close = React18.useCallback(() => setOpen(false), []);
3826
- const finish = React18.useCallback(
4449
+ const close = React19.useCallback(() => setOpen(false), []);
4450
+ const finish = React19.useCallback(
3827
4451
  (result) => {
3828
4452
  onComplete == null ? void 0 : onComplete(result);
3829
4453
  setOpen(false);
@@ -3929,20 +4553,6 @@ var themeFromBrand = (input) => {
3929
4553
  button: { ...base.button, ...extra.button }
3930
4554
  };
3931
4555
  };
3932
- var _DoneBlock = ({
3933
- title = "You're all set",
3934
- message = "Personalizing your experience\u2026"
3935
- }) => {
3936
- const t = useOnboardingTheme();
3937
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles16.center, { gap: t.spacing.sm, padding: t.spacing.lg }], children: [
3938
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [headingStyle(t.fonts), { color: t.colors.success, textAlign: "center" }], children: title }),
3939
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }], children: message })
3940
- ] });
3941
- };
3942
- var DoneBlock = React18__default.default.memo(_DoneBlock);
3943
- var styles16 = reactNative.StyleSheet.create({
3944
- center: { flex: 1, alignItems: "center", justifyContent: "center" }
3945
- });
3946
4556
 
3947
4557
  // src/motion/centeredModalMotion.ts
3948
4558
  var CENTERED_MODAL_ANIM = {
@@ -3955,13 +4565,13 @@ var CENTERED_MODAL_ANIM = {
3955
4565
  /** Peak dim of the backdrop at progress 1. */
3956
4566
  backdropOpacity: 0.55
3957
4567
  };
3958
- var _CenteredModal = React18.forwardRef(
4568
+ var _CenteredModal = React19.forwardRef(
3959
4569
  ({ theme, dismissible = true, onDismiss, children }, ref) => {
3960
- const progress = React18.useRef(new reactNative.Animated.Value(0)).current;
3961
- const [open, setOpen] = React18.useState(true);
3962
- 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);
3963
4573
  const reduced = useReducedMotion();
3964
- React18.useEffect(() => {
4574
+ React19.useEffect(() => {
3965
4575
  const anim = reduced ? reactNative.Animated.timing(progress, {
3966
4576
  toValue: 1,
3967
4577
  duration: REDUCED_FADE_MS,
@@ -3975,7 +4585,7 @@ var _CenteredModal = React18.forwardRef(
3975
4585
  anim.start();
3976
4586
  return () => anim.stop();
3977
4587
  }, [progress, reduced]);
3978
- const close = React18.useCallback(() => {
4588
+ const close = React19.useCallback(() => {
3979
4589
  if (closingRef.current) return;
3980
4590
  closingRef.current = true;
3981
4591
  reactNative.Animated.timing(progress, {
@@ -3988,15 +4598,15 @@ var _CenteredModal = React18.forwardRef(
3988
4598
  onDismiss();
3989
4599
  });
3990
4600
  }, [progress, onDismiss, reduced]);
3991
- React18.useImperativeHandle(ref, () => ({ close }), [close]);
3992
- React18.useEffect(() => {
4601
+ React19.useImperativeHandle(ref, () => ({ close }), [close]);
4602
+ React19.useEffect(() => {
3993
4603
  const sub = reactNative.BackHandler.addEventListener("hardwareBackPress", () => {
3994
4604
  if (dismissible) close();
3995
4605
  return true;
3996
4606
  });
3997
4607
  return () => sub.remove();
3998
4608
  }, [dismissible, close]);
3999
- const onBackdrop = React18.useCallback(() => {
4609
+ const onBackdrop = React19.useCallback(() => {
4000
4610
  if (dismissible) close();
4001
4611
  }, [dismissible, close]);
4002
4612
  const backdropOpacity = progress.interpolate({
@@ -4058,7 +4668,7 @@ var _CenteredModal = React18.forwardRef(
4058
4668
  }
4059
4669
  );
4060
4670
  _CenteredModal.displayName = "CenteredModal";
4061
- var CenteredModal = React18__default.default.memo(_CenteredModal);
4671
+ var CenteredModal = React19__default.default.memo(_CenteredModal);
4062
4672
  var styles17 = reactNative.StyleSheet.create({
4063
4673
  fill: { flex: 1 },
4064
4674
  backdrop: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "#000" },
@@ -4188,16 +4798,16 @@ var fetchWireFeatures = async (config) => {
4188
4798
  }
4189
4799
  };
4190
4800
  var useStableValue = (value, isEqual) => {
4191
- const ref = React18.useRef(value);
4801
+ const ref = React19.useRef(value);
4192
4802
  if (!isEqual(ref.current, value)) ref.current = value;
4193
4803
  return ref.current;
4194
4804
  };
4195
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);
4196
4806
  var useWireFeatures = (config) => {
4197
4807
  const stableConfig = useStableValue(config, sameFetchKey);
4198
- const [flags, setFlags] = React18.useState(defaultWireFeatures);
4808
+ const [flags, setFlags] = React19.useState(defaultWireFeatures);
4199
4809
  const canFetch = !!(stableConfig == null ? void 0 : stableConfig.serverUrl) && !!(stableConfig == null ? void 0 : stableConfig.apiKey);
4200
- React18.useEffect(() => {
4810
+ React19.useEffect(() => {
4201
4811
  if (!canFetch) {
4202
4812
  setFlags((prev) => featuresEqual(prev, defaultWireFeatures) ? prev : defaultWireFeatures);
4203
4813
  return;
@@ -4216,7 +4826,7 @@ var useWireFeatures = (config) => {
4216
4826
  var CONTEXT_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.context");
4217
4827
  var globalObj = global;
4218
4828
  if (!globalObj[CONTEXT_SYMBOL]) {
4219
- globalObj[CONTEXT_SYMBOL] = React18.createContext(null);
4829
+ globalObj[CONTEXT_SYMBOL] = React19.createContext(null);
4220
4830
  }
4221
4831
  var WireFeaturesContext = globalObj[CONTEXT_SYMBOL];
4222
4832
  var WireFeaturesProvider = ({
@@ -4226,11 +4836,11 @@ var WireFeaturesProvider = ({
4226
4836
  }) => {
4227
4837
  const fetched = useWireFeatures(flags ? void 0 : config);
4228
4838
  const value = flags != null ? flags : fetched;
4229
- const stable = React18.useMemo(() => value, [value]);
4839
+ const stable = React19.useMemo(() => value, [value]);
4230
4840
  return /* @__PURE__ */ jsxRuntime.jsx(WireFeaturesContext.Provider, { value: stable, children });
4231
4841
  };
4232
4842
  WireFeaturesProvider.displayName = "WireFeaturesProvider";
4233
- var useWireFeaturesContext = () => React18.useContext(WireFeaturesContext);
4843
+ var useWireFeaturesContext = () => React19.useContext(WireFeaturesContext);
4234
4844
  var useResolvedFeatures = (options) => {
4235
4845
  var _a2, _b, _c;
4236
4846
  const ctx = useWireFeaturesContext();
@@ -4245,11 +4855,6 @@ var WIRE_ENV_VARS = [
4245
4855
  "EXPO_PUBLIC_WIREAI_SERVER_URL",
4246
4856
  "EXPO_PUBLIC_WIREAI_APP_ID"
4247
4857
  ];
4248
- var warnInDev3 = (message) => {
4249
- if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
4250
- console.warn(message);
4251
- }
4252
- };
4253
4858
  var wireConfigFromEnv = (overrides) => {
4254
4859
  var _a2, _b, _c, _d, _e;
4255
4860
  const apiKey = (_a2 = overrides == null ? void 0 : overrides.apiKey) != null ? _a2 : process.env.EXPO_PUBLIC_WIREAI_API_KEY;
@@ -4260,7 +4865,7 @@ var wireConfigFromEnv = (overrides) => {
4260
4865
  apiKey ? void 0 : "EXPO_PUBLIC_WIREAI_API_KEY",
4261
4866
  serverUrl ? void 0 : "EXPO_PUBLIC_WIREAI_SERVER_URL"
4262
4867
  ].filter(Boolean);
4263
- warnInDev3(
4868
+ warnInDev(
4264
4869
  `[wireai] wireConfigFromEnv() returned null: ${missing.join(" and ")} ${missing.length > 1 ? "are" : "is"} missing. The kit is now FULLY DISABLED (no onboarding, no analytics, no gates) and nothing else will report an error. Set the var(s) in your env / EAS secrets.`
4265
4870
  );
4266
4871
  return null;
@@ -4315,6 +4920,11 @@ var toAnalyticsEvent = (event) => {
4315
4920
  };
4316
4921
  case "fallback":
4317
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
+ };
4318
4928
  default: {
4319
4929
  const _exhaustive = event;
4320
4930
  return _exhaustive;
@@ -4368,15 +4978,17 @@ var resetActivationRevalidation = () => {
4368
4978
  };
4369
4979
 
4370
4980
  // src/activation/wireActivation.ts
4371
- var clean = (value) => {
4372
- if (typeof value !== "string") return void 0;
4373
- const trimmed = value.trim();
4374
- return trimmed.length > 0 ? trimmed : void 0;
4375
- };
4981
+ var clean = cleanString;
4376
4982
  var createWireActivation = (config) => {
4377
4983
  var _a2, _b;
4378
4984
  const target = { serverUrl: config.serverUrl, apiKey: config.apiKey };
4379
4985
  const explicitDeviceKey = (_b = clean(config.deviceKey)) != null ? _b : clean((_a2 = config.userContext) == null ? void 0 : _a2.deviceKey);
4986
+ resolveIdentity({
4987
+ value: explicitDeviceKey,
4988
+ space: "device",
4989
+ source: "host",
4990
+ scope: config.appId
4991
+ });
4380
4992
  const autoDeviceKeyOptions = {
4381
4993
  appId: config.appId,
4382
4994
  // An explicit key opts out of minting AND persisting (unchanged contract).
@@ -4422,15 +5034,15 @@ var createWireActivation = (config) => {
4422
5034
  getRevalidationVersion: getActivationRevalidationVersion
4423
5035
  };
4424
5036
  };
4425
- var useActivationRevalidation = () => React18.useSyncExternalStore(
5037
+ var useActivationRevalidation = () => React19.useSyncExternalStore(
4426
5038
  subscribeActivationRevalidation,
4427
5039
  getActivationRevalidationVersion,
4428
5040
  getActivationRevalidationVersion
4429
5041
  );
4430
5042
  var useWireActivation = (config) => {
4431
5043
  var _a2;
4432
- const ref = React18.useRef(void 0);
4433
- const prevKeys = React18.useRef("");
5044
+ const ref = React19.useRef(void 0);
5045
+ const prevKeys = React19.useRef("");
4434
5046
  const currentKeys = [
4435
5047
  config.serverUrl,
4436
5048
  config.apiKey,
@@ -4655,9 +5267,9 @@ var reportSessionStart = (opts) => {
4655
5267
  };
4656
5268
  var BACKGROUND_SESSION_MS = 30 * 60 * 1e3;
4657
5269
  var useSessionStart = (config, options = {}) => {
4658
- const latest = React18.useRef({ config, options });
5270
+ const latest = React19.useRef({ config, options });
4659
5271
  latest.current = { config, options };
4660
- React18.useEffect(() => {
5272
+ React19.useEffect(() => {
4661
5273
  const resolveDeviceKey = (cfg, opts) => {
4662
5274
  const host = typeof opts.deviceKey === "string" && opts.deviceKey.trim() ? opts.deviceKey : void 0;
4663
5275
  if (host) return host;
@@ -4810,6 +5422,30 @@ var DEFAULTS = {
4810
5422
  maxRetries: 6
4811
5423
  };
4812
5424
  var READ_TIMEOUT_MS4 = 1500;
5425
+ var QUEUE_KEY_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:eventQueueKeys");
5426
+ var queueKeyGlobal = globalThis;
5427
+ var claimedQueueKeys = () => {
5428
+ const existing = queueKeyGlobal[QUEUE_KEY_SLOT];
5429
+ if (existing) return existing;
5430
+ const created = /* @__PURE__ */ new Set();
5431
+ queueKeyGlobal[QUEUE_KEY_SLOT] = created;
5432
+ return created;
5433
+ };
5434
+ var claimQueueKey = (preferred, explicit) => {
5435
+ const claimed = claimedQueueKeys();
5436
+ if (explicit || !claimed.has(preferred)) {
5437
+ claimed.add(preferred);
5438
+ return preferred;
5439
+ }
5440
+ let ordinal = 2;
5441
+ while (claimed.has(`${preferred}#${ordinal}`)) ordinal++;
5442
+ const key = `${preferred}#${ordinal}`;
5443
+ claimed.add(key);
5444
+ warnInDev(
5445
+ `[wireai] a second event queue was created for the same appId, and "${preferred}" is already claimed by a live one. Two queues sharing one storage slot overwrite each other's backlog, delete each other's pending events when one drains to empty, and double-send on relaunch, so this queue was given "${key}" instead. Prefer ONE analytics instance per app; if you really need two, pass an explicit \`storageKey\` to each so the slots are yours to reason about.`
5446
+ );
5447
+ return key;
5448
+ };
4813
5449
  var withTimeout4 = (p, ms) => {
4814
5450
  let timer;
4815
5451
  const timeout = new Promise((resolve) => {
@@ -4841,7 +5477,8 @@ var createEventQueue = (options) => {
4841
5477
  var _a2, _b, _c, _d, _e, _f, _g;
4842
5478
  const target = options.target;
4843
5479
  const storage = options.storage;
4844
- const key = (_b = options.storageKey) != null ? _b : `wireai:evtq:${(_a2 = options.appId) != null ? _a2 : "default"}`;
5480
+ const defaultKey = (_b = options.storageKey) != null ? _b : `wireai:evtq:${(_a2 = options.appId) != null ? _a2 : "default"}`;
5481
+ const key = storage ? claimQueueKey(defaultKey, options.storageKey !== void 0) : defaultKey;
4845
5482
  const maxSize = (_c = options.maxSize) != null ? _c : DEFAULTS.maxSize;
4846
5483
  const batchSize = (_d = options.batchSize) != null ? _d : DEFAULTS.batchSize;
4847
5484
  const baseBackoffMs = (_e = options.baseBackoffMs) != null ? _e : DEFAULTS.baseBackoffMs;
@@ -5006,10 +5643,10 @@ var createEventQueue = (options) => {
5006
5643
 
5007
5644
  // src/session-analytics/useLifecycleEvents.ts
5008
5645
  var useLifecycleEvents = (config, options = {}) => {
5009
- const latest = React18.useRef({ config, options });
5646
+ const latest = React19.useRef({ config, options });
5010
5647
  latest.current = { config, options };
5011
- const queueRef = React18.useRef(void 0);
5012
- React18.useEffect(() => {
5648
+ const queueRef = React19.useRef(void 0);
5649
+ React19.useEffect(() => {
5013
5650
  const resolveSink = () => {
5014
5651
  var _a2, _b;
5015
5652
  const { config: cfg, options: opts } = latest.current;
@@ -5031,7 +5668,13 @@ var useLifecycleEvents = (config, options = {}) => {
5031
5668
  return (cfg == null ? void 0 : cfg.serverUrl) ? { serverUrl: cfg.serverUrl, apiKey: (_a2 = cfg.apiKey) != null ? _a2 : "" } : void 0;
5032
5669
  };
5033
5670
  const resolveDeviceKey = (cfg, opts) => {
5034
- const host = typeof opts.deviceKey === "string" && opts.deviceKey.trim() ? opts.deviceKey : void 0;
5671
+ var _a2;
5672
+ const host = (_a2 = resolveIdentity({
5673
+ value: opts.deviceKey,
5674
+ space: "device",
5675
+ source: "host",
5676
+ scope: cfg == null ? void 0 : cfg.appId
5677
+ })) == null ? void 0 : _a2.value;
5035
5678
  if (host) return host;
5036
5679
  if (!(cfg == null ? void 0 : cfg.storage)) return void 0;
5037
5680
  return resolveAutoDeviceKey({ appId: cfg.appId, storage: cfg.storage });
@@ -5118,24 +5761,29 @@ exports.CenteredModal = CenteredModal;
5118
5761
  exports.ChipSelectCard = ChipSelectCard;
5119
5762
  exports.CompletionView = CompletionView;
5120
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;
5121
5766
  exports.DEFAULT_SESSION_TTL_MS = DEFAULT_SESSION_TTL_MS;
5122
5767
  exports.DemoOnboarding = DemoOnboarding;
5123
- exports.DoneBlock = DoneBlock;
5124
5768
  exports.EXTRA_KEY_PREFIX = EXTRA_KEY_PREFIX;
5125
5769
  exports.ErrorBlock = ErrorBlock;
5126
5770
  exports.FIRST_OPEN_EVENT = FIRST_OPEN_EVENT;
5771
+ exports.GENERIC_PERMISSION_COPY = GENERIC_PERMISSION_COPY;
5127
5772
  exports.IconRegistryProvider = IconRegistryProvider;
5128
5773
  exports.IllustrationProvider = IllustrationProvider;
5129
5774
  exports.InterstitialCard = InterstitialCard;
5130
5775
  exports.LoadingBlock = LoadingBlock;
5131
5776
  exports.LoadingScreen = LoadingScreen;
5777
+ exports.NOTIFICATIONS_PERMISSION_COPY = NOTIFICATIONS_PERMISSION_COPY;
5132
5778
  exports.NumberStepperCard = NumberStepperCard;
5133
5779
  exports.OnboardingButton = Button;
5134
5780
  exports.OnboardingFlow = OnboardingFlow;
5135
5781
  exports.OnboardingScaffold = OnboardingScaffold;
5136
5782
  exports.OnboardingThemeProvider = OnboardingThemeProvider;
5783
+ exports.PERMISSION_CARD_NAME = PERMISSION_CARD_NAME;
5137
5784
  exports.PLAN_TIER_CONTEXT_KEY = PLAN_TIER_CONTEXT_KEY;
5138
- exports.RESERVED_USER_CONTEXT_KEYS = RESERVED_USER_CONTEXT_KEYS;
5785
+ exports.PermissionCard = PermissionCard;
5786
+ exports.PermissionCardView = PermissionCardView;
5139
5787
  exports.SESSION_STARTED_EVENT = SESSION_STARTED_EVENT;
5140
5788
  exports.SelectionCard = SelectionCard;
5141
5789
  exports.StatusCard = StatusCard;
@@ -5146,6 +5794,7 @@ exports.WIRE_ENV_VARS = WIRE_ENV_VARS;
5146
5794
  exports.WIRE_ICON_GLYPHS = WIRE_ICON_GLYPHS;
5147
5795
  exports.WIRE_ICON_NAMES = WIRE_ICON_NAMES;
5148
5796
  exports.WIRE_ONBOARDING_EVENTS = WIRE_ONBOARDING_EVENTS;
5797
+ exports.WIRE_PERMISSION_EVENTS = WIRE_PERMISSION_EVENTS;
5149
5798
  exports.WIRE_PURCHASE_EVENTS = WIRE_PURCHASE_EVENTS;
5150
5799
  exports.WireFeaturesProvider = WireFeaturesProvider;
5151
5800
  exports.WireIcon = WireIcon;
@@ -5157,6 +5806,7 @@ exports.attributionMetadata = attributionMetadata;
5157
5806
  exports.bumpActivationRevalidation = bumpActivationRevalidation;
5158
5807
  exports.clearPersistedSession = clearPersistedSession;
5159
5808
  exports.clearPiiFromContext = clearPiiFromContext;
5809
+ exports.clearSettledPermissions = clearSettledPermissions;
5160
5810
  exports.clearUserContext = clearUserContext;
5161
5811
  exports.collectDeviceContext = collectDeviceContext;
5162
5812
  exports.createRevenueCatBridge = createRevenueCatBridge;
@@ -5179,13 +5829,17 @@ exports.firstOpenStorageKey = firstOpenStorageKey;
5179
5829
  exports.getActivationRevalidationVersion = getActivationRevalidationVersion;
5180
5830
  exports.getCurrentSessionId = getCurrentSessionId;
5181
5831
  exports.hashEmailFnv1a = hashEmailFnv1a;
5832
+ exports.hostIdentity = hostIdentity;
5182
5833
  exports.hydrateAutoDeviceKey = hydrateAutoDeviceKey;
5834
+ exports.hydrateDeviceIdentity = hydrateDeviceIdentity;
5183
5835
  exports.identifyOnboarding = identifyOnboarding;
5184
5836
  exports.isFeaturesFresh = isFeaturesFresh;
5185
5837
  exports.isOnboardingEnabled = isOnboardingEnabled;
5838
+ exports.isPermissionDue = isPermissionDue;
5186
5839
  exports.isUserCancelled = isUserCancelled;
5187
5840
  exports.isWireScalar = isWireScalar;
5188
5841
  exports.loadPersistedSession = loadPersistedSession;
5842
+ exports.loadSettledPermissions = loadSettledPermissions;
5189
5843
  exports.looksLikeEmail = looksLikeEmail;
5190
5844
  exports.lookupIconGlyph = lookupIconGlyph;
5191
5845
  exports.makeSessionId = makeSessionId;
@@ -5193,11 +5847,18 @@ exports.mergeTheme = mergeTheme;
5193
5847
  exports.mintDeviceId = mintDeviceId;
5194
5848
  exports.motionSpec = motionSpec_exports;
5195
5849
  exports.namespaceExtra = namespaceExtra;
5850
+ exports.normalizeAfterCard = normalizeAfterCard;
5851
+ exports.normalizePermissionStatus = normalizePermissionStatus;
5196
5852
  exports.onboardingComponents = onboardingComponents;
5197
5853
  exports.parseWireFeatures = parseWireFeatures;
5198
5854
  exports.peekPersistedSession = peekPersistedSession;
5855
+ exports.permissionEventName = permissionEventName;
5856
+ exports.permissionEventProps = permissionEventProps;
5857
+ exports.permissionScreenId = permissionScreenId;
5858
+ exports.permissionStorageKey = permissionStorageKey;
5199
5859
  exports.readCachedFeatures = readCachedFeatures;
5200
5860
  exports.readProgress = readProgress;
5861
+ exports.readSettledPermissions = readSettledPermissions;
5201
5862
  exports.reportClientEvent = reportClientEvent;
5202
5863
  exports.reportClientEventAwait = reportClientEventAwait;
5203
5864
  exports.reportClientEvents = reportClientEvents;
@@ -5208,12 +5869,17 @@ exports.resetActivationRevalidation = resetActivationRevalidation;
5208
5869
  exports.resetAutoDeviceKeys = resetAutoDeviceKeys;
5209
5870
  exports.resetCurrentSessionId = resetCurrentSessionId;
5210
5871
  exports.resetFirstOpenLatch = resetFirstOpenLatch;
5872
+ exports.resetIdentityProvenance = resetIdentityProvenance;
5211
5873
  exports.resetSessionStartGuard = resetSessionStartGuard;
5212
5874
  exports.resolveAutoDeviceKey = resolveAutoDeviceKey;
5875
+ exports.resolveIdentity = resolveIdentity;
5876
+ exports.resolvePermissionCopy = resolvePermissionCopy;
5213
5877
  exports.resolvePlanTier = resolvePlanTier;
5214
5878
  exports.resolveUserContext = resolveUserContext;
5215
5879
  exports.sanitizeUserId = sanitizeUserId;
5216
5880
  exports.savePersistedSession = savePersistedSession;
5881
+ exports.saveSettledPermissions = saveSettledPermissions;
5882
+ exports.selectDuePermissionScreen = selectDuePermissionScreen;
5217
5883
  exports.sessionStorageKey = sessionStorageKey;
5218
5884
  exports.setCurrentSessionId = setCurrentSessionId;
5219
5885
  exports.subscribeActivationRevalidation = subscribeActivationRevalidation;