@wireai/activation 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/AGENTS.md +29 -5
  2. package/CHANGELOG.md +198 -6
  3. package/README.md +105 -10
  4. package/dist/analytics/index.d.mts +12 -8
  5. package/dist/analytics/index.d.ts +12 -8
  6. package/dist/analytics/index.js +525 -55
  7. package/dist/analytics/index.js.map +1 -1
  8. package/dist/analytics/index.mjs +525 -55
  9. package/dist/analytics/index.mjs.map +1 -1
  10. package/dist/coachmarks/index.d.mts +2 -2
  11. package/dist/coachmarks/index.d.ts +2 -2
  12. package/dist/coachmarks/index.js.map +1 -1
  13. package/dist/coachmarks/index.mjs.map +1 -1
  14. package/dist/{currentSession-CxnP7gAa.d.ts → currentSession-61dcm3V-.d.ts} +19 -3
  15. package/dist/{currentSession-BJBB7i4-.d.mts → currentSession-Bs2JfTJ8.d.mts} +19 -3
  16. package/dist/{decision-BzbiKwk3.d.mts → decision-Bl_M2y3r.d.mts} +1 -1
  17. package/dist/{decision-plDEOCkt.d.ts → decision-Cau5KmP6.d.ts} +1 -1
  18. package/dist/index.d.mts +620 -13
  19. package/dist/index.d.ts +620 -13
  20. package/dist/index.js +1194 -369
  21. package/dist/index.js.map +1 -1
  22. package/dist/index.mjs +1015 -201
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/questionnaire/index.d.mts +69 -23
  25. package/dist/questionnaire/index.d.ts +69 -23
  26. package/dist/questionnaire/index.js +643 -172
  27. package/dist/questionnaire/index.js.map +1 -1
  28. package/dist/questionnaire/index.mjs +608 -137
  29. package/dist/questionnaire/index.mjs.map +1 -1
  30. package/dist/reviews/index.d.mts +24 -12
  31. package/dist/reviews/index.d.ts +24 -12
  32. package/dist/reviews/index.js +507 -173
  33. package/dist/reviews/index.js.map +1 -1
  34. package/dist/reviews/index.mjs +462 -129
  35. package/dist/reviews/index.mjs.map +1 -1
  36. package/dist/showcase/index.d.mts +2 -2
  37. package/dist/showcase/index.d.ts +2 -2
  38. package/dist/showcase/index.js.map +1 -1
  39. package/dist/showcase/index.mjs.map +1 -1
  40. package/dist/transport-DzU-TqZz.d.ts +86 -0
  41. package/dist/transport-f5VVB5hH.d.mts +86 -0
  42. package/dist/{types-GL_hQ0TN.d.mts → types-BcmagF6K.d.mts} +1 -1
  43. package/dist/{types-GL_hQ0TN.d.ts → types-BcmagF6K.d.ts} +1 -1
  44. package/dist/{types-BhpXJGlg.d.ts → types-Buj9Lw9t.d.ts} +15 -3
  45. package/dist/{types-CMuOexw0.d.mts → types-CKFhyrMu.d.mts} +1 -1
  46. package/dist/{types-CMuOexw0.d.ts → types-CKFhyrMu.d.ts} +1 -1
  47. package/dist/{types-A6pTxIZV.d.mts → types-CNUqMK0D.d.mts} +15 -3
  48. package/metro/index.d.ts +18 -1
  49. package/metro/index.js +80 -0
  50. package/package.json +9 -1
  51. package/src/WireOnboarding.tsx +4 -0
  52. package/src/analytics/analyticsFacade.ts +55 -10
  53. package/src/analytics/currentSession.ts +1 -1
  54. package/src/cards/CardGridSelectCard.tsx +253 -0
  55. package/src/cards/ChipSelectCard.tsx +1 -1
  56. package/src/cards/SelectionCard.tsx +15 -19
  57. package/src/cards/index.ts +13 -1
  58. package/src/cards/optionSchema.ts +65 -0
  59. package/src/coachmarks/types.ts +1 -1
  60. package/src/components/CardHandoff.tsx +39 -7
  61. package/src/components/DoneCheck.tsx +94 -0
  62. package/src/components/ModalPaneHost.tsx +141 -0
  63. package/src/components/StepDots.tsx +112 -0
  64. package/src/context/deviceId.ts +43 -0
  65. package/src/device/appVersion.ts +29 -8
  66. package/src/device/deviceContext.ts +14 -1
  67. package/src/device/deviceModel.ts +106 -0
  68. package/src/features/cache.ts +1 -1
  69. package/src/haptics/expo-haptics.d.ts +31 -0
  70. package/src/haptics/haptics.ts +58 -0
  71. package/src/icons/IconRegistry.tsx +53 -0
  72. package/src/icons/WireIcon.tsx +85 -0
  73. package/src/icons/expoIcons.ts +165 -0
  74. package/src/icons/index.ts +18 -0
  75. package/src/icons/vocabulary.ts +154 -0
  76. package/src/index.ts +22 -0
  77. package/src/questionnaire/QuestionnaireGate.tsx +315 -164
  78. package/src/questionnaire/decision.ts +4 -2
  79. package/src/questionnaire/index.ts +3 -2
  80. package/src/questionnaire/steps.ts +261 -0
  81. package/src/questionnaire/types.ts +33 -12
  82. package/src/reviews/ReviewGate.tsx +250 -152
  83. package/src/reviews/index.ts +7 -3
  84. package/src/reviews/transport.ts +77 -3
  85. package/src/reviews/types.ts +13 -0
  86. package/src/session/persistedSession.ts +1 -1
  87. package/src/session-analytics/lifecycle.ts +1 -1
  88. package/src/session-analytics/useSessionStart.ts +2 -2
  89. package/src/showcase/FeatureShowcase.tsx +1 -1
  90. package/src/showcase/showcaseColors.ts +1 -1
  91. package/src/theme/mergeThemeOver.ts +27 -0
  92. package/src/types.ts +8 -0
  93. package/dist/transport-BeO_Brcu.d.mts +0 -40
  94. package/dist/transport-DLpd1v5_.d.ts +0 -40
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
- var React4 = require('react');
3
+ var React7 = require('react');
4
4
  var reactNative = require('react-native');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
6
  var reactNativeSafeAreaContext = require('react-native-safe-area-context');
7
7
 
8
8
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
9
 
10
- var React4__default = /*#__PURE__*/_interopDefault(React4);
10
+ var React7__default = /*#__PURE__*/_interopDefault(React7);
11
11
 
12
12
  // src/reviews/ReviewGate.tsx
13
13
 
@@ -60,9 +60,9 @@ var defaultOnboardingTheme = {
60
60
  var mergeTheme = (override) => {
61
61
  return defaultOnboardingTheme;
62
62
  };
63
- var OnboardingThemeContext = React4.createContext(null);
63
+ var OnboardingThemeContext = React7.createContext(null);
64
64
  var useOnboardingTheme = () => {
65
- const ctx = React4.useContext(OnboardingThemeContext);
65
+ const ctx = React7.useContext(OnboardingThemeContext);
66
66
  return ctx != null ? ctx : mergeTheme();
67
67
  };
68
68
 
@@ -94,16 +94,26 @@ var buttonLabelStyle = (f) => {
94
94
  };
95
95
 
96
96
  // src/motion/motionSpec.ts
97
+ var WIRE_BEZIER = [0.16, 1, 0.3, 1];
97
98
  var REDUCED_FADE_MS = 120;
98
99
  var BUTTON_PRESS_SCALE = 0.975;
99
100
  var REDUCED_PRESS_OPACITY = 0.85;
100
101
  var BUTTON_PRESS_SPRING = { friction: 8, tension: 240 };
102
+ var CARD_ENTER_MS = 220;
103
+ var CARD_EXIT_MS = 180;
104
+ var CARD_EXIT_LEAD_MS = 80;
105
+ var CARD_SLIDE_PX = 26;
106
+ var CARD_ENTER_SCALE_FROM = 0.96;
107
+ var CARD_SPRING = { damping: 14, stiffness: 180, mass: 1 };
108
+ var LOADER_HANDOFF_FADE_MS = 120;
109
+ var STATUS_POP_SPRING = { friction: 6, tension: 200 };
110
+ var STATUS_POP_SCALE_FROM = 0.4;
101
111
  var durationScale = 1;
102
112
  var scaledMs = (ms) => Math.round(ms * durationScale);
103
113
  var lastKnown = false;
104
114
  var useReducedMotion = () => {
105
- const [reduced, setReduced] = React4.useState(lastKnown);
106
- React4.useEffect(() => {
115
+ const [reduced, setReduced] = React7.useState(lastKnown);
116
+ React7.useEffect(() => {
107
117
  let mounted = true;
108
118
  reactNative.AccessibilityInfo.isReduceMotionEnabled().then((value) => {
109
119
  lastKnown = value;
@@ -131,8 +141,8 @@ var _Button = ({
131
141
  const t = useOnboardingTheme();
132
142
  const reduced = useReducedMotion();
133
143
  const isPrimary = variant === "primary";
134
- const pressT = React4.useRef(new reactNative.Animated.Value(0)).current;
135
- const pressTo = React4.useCallback(
144
+ const pressT = React7.useRef(new reactNative.Animated.Value(0)).current;
145
+ const pressTo = React7.useCallback(
136
146
  (to) => {
137
147
  if (reduced) {
138
148
  reactNative.Animated.timing(pressT, {
@@ -151,16 +161,16 @@ var _Button = ({
151
161
  },
152
162
  [pressT, reduced]
153
163
  );
154
- const lastPress = React4.useRef(0);
155
- const handlePress = React4.useCallback(() => {
164
+ const lastPress = React7.useRef(0);
165
+ const handlePress = React7.useCallback(() => {
156
166
  if (!onPress) return;
157
167
  const now = Date.now();
158
168
  if (now - lastPress.current < 500) return;
159
169
  lastPress.current = now;
160
170
  onPress();
161
171
  }, [onPress]);
162
- const handlePressIn = React4.useCallback(() => pressTo(1), [pressTo]);
163
- const handlePressOut = React4.useCallback(() => pressTo(0), [pressTo]);
172
+ const handlePressIn = React7.useCallback(() => pressTo(1), [pressTo]);
173
+ const handlePressOut = React7.useCallback(() => pressTo(0), [pressTo]);
164
174
  const scale = pressT.interpolate({
165
175
  inputRange: [0, 1],
166
176
  outputRange: [1, reduced ? 1 : BUTTON_PRESS_SCALE]
@@ -211,11 +221,282 @@ var _Button = ({
211
221
  }
212
222
  );
213
223
  };
214
- var Button = React4__default.default.memo(_Button);
224
+ var Button = React7__default.default.memo(_Button);
215
225
  var styles = reactNative.StyleSheet.create({
216
226
  base: { alignItems: "center", justifyContent: "center" },
217
227
  fill: { width: "100%" }
218
228
  });
229
+ var _DoneCheck = ({ theme, size = 48 }) => {
230
+ const reduced = useReducedMotion();
231
+ const pop = React7.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
232
+ React7.useEffect(() => {
233
+ if (reduced) {
234
+ pop.setValue(1);
235
+ return;
236
+ }
237
+ const anim = reactNative.Animated.spring(pop, {
238
+ toValue: 1,
239
+ friction: STATUS_POP_SPRING.friction,
240
+ tension: STATUS_POP_SPRING.tension,
241
+ useNativeDriver: true
242
+ });
243
+ anim.start();
244
+ return () => anim.stop();
245
+ }, [pop, reduced]);
246
+ const scale = pop.interpolate({
247
+ inputRange: [0, 1],
248
+ outputRange: [STATUS_POP_SCALE_FROM, 1]
249
+ });
250
+ return /* @__PURE__ */ jsxRuntime.jsx(
251
+ reactNative.Animated.View,
252
+ {
253
+ accessibilityElementsHidden: true,
254
+ importantForAccessibility: "no-hide-descendants",
255
+ style: [
256
+ styles2.ring,
257
+ {
258
+ width: size,
259
+ height: size,
260
+ borderRadius: theme.radius.full,
261
+ backgroundColor: theme.colors.success,
262
+ opacity: pop,
263
+ transform: [{ scale }]
264
+ }
265
+ ],
266
+ children: /* @__PURE__ */ jsxRuntime.jsx(
267
+ reactNative.Animated.Text,
268
+ {
269
+ style: [
270
+ styles2.glyph,
271
+ { color: theme.colors.onPrimary, fontSize: Math.round(size * 0.5), fontFamily: theme.fonts.bold }
272
+ ],
273
+ children: "\u2713"
274
+ }
275
+ )
276
+ }
277
+ );
278
+ };
279
+ var DoneCheck = React7__default.default.memo(_DoneCheck);
280
+ DoneCheck.displayName = "DoneCheck";
281
+ var styles2 = reactNative.StyleSheet.create({
282
+ ring: { alignItems: "center", justifyContent: "center", alignSelf: "center" },
283
+ glyph: { includeFontPadding: false, textAlign: "center" }
284
+ });
285
+ var easeWire = reactNative.Easing.bezier(...WIRE_BEZIER);
286
+ var _CardHandoff = ({
287
+ transitionKey,
288
+ variant = "slide",
289
+ direction = "forward",
290
+ layout = "fill",
291
+ children
292
+ }) => {
293
+ const reduced = useReducedMotion();
294
+ const [leaving, setLeaving] = React7.useState(null);
295
+ const enterT = React7.useRef(new reactNative.Animated.Value(0)).current;
296
+ const exitT = React7.useRef(new reactNative.Animated.Value(0)).current;
297
+ const prev = React7.useRef({ key: transitionKey, node: children, variant });
298
+ const running = React7.useRef({});
299
+ const mounted = React7.useRef(false);
300
+ React7.useEffect(() => {
301
+ var _a, _b;
302
+ const last = prev.current;
303
+ prev.current = { key: transitionKey, node: children, variant };
304
+ if (!mounted.current) {
305
+ mounted.current = true;
306
+ running.current.enter = startEnter(enterT, variant, reduced, 0);
307
+ return;
308
+ }
309
+ if (last.key === transitionKey) return;
310
+ (_a = running.current.enter) == null ? void 0 : _a.stop();
311
+ (_b = running.current.exit) == null ? void 0 : _b.stop();
312
+ setLeaving(last);
313
+ enterT.setValue(0);
314
+ exitT.setValue(0);
315
+ const exitDuration = reduced ? REDUCED_FADE_MS : last.variant === "fade" ? LOADER_HANDOFF_FADE_MS : CARD_EXIT_MS;
316
+ const exit = reactNative.Animated.timing(exitT, {
317
+ toValue: 1,
318
+ duration: scaledMs(exitDuration),
319
+ easing: easeWire,
320
+ useNativeDriver: true
321
+ });
322
+ running.current.exit = exit;
323
+ exit.start();
324
+ const enter = startEnter(
325
+ enterT,
326
+ variant,
327
+ reduced,
328
+ reduced ? 0 : CARD_EXIT_LEAD_MS,
329
+ () => setLeaving(null)
330
+ );
331
+ running.current.enter = enter;
332
+ });
333
+ React7.useEffect(
334
+ () => () => {
335
+ var _a, _b;
336
+ (_a = running.current.enter) == null ? void 0 : _a.stop();
337
+ (_b = running.current.exit) == null ? void 0 : _b.stop();
338
+ },
339
+ []
340
+ );
341
+ const enterStyle = {
342
+ opacity: enterT,
343
+ transform: transformFor(variant, enterT, reduced, "enter", direction)
344
+ };
345
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
346
+ leaving ? /* @__PURE__ */ jsxRuntime.jsx(
347
+ reactNative.Animated.View,
348
+ {
349
+ pointerEvents: "none",
350
+ style: [
351
+ reactNative.StyleSheet.absoluteFill,
352
+ {
353
+ opacity: exitT.interpolate({ inputRange: [0, 1], outputRange: [1, 0] }),
354
+ // The CURRENT direction drives BOTH layers: one transition, one geography.
355
+ transform: transformFor(leaving.variant, exitT, reduced, "exit", direction)
356
+ }
357
+ ],
358
+ children: leaving.node
359
+ }
360
+ ) : null,
361
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: [layout === "fill" ? styles3.fill : null, enterStyle], children }, variant)
362
+ ] });
363
+ };
364
+ var startEnter = (value, variant, reduced, delay, onDone) => {
365
+ value.setValue(0);
366
+ const anim = !reduced && variant === "spring" ? reactNative.Animated.spring(value, {
367
+ toValue: 1,
368
+ delay: scaledMs(delay),
369
+ damping: CARD_SPRING.damping,
370
+ stiffness: CARD_SPRING.stiffness,
371
+ mass: CARD_SPRING.mass,
372
+ useNativeDriver: true
373
+ }) : reactNative.Animated.timing(value, {
374
+ toValue: 1,
375
+ delay: scaledMs(delay),
376
+ duration: scaledMs(
377
+ reduced ? REDUCED_FADE_MS : variant === "fade" ? LOADER_HANDOFF_FADE_MS : CARD_ENTER_MS
378
+ ),
379
+ easing: easeWire,
380
+ useNativeDriver: true
381
+ });
382
+ anim.start(({ finished }) => {
383
+ if (finished) onDone == null ? void 0 : onDone();
384
+ });
385
+ return anim;
386
+ };
387
+ var transformFor = (variant, t, reduced, layer, travel) => {
388
+ if (reduced || variant === "fade") return [];
389
+ if (variant === "spring" && layer === "enter") {
390
+ return [{ scale: t.interpolate({ inputRange: [0, 1], outputRange: [CARD_ENTER_SCALE_FROM, 1] }) }];
391
+ }
392
+ const slide = travel === "forward" ? CARD_SLIDE_PX : -CARD_SLIDE_PX;
393
+ return [
394
+ {
395
+ translateX: t.interpolate({
396
+ inputRange: [0, 1],
397
+ outputRange: layer === "enter" ? [slide, 0] : [0, -slide]
398
+ })
399
+ }
400
+ ];
401
+ };
402
+ var CardHandoff = React7__default.default.memo(_CardHandoff);
403
+ var styles3 = reactNative.StyleSheet.create({
404
+ fill: { flex: 1 }
405
+ });
406
+ var easeWire2 = reactNative.Easing.bezier(...WIRE_BEZIER);
407
+ var _ModalPaneHost = ({
408
+ paneKey,
409
+ direction = "forward",
410
+ header,
411
+ children
412
+ }) => {
413
+ const reduced = useReducedMotion();
414
+ const height = React7.useRef(new reactNative.Animated.Value(0)).current;
415
+ const measured = React7.useRef(null);
416
+ const [sized, setSized] = React7.useState(false);
417
+ const paneChanged = React7.useRef(false);
418
+ const running = React7.useRef(null);
419
+ React7.useEffect(() => {
420
+ paneChanged.current = true;
421
+ }, [paneKey]);
422
+ React7.useEffect(() => () => {
423
+ var _a;
424
+ return (_a = running.current) == null ? void 0 : _a.stop();
425
+ }, []);
426
+ const onLayout = React7.useCallback(
427
+ (event) => {
428
+ var _a;
429
+ const next = Math.round(event.nativeEvent.layout.height);
430
+ if (!next) return;
431
+ const previous = measured.current;
432
+ if (previous !== null && Math.abs(previous - next) < 1) return;
433
+ const first = previous === null;
434
+ const ease = !first && !reduced && paneChanged.current;
435
+ paneChanged.current = false;
436
+ measured.current = next;
437
+ (_a = running.current) == null ? void 0 : _a.stop();
438
+ if (!ease) {
439
+ height.setValue(next);
440
+ if (first) setSized(true);
441
+ return;
442
+ }
443
+ const anim = reactNative.Animated.timing(height, {
444
+ toValue: next,
445
+ duration: scaledMs(CARD_ENTER_MS),
446
+ easing: easeWire2,
447
+ // height is layout, not transform — see the header note.
448
+ useNativeDriver: false
449
+ });
450
+ running.current = anim;
451
+ anim.start();
452
+ },
453
+ [height, reduced]
454
+ );
455
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: sized ? { height, overflow: "hidden" } : void 0, children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { onLayout, style: styles4.pane, children: [
456
+ header,
457
+ /* @__PURE__ */ jsxRuntime.jsx(CardHandoff, { transitionKey: paneKey, direction, layout: "auto", variant: "slide", children })
458
+ ] }) });
459
+ };
460
+ var ModalPaneHost = React7__default.default.memo(_ModalPaneHost);
461
+ ModalPaneHost.displayName = "ModalPaneHost";
462
+ var styles4 = reactNative.StyleSheet.create({
463
+ pane: { width: "100%" }
464
+ });
465
+
466
+ // src/haptics/haptics.ts
467
+ var play = async (tone) => {
468
+ try {
469
+ const mod = await import('expo-haptics');
470
+ if (!mod) return false;
471
+ if (tone === "selection") {
472
+ await mod.selectionAsync();
473
+ return true;
474
+ }
475
+ if (tone === "light") {
476
+ await mod.impactAsync(mod.ImpactFeedbackStyle.Light);
477
+ return true;
478
+ }
479
+ await mod.notificationAsync(mod.NotificationFeedbackType.Success);
480
+ return true;
481
+ } catch {
482
+ return false;
483
+ }
484
+ };
485
+ var playHaptic = (tone) => {
486
+ void play(tone);
487
+ };
488
+
489
+ // src/theme/mergeThemeOver.ts
490
+ var mergeThemeOver = (base, override) => {
491
+ if (!override) return base;
492
+ return {
493
+ colors: { ...base.colors, ...override.colors },
494
+ fonts: { ...base.fonts, ...override.fonts },
495
+ radius: { ...base.radius, ...override.radius },
496
+ spacing: { ...base.spacing, ...override.spacing },
497
+ button: { ...base.button, ...override.button }
498
+ };
499
+ };
219
500
 
220
501
  // src/reviews/decision.ts
221
502
  var decideReview = (local, decision) => decision != null ? decision : local;
@@ -294,13 +575,13 @@ var centeredModalFrame = (progress, backdropMax = CENTERED_MODAL_ANIM.backdropOp
294
575
  // src/reviews/presentation.ts
295
576
  var DEFAULT_PRESENTATION = "modal";
296
577
  var resolvePresentation = (p) => p != null ? p : DEFAULT_PRESENTATION;
297
- var _CenteredModal = React4.forwardRef(
578
+ var _CenteredModal = React7.forwardRef(
298
579
  ({ theme, dismissible = true, onDismiss, children }, ref) => {
299
- const progress = React4.useRef(new reactNative.Animated.Value(0)).current;
300
- const [open, setOpen] = React4.useState(true);
301
- const closingRef = React4.useRef(false);
580
+ const progress = React7.useRef(new reactNative.Animated.Value(0)).current;
581
+ const [open, setOpen] = React7.useState(true);
582
+ const closingRef = React7.useRef(false);
302
583
  const reduced = useReducedMotion();
303
- React4.useEffect(() => {
584
+ React7.useEffect(() => {
304
585
  const anim = reduced ? reactNative.Animated.timing(progress, {
305
586
  toValue: 1,
306
587
  duration: REDUCED_FADE_MS,
@@ -314,7 +595,7 @@ var _CenteredModal = React4.forwardRef(
314
595
  anim.start();
315
596
  return () => anim.stop();
316
597
  }, [progress, reduced]);
317
- const close = React4.useCallback(() => {
598
+ const close = React7.useCallback(() => {
318
599
  if (closingRef.current) return;
319
600
  closingRef.current = true;
320
601
  reactNative.Animated.timing(progress, {
@@ -327,15 +608,15 @@ var _CenteredModal = React4.forwardRef(
327
608
  onDismiss();
328
609
  });
329
610
  }, [progress, onDismiss, reduced]);
330
- React4.useImperativeHandle(ref, () => ({ close }), [close]);
331
- React4.useEffect(() => {
611
+ React7.useImperativeHandle(ref, () => ({ close }), [close]);
612
+ React7.useEffect(() => {
332
613
  const sub = reactNative.BackHandler.addEventListener("hardwareBackPress", () => {
333
614
  if (dismissible) close();
334
615
  return true;
335
616
  });
336
617
  return () => sub.remove();
337
618
  }, [dismissible, close]);
338
- const onBackdrop = React4.useCallback(() => {
619
+ const onBackdrop = React7.useCallback(() => {
339
620
  if (dismissible) close();
340
621
  }, [dismissible, close]);
341
622
  const backdropOpacity = progress.interpolate({
@@ -358,25 +639,25 @@ var _CenteredModal = React4.forwardRef(
358
639
  /* @__PURE__ */ jsxRuntime.jsx(
359
640
  reactNative.Animated.View,
360
641
  {
361
- style: [styles2.backdrop, { opacity: backdropOpacity }],
642
+ style: [styles5.backdrop, { opacity: backdropOpacity }],
362
643
  pointerEvents: "none"
363
644
  }
364
645
  ),
365
646
  /* @__PURE__ */ jsxRuntime.jsx(
366
647
  reactNative.Pressable,
367
648
  {
368
- style: styles2.fill,
649
+ style: styles5.fill,
369
650
  onPress: onBackdrop,
370
651
  accessibilityRole: "button",
371
652
  accessibilityLabel: "Dismiss",
372
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNativeSafeAreaContext.SafeAreaView, { style: styles2.center, pointerEvents: "box-none", children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => {
373
- }, style: styles2.cardWrap, children: /* @__PURE__ */ jsxRuntime.jsx(
653
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNativeSafeAreaContext.SafeAreaView, { style: styles5.center, pointerEvents: "box-none", children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => {
654
+ }, style: styles5.cardWrap, children: /* @__PURE__ */ jsxRuntime.jsx(
374
655
  reactNative.Animated.View,
375
656
  {
376
657
  accessibilityViewIsModal: true,
377
658
  accessibilityRole: "alert",
378
659
  style: [
379
- styles2.card,
660
+ styles5.card,
380
661
  {
381
662
  backgroundColor: theme.colors.surface,
382
663
  borderRadius: theme.radius.lg,
@@ -397,8 +678,8 @@ var _CenteredModal = React4.forwardRef(
397
678
  }
398
679
  );
399
680
  _CenteredModal.displayName = "CenteredModal";
400
- var CenteredModal = React4__default.default.memo(_CenteredModal);
401
- var styles2 = reactNative.StyleSheet.create({
681
+ var CenteredModal = React7__default.default.memo(_CenteredModal);
682
+ var styles5 = reactNative.StyleSheet.create({
402
683
  fill: { flex: 1 },
403
684
  backdrop: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "#000" },
404
685
  center: { flex: 1, alignItems: "center", justifyContent: "center", padding: 24 },
@@ -463,6 +744,26 @@ var submitReview = (target, review) => {
463
744
  } catch {
464
745
  }
465
746
  };
747
+ var fetchReviewDecision = async (target, options = {}) => {
748
+ if (!(target == null ? void 0 : target.serverUrl)) return null;
749
+ try {
750
+ const base = target.serverUrl.replace(/\/$/, "");
751
+ const params = new URLSearchParams();
752
+ if (options.sessionId) params.set("session_id", options.sessionId);
753
+ if (options.deviceKey) params.set("device_key", options.deviceKey);
754
+ const qs = params.toString();
755
+ const url = `${base}/v1/reviews/decision${qs ? `?${qs}` : ""}`;
756
+ const headers = {};
757
+ if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
758
+ const res = await fetch(url, { headers });
759
+ if (!res || !res.ok) return null;
760
+ const json = await res.json();
761
+ if (!json || typeof json.fire !== "boolean") return null;
762
+ return json;
763
+ } catch {
764
+ return null;
765
+ }
766
+ };
466
767
  var reportAppEvent = (target, name, options = {}) => {
467
768
  if (!(target == null ? void 0 : target.serverUrl) || !name) return;
468
769
  try {
@@ -487,16 +788,6 @@ var reportAppEvent = (target, name, options = {}) => {
487
788
  } catch {
488
789
  }
489
790
  };
490
- var mergeThemeOver = (base, override) => {
491
- if (!override) return base;
492
- return {
493
- colors: { ...base.colors, ...override.colors },
494
- fonts: { ...base.fonts, ...override.fonts },
495
- radius: { ...base.radius, ...override.radius },
496
- spacing: { ...base.spacing, ...override.spacing },
497
- button: { ...base.button, ...override.button }
498
- };
499
- };
500
791
  var _ReviewGate = ({
501
792
  appName,
502
793
  store,
@@ -512,36 +803,60 @@ var _ReviewGate = ({
512
803
  id = "default"
513
804
  }) => {
514
805
  const active = useOnboardingTheme();
515
- const t = React4.useMemo(() => mergeThemeOver(active, themeOverride), [active, themeOverride]);
806
+ const t = React7.useMemo(() => mergeThemeOver(active, themeOverride), [active, themeOverride]);
516
807
  const mode = resolvePresentation(presentation);
517
- const modalRef = React4__default.default.useRef(null);
518
- const [phase, setPhase] = React4.useState("rating");
519
- const [stars, setStars] = React4.useState(0);
520
- const [hovered, setHovered] = React4.useState(0);
521
- const [feedback, setFeedback] = React4.useState("");
522
- const [suggestion, setSuggestion] = React4.useState("");
523
- const shownRef = React4__default.default.useRef(false);
524
- const onShownRef = React4__default.default.useRef(onShown);
525
- const onEventRef = React4__default.default.useRef(onEvent);
808
+ const modalRef = React7__default.default.useRef(null);
809
+ const [phase, setPhase] = React7.useState("rating");
810
+ const [stars, setStars] = React7.useState(0);
811
+ const [hovered, setHovered] = React7.useState(0);
812
+ const [feedback, setFeedback] = React7.useState("");
813
+ const [suggestion, setSuggestion] = React7.useState("");
814
+ const shownRef = React7__default.default.useRef(false);
815
+ const onShownRef = React7__default.default.useRef(onShown);
816
+ const onEventRef = React7__default.default.useRef(onEvent);
526
817
  onShownRef.current = onShown;
527
818
  onEventRef.current = onEvent;
528
- React4__default.default.useEffect(() => {
819
+ React7__default.default.useEffect(() => {
529
820
  var _a, _b;
530
821
  if (shownRef.current) return;
531
822
  shownRef.current = true;
532
823
  (_a = onShownRef.current) == null ? void 0 : _a.call(onShownRef);
533
824
  (_b = onEventRef.current) == null ? void 0 : _b.call(onEventRef, { name: "review_prompt_shown", id });
534
825
  }, []);
535
- const resolveTimer = React4__default.default.useRef(null);
536
- React4__default.default.useEffect(() => {
826
+ const resolveTimer = React7__default.default.useRef(null);
827
+ React7__default.default.useEffect(() => {
537
828
  return () => {
538
829
  if (resolveTimer.current) clearTimeout(resolveTimer.current);
539
830
  };
540
831
  }, []);
541
- const resolve = React4.useCallback(() => {
832
+ const resolve = React7.useCallback(() => {
542
833
  onResolved == null ? void 0 : onResolved();
543
834
  }, [onResolved]);
544
- const resolveWithDelay = React4.useCallback(() => {
835
+ const postedRef = React7__default.default.useRef(false);
836
+ const postOnce = React7.useCallback(
837
+ (body) => {
838
+ if (postedRef.current) return;
839
+ postedRef.current = true;
840
+ submitReview(target, body);
841
+ },
842
+ [target]
843
+ );
844
+ const abandonRef = React7__default.default.useRef(() => {
845
+ });
846
+ abandonRef.current = () => {
847
+ if (postedRef.current || stars < 1) return;
848
+ postOnce(
849
+ buildReviewSubmission({
850
+ stars,
851
+ feedbackText: feedback,
852
+ suggestion,
853
+ sessionId,
854
+ meta: { ...meta, abandoned: true }
855
+ })
856
+ );
857
+ };
858
+ React7__default.default.useEffect(() => () => abandonRef.current(), []);
859
+ const resolveWithDelay = React7.useCallback(() => {
545
860
  if (resolveTimer.current) clearTimeout(resolveTimer.current);
546
861
  resolveTimer.current = setTimeout(() => {
547
862
  var _a;
@@ -552,22 +867,32 @@ var _ReviewGate = ({
552
867
  }
553
868
  }, 1500);
554
869
  }, [mode, onResolved]);
555
- const pickRating = React4.useCallback(
870
+ const pickRating = React7.useCallback(
556
871
  async (value) => {
557
872
  setStars(value);
873
+ playHaptic("selection");
558
874
  onEvent == null ? void 0 : onEvent({ name: "review_rating_selected", id, stars: value });
559
875
  if (routeRating(value) === "store") {
560
876
  onEvent == null ? void 0 : onEvent({ name: "store_review_requested", id, stars: value });
561
- await requestStoreReview(store);
877
+ const route = await requestStoreReview(store);
878
+ postOnce(
879
+ buildReviewSubmission({
880
+ stars: value,
881
+ sessionId,
882
+ meta: { ...meta, store_route: route }
883
+ })
884
+ );
562
885
  setPhase("thanks");
563
886
  resolveWithDelay();
564
887
  } else {
565
888
  setPhase("feedback");
566
889
  }
567
890
  },
568
- [onEvent, id, store, resolveWithDelay]
891
+ [onEvent, id, store, postOnce, sessionId, meta, resolveWithDelay]
569
892
  );
570
- const sendFeedback = React4.useCallback(() => {
893
+ const feedbackReady = feedback.trim().length > 0;
894
+ const sendFeedback = React7.useCallback(() => {
895
+ if (!feedbackReady) return;
571
896
  const body = buildReviewSubmission({
572
897
  stars,
573
898
  feedbackText: feedback,
@@ -575,21 +900,21 @@ var _ReviewGate = ({
575
900
  sessionId,
576
901
  meta
577
902
  });
578
- submitReview(target, body);
903
+ postOnce(body);
579
904
  onEvent == null ? void 0 : onEvent({ name: "review_feedback_submitted", id, stars });
905
+ playHaptic("success");
580
906
  setPhase("thanks");
581
907
  resolveWithDelay();
582
- }, [stars, feedback, suggestion, sessionId, meta, target, onEvent, id, resolveWithDelay]);
583
- const dismiss = React4.useCallback(() => {
908
+ }, [feedbackReady, stars, feedback, suggestion, sessionId, meta, postOnce, onEvent, id, resolveWithDelay]);
909
+ const dismiss = React7.useCallback(() => {
584
910
  setPhase("thanks");
585
911
  resolveWithDelay();
586
912
  }, [resolveWithDelay]);
587
- const handleDismissTap = React4.useCallback(() => {
913
+ const handleDismissTap = React7.useCallback(() => {
588
914
  var _a;
589
915
  if (mode === "modal") (_a = modalRef.current) == null ? void 0 : _a.close();
590
916
  else dismiss();
591
917
  }, [mode, dismiss]);
592
- const title = phase === "feedback" ? "What should we improve?" : phase === "thanks" ? "Thank you" : appName ? `Enjoying ${appName}?` : "How's your experience?";
593
918
  const surface = {
594
919
  backgroundColor: t.colors.surface,
595
920
  borderRadius: t.radius.lg,
@@ -597,113 +922,121 @@ var _ReviewGate = ({
597
922
  gap: t.spacing.md
598
923
  };
599
924
  const feedbackBox = feedbackInputHeights(t.fonts, t.spacing.md);
600
- const content = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
601
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [headingStyle(t.fonts), { color: t.colors.text }], children: title }),
602
- phase === "rating" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
603
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [captionStyle(t.fonts), { color: t.colors.textMuted }], children: "Tap a star to let us know." }),
604
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles3.stars, children: [1, 2, 3, 4, 5].map((n) => {
605
- const filled = n <= (hovered || stars);
606
- return /* @__PURE__ */ jsxRuntime.jsx(
607
- reactNative.Pressable,
608
- {
609
- onPressIn: () => setHovered(n),
610
- onPressOut: () => setHovered(0),
611
- onPress: () => void pickRating(n),
612
- accessibilityRole: "button",
613
- accessibilityLabel: `${n} star${n > 1 ? "s" : ""}`,
614
- hitSlop: 8,
615
- children: /* @__PURE__ */ jsxRuntime.jsx(
616
- reactNative.Text,
617
- {
618
- style: [
619
- styles3.star,
620
- { color: filled ? t.colors.primary : t.colors.border }
621
- ],
622
- children: filled ? "\u2605" : "\u2606"
623
- }
624
- )
625
- },
626
- n
627
- );
628
- }) }),
629
- dismissible && /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: handleDismissTap, hitSlop: 8, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [captionStyle(t.fonts), styles3.link, { color: t.colors.textMuted }], children: "Not now" }) })
630
- ] }),
631
- phase === "feedback" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
632
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [captionStyle(t.fonts), { color: t.colors.textMuted }], children: "A few words is plenty. Both boxes are optional, so send whatever you like." }),
633
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [captionStyle(t.fonts), { color: t.colors.textMuted }], children: "Why this rating? (optional)" }),
634
- /* @__PURE__ */ jsxRuntime.jsx(
635
- reactNative.TextInput,
636
- {
637
- value: feedback,
638
- onChangeText: setFeedback,
639
- placeholder: "e.g. too many steps",
640
- placeholderTextColor: t.colors.textMuted,
641
- multiline: true,
642
- textAlignVertical: "top",
643
- style: [
644
- bodyStyle(t.fonts),
645
- styles3.input,
646
- {
647
- color: t.colors.text,
648
- backgroundColor: t.colors.background,
649
- borderRadius: t.radius.md,
650
- borderColor: t.colors.border,
651
- padding: t.spacing.md,
652
- minHeight: feedbackBox.minHeight,
653
- maxHeight: feedbackBox.maxHeight
654
- }
655
- ]
656
- }
657
- ),
658
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [captionStyle(t.fonts), { color: t.colors.textMuted }], children: "What can we do better? (optional)" }),
659
- /* @__PURE__ */ jsxRuntime.jsx(
660
- reactNative.TextInput,
661
- {
662
- value: suggestion,
663
- onChangeText: setSuggestion,
664
- placeholder: "e.g. save my place between steps",
665
- placeholderTextColor: t.colors.textMuted,
666
- style: [
667
- bodyStyle(t.fonts),
668
- styles3.input,
669
- {
670
- color: t.colors.text,
671
- backgroundColor: t.colors.background,
672
- borderRadius: t.radius.md,
673
- borderColor: t.colors.border,
674
- paddingHorizontal: t.spacing.md,
675
- paddingVertical: t.spacing.sm,
676
- minHeight: t.button.height
677
- }
678
- ]
679
- }
680
- ),
681
- /* @__PURE__ */ jsxRuntime.jsx(
682
- Button,
925
+ const linkStyle = [captionStyle(t.fonts), styles6.link, { color: t.colors.textMuted }];
926
+ const ratingPane = /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { gap: t.spacing.md }, children: [
927
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [headingStyle(t.fonts), { color: t.colors.text }], children: appName ? `Enjoying ${appName}?` : "How's your experience?" }),
928
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [captionStyle(t.fonts), { color: t.colors.textMuted }], children: "Tap a star to let us know." }),
929
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles6.stars, children: [1, 2, 3, 4, 5].map((n) => {
930
+ const filled = n <= (hovered || stars);
931
+ return /* @__PURE__ */ jsxRuntime.jsx(
932
+ reactNative.Pressable,
683
933
  {
684
- title: "Send feedback",
685
- onPress: sendFeedback,
686
- variant: "primary",
687
- fullWidth: true
688
- }
689
- ),
690
- dismissible && /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: handleDismissTap, hitSlop: 8, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [captionStyle(t.fonts), styles3.link, { color: t.colors.textMuted }], children: "Skip" }) })
691
- ] }),
692
- phase === "thanks" && /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted }], children: "We appreciate you. This helps us make it better." })
934
+ onPressIn: () => setHovered(n),
935
+ onPressOut: () => setHovered(0),
936
+ onPress: () => void pickRating(n),
937
+ accessibilityRole: "button",
938
+ accessibilityLabel: `${n} star${n > 1 ? "s" : ""}`,
939
+ hitSlop: 8,
940
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles6.star, { color: filled ? t.colors.primary : t.colors.border }], children: filled ? "\u2605" : "\u2606" })
941
+ },
942
+ n
943
+ );
944
+ }) }),
945
+ dismissible && /* @__PURE__ */ jsxRuntime.jsx(
946
+ reactNative.Pressable,
947
+ {
948
+ onPress: handleDismissTap,
949
+ hitSlop: 8,
950
+ accessibilityRole: "button",
951
+ accessibilityLabel: "Dismiss the review prompt",
952
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: linkStyle, children: "Not now" })
953
+ }
954
+ )
955
+ ] });
956
+ const feedbackPane = /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { gap: t.spacing.md }, children: [
957
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [headingStyle(t.fonts), { color: t.colors.text }], children: "What should we improve?" }),
958
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [captionStyle(t.fonts), { color: t.colors.textMuted }], children: "A few words is plenty \u2014 tell us what went wrong and we'll fix it." }),
959
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [captionStyle(t.fonts), { color: t.colors.textMuted }], children: "Why this rating?" }),
960
+ /* @__PURE__ */ jsxRuntime.jsx(
961
+ reactNative.TextInput,
962
+ {
963
+ value: feedback,
964
+ onChangeText: setFeedback,
965
+ placeholder: "e.g. too many steps",
966
+ placeholderTextColor: t.colors.textMuted,
967
+ accessibilityLabel: "Why this rating?",
968
+ multiline: true,
969
+ textAlignVertical: "top",
970
+ style: [
971
+ bodyStyle(t.fonts),
972
+ styles6.input,
973
+ {
974
+ color: t.colors.text,
975
+ backgroundColor: t.colors.background,
976
+ borderRadius: t.radius.md,
977
+ borderColor: t.colors.border,
978
+ padding: t.spacing.md,
979
+ minHeight: feedbackBox.minHeight,
980
+ maxHeight: feedbackBox.maxHeight
981
+ }
982
+ ]
983
+ }
984
+ ),
985
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [captionStyle(t.fonts), { color: t.colors.textMuted }], children: "What can we do better? (optional)" }),
986
+ /* @__PURE__ */ jsxRuntime.jsx(
987
+ reactNative.TextInput,
988
+ {
989
+ value: suggestion,
990
+ onChangeText: setSuggestion,
991
+ placeholder: "e.g. save my place between steps",
992
+ placeholderTextColor: t.colors.textMuted,
993
+ accessibilityLabel: "What can we do better? (optional)",
994
+ style: [
995
+ bodyStyle(t.fonts),
996
+ styles6.input,
997
+ {
998
+ color: t.colors.text,
999
+ backgroundColor: t.colors.background,
1000
+ borderRadius: t.radius.md,
1001
+ borderColor: t.colors.border,
1002
+ paddingHorizontal: t.spacing.md,
1003
+ paddingVertical: t.spacing.sm,
1004
+ minHeight: t.button.height
1005
+ }
1006
+ ]
1007
+ }
1008
+ ),
1009
+ /* @__PURE__ */ jsxRuntime.jsx(
1010
+ Button,
1011
+ {
1012
+ title: "Send feedback",
1013
+ onPress: sendFeedback,
1014
+ variant: "primary",
1015
+ fullWidth: true,
1016
+ disabled: !feedbackReady
1017
+ }
1018
+ )
693
1019
  ] });
1020
+ const thanksPane = /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { gap: t.spacing.md }, children: [
1021
+ /* @__PURE__ */ jsxRuntime.jsx(DoneCheck, { theme: t }),
1022
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [headingStyle(t.fonts), styles6.centerText, { color: t.colors.text }], children: "Thank you" }),
1023
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), styles6.centerText, { color: t.colors.textMuted }], children: "We appreciate you. This helps us make it better." })
1024
+ ] });
1025
+ const content = /* @__PURE__ */ jsxRuntime.jsx(ModalPaneHost, { paneKey: phase, children: phase === "rating" ? ratingPane : phase === "feedback" ? feedbackPane : thanksPane });
694
1026
  if (mode === "modal") {
695
1027
  return /* @__PURE__ */ jsxRuntime.jsx(CenteredModal, { ref: modalRef, theme: t, dismissible, onDismiss: resolve, children: content });
696
1028
  }
697
- return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles3.card, surface], children: content });
1029
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles6.card, surface], children: content });
698
1030
  };
699
- var ReviewGate = React4__default.default.memo(_ReviewGate);
1031
+ var ReviewGate = React7__default.default.memo(_ReviewGate);
700
1032
  ReviewGate.displayName = "ReviewGate";
701
- var styles3 = reactNative.StyleSheet.create({
1033
+ var styles6 = reactNative.StyleSheet.create({
702
1034
  card: { width: "100%" },
703
1035
  stars: { flexDirection: "row", justifyContent: "center", gap: 8 },
704
1036
  star: { fontSize: 40, lineHeight: 46 },
705
1037
  input: { borderWidth: 1 },
706
- link: { textAlign: "center", textDecorationLine: "underline" }
1038
+ link: { textAlign: "center", textDecorationLine: "underline" },
1039
+ centerText: { textAlign: "center" }
707
1040
  });
708
1041
 
709
1042
  // src/coachmarks/runtime.ts
@@ -845,16 +1178,16 @@ var fetchWireFeatures = async (config) => {
845
1178
 
846
1179
  // src/features/useWireFeatures.ts
847
1180
  var useStableValue = (value, isEqual) => {
848
- const ref = React4.useRef(value);
1181
+ const ref = React7.useRef(value);
849
1182
  if (!isEqual(ref.current, value)) ref.current = value;
850
1183
  return ref.current;
851
1184
  };
852
1185
  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);
853
1186
  var useWireFeatures = (config) => {
854
1187
  const stableConfig = useStableValue(config, sameFetchKey);
855
- const [flags, setFlags] = React4.useState(defaultWireFeatures);
1188
+ const [flags, setFlags] = React7.useState(defaultWireFeatures);
856
1189
  const canFetch = !!(stableConfig == null ? void 0 : stableConfig.serverUrl) && !!(stableConfig == null ? void 0 : stableConfig.apiKey);
857
- React4.useEffect(() => {
1190
+ React7.useEffect(() => {
858
1191
  if (!canFetch) {
859
1192
  setFlags((prev) => featuresEqual(prev, defaultWireFeatures) ? prev : defaultWireFeatures);
860
1193
  return;
@@ -873,10 +1206,10 @@ var useWireFeatures = (config) => {
873
1206
  var CONTEXT_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.context");
874
1207
  var globalObj = global;
875
1208
  if (!globalObj[CONTEXT_SYMBOL]) {
876
- globalObj[CONTEXT_SYMBOL] = React4.createContext(null);
1209
+ globalObj[CONTEXT_SYMBOL] = React7.createContext(null);
877
1210
  }
878
1211
  var WireFeaturesContext = globalObj[CONTEXT_SYMBOL];
879
- var useWireFeaturesContext = () => React4.useContext(WireFeaturesContext);
1212
+ var useWireFeaturesContext = () => React7.useContext(WireFeaturesContext);
880
1213
  var useResolvedFeatures = (options) => {
881
1214
  var _a, _b, _c;
882
1215
  const ctx = useWireFeaturesContext();
@@ -931,7 +1264,7 @@ var writeInt = (storage, key, value) => {
931
1264
 
932
1265
  // src/reviews/useReviewGate.ts
933
1266
  var useStableValue2 = (value, isEqual) => {
934
- const ref = React4.useRef(value);
1267
+ const ref = React7.useRef(value);
935
1268
  if (!isEqual(ref.current, value)) ref.current = value;
936
1269
  return ref.current;
937
1270
  };
@@ -954,7 +1287,7 @@ var useReviewGate = ({
954
1287
  const seenKey = reviewSeenKey(config.id, config.oncePerVersion === false ? void 0 : config.appVersion);
955
1288
  const lastKey = reviewLastShownKey(config.id);
956
1289
  const sessionsKey = reviewSessionsKey(config.id);
957
- const sessions = React4.useState(() => {
1290
+ const sessions = React7.useState(() => {
958
1291
  const store = resolveStorage(storage);
959
1292
  const next = readInt(store, sessionsKey) + 1;
960
1293
  writeInt(store, sessionsKey, next);
@@ -963,8 +1296,8 @@ var useReviewGate = ({
963
1296
  const hasServerDecision = decision != null;
964
1297
  const timeoutMs = config.timeoutFallbackMs;
965
1298
  const waits = !hasServerDecision && timeoutMs != null && timeoutMs > 0;
966
- const [elapsed, setElapsed] = React4.useState(!waits);
967
- React4.useEffect(() => {
1299
+ const [elapsed, setElapsed] = React7.useState(!waits);
1300
+ React7.useEffect(() => {
968
1301
  if (!waits) {
969
1302
  setElapsed(true);
970
1303
  return;
@@ -972,7 +1305,7 @@ var useReviewGate = ({
972
1305
  const timer = setTimeout(() => setElapsed(true), timeoutMs);
973
1306
  return () => clearTimeout(timer);
974
1307
  }, [waits, timeoutMs]);
975
- const { visible, verdict } = React4.useMemo(() => {
1308
+ const { visible, verdict } = React7.useMemo(() => {
976
1309
  if (!featureEnabled) return { visible: false, verdict: { fire: false, reason: "feature_disabled" } };
977
1310
  if (testing2) return { visible: true, verdict: { fire: true, reason: "testing" } };
978
1311
  const store = resolveStorage(storage);
@@ -988,16 +1321,16 @@ var useReviewGate = ({
988
1321
  const ready = hasServerDecision || elapsed;
989
1322
  return { visible: ready && resolved.fire && !alreadySeen, verdict: resolved };
990
1323
  }, [featureEnabled, testing2, config, decision, events, sessions, elapsed, hasServerDecision, seenKey, lastKey, storage, isTesting]);
991
- const shownRef = React4.useRef(false);
992
- const markShown = React4.useCallback(() => {
1324
+ const shownRef = React7.useRef(false);
1325
+ const markShown = React7.useCallback(() => {
993
1326
  if (shownRef.current) return;
994
1327
  shownRef.current = true;
995
1328
  writeInt(resolveStorage(storage), lastKey, Date.now());
996
1329
  }, [storage, lastKey]);
997
- const markResolved = React4.useCallback(() => {
1330
+ const markResolved = React7.useCallback(() => {
998
1331
  markSeenGate(seenKey, storage, isTesting);
999
1332
  }, [seenKey, storage, isTesting]);
1000
- return React4.useMemo(
1333
+ return React7.useMemo(
1001
1334
  () => ({ visible, decision: verdict, markShown, markResolved }),
1002
1335
  [visible, verdict, markShown, markResolved]
1003
1336
  );
@@ -1014,6 +1347,7 @@ exports.decideReview = decideReview;
1014
1347
  exports.entranceFrame = centeredModalFrame;
1015
1348
  exports.evaluateGate = evaluateGate;
1016
1349
  exports.feedbackInputHeights = feedbackInputHeights;
1350
+ exports.fetchReviewDecision = fetchReviewDecision;
1017
1351
  exports.openStoreListing = openStoreListing;
1018
1352
  exports.reportAppEvent = reportAppEvent;
1019
1353
  exports.requestStoreReview = requestStoreReview;