@wireai/activation 0.1.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 (142) hide show
  1. package/AGENTS.md +73 -0
  2. package/CHANGELOG.md +572 -0
  3. package/INTEGRATION_PROMPT.md +55 -0
  4. package/LICENSE +21 -0
  5. package/README.md +923 -0
  6. package/dist/CenteredModal-C3qQBHsA.d.mts +38 -0
  7. package/dist/CenteredModal-Cdgns6--.d.ts +38 -0
  8. package/dist/coachmarks/index.d.mts +352 -0
  9. package/dist/coachmarks/index.d.ts +352 -0
  10. package/dist/coachmarks/index.js +944 -0
  11. package/dist/coachmarks/index.js.map +1 -0
  12. package/dist/coachmarks/index.mjs +918 -0
  13. package/dist/coachmarks/index.mjs.map +1 -0
  14. package/dist/decision-CFvGY6nP.d.ts +228 -0
  15. package/dist/decision-Cl8OFYzu.d.mts +228 -0
  16. package/dist/index.d.mts +1390 -0
  17. package/dist/index.d.ts +1390 -0
  18. package/dist/index.js +3260 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/index.mjs +3186 -0
  21. package/dist/index.mjs.map +1 -0
  22. package/dist/questionnaire/index.d.mts +249 -0
  23. package/dist/questionnaire/index.d.ts +249 -0
  24. package/dist/questionnaire/index.js +922 -0
  25. package/dist/questionnaire/index.js.map +1 -0
  26. package/dist/questionnaire/index.mjs +906 -0
  27. package/dist/questionnaire/index.mjs.map +1 -0
  28. package/dist/reviews/index.d.mts +213 -0
  29. package/dist/reviews/index.d.ts +213 -0
  30. package/dist/reviews/index.js +1001 -0
  31. package/dist/reviews/index.js.map +1 -0
  32. package/dist/reviews/index.mjs +971 -0
  33. package/dist/reviews/index.mjs.map +1 -0
  34. package/dist/showcase/index.d.mts +119 -0
  35. package/dist/showcase/index.d.ts +119 -0
  36. package/dist/showcase/index.js +660 -0
  37. package/dist/showcase/index.js.map +1 -0
  38. package/dist/showcase/index.mjs +651 -0
  39. package/dist/showcase/index.mjs.map +1 -0
  40. package/dist/types-BKfpdZzX.d.mts +84 -0
  41. package/dist/types-BKfpdZzX.d.ts +84 -0
  42. package/dist/types-CMuOexw0.d.mts +116 -0
  43. package/dist/types-CMuOexw0.d.ts +116 -0
  44. package/dist/types-GL_hQ0TN.d.mts +46 -0
  45. package/dist/types-GL_hQ0TN.d.ts +46 -0
  46. package/llms.txt +36 -0
  47. package/metro/index.d.ts +32 -0
  48. package/metro/index.js +83 -0
  49. package/package.json +171 -0
  50. package/src/DemoOnboarding.tsx +133 -0
  51. package/src/OnboardingFlow.tsx +504 -0
  52. package/src/WireOnboarding.tsx +248 -0
  53. package/src/analytics/analyticsEvent.ts +69 -0
  54. package/src/analytics/reportClientEvent.ts +119 -0
  55. package/src/analytics/sendPreview.ts +73 -0
  56. package/src/attribution/attribution.ts +46 -0
  57. package/src/cards/ChipSelectCard.tsx +265 -0
  58. package/src/cards/InterstitialCard.tsx +299 -0
  59. package/src/cards/NumberStepperCard.tsx +121 -0
  60. package/src/cards/SelectionCard.tsx +270 -0
  61. package/src/cards/StatusCard.tsx +170 -0
  62. package/src/cards/TextInputCard.tsx +147 -0
  63. package/src/cards/index.ts +23 -0
  64. package/src/coachmarks/CoachmarkOverlayHost.tsx +44 -0
  65. package/src/coachmarks/CoachmarkProvider.tsx +108 -0
  66. package/src/coachmarks/GestureHint.tsx +331 -0
  67. package/src/coachmarks/SpotlightOverlay.tsx +254 -0
  68. package/src/coachmarks/coachmarkAnchorRegistry.ts +70 -0
  69. package/src/coachmarks/coachmarkOverlayStore.ts +63 -0
  70. package/src/coachmarks/index.ts +62 -0
  71. package/src/coachmarks/runtime.ts +103 -0
  72. package/src/coachmarks/selectTourSteps.ts +45 -0
  73. package/src/coachmarks/types.ts +58 -0
  74. package/src/coachmarks/useCoachmarkAnchor.ts +49 -0
  75. package/src/coachmarks/useCoachmarkTour.ts +200 -0
  76. package/src/components/AnimatedSparkle.tsx +101 -0
  77. package/src/components/Button.tsx +129 -0
  78. package/src/components/CardHandoff.tsx +212 -0
  79. package/src/components/CardLayout.tsx +103 -0
  80. package/src/components/CenteredModal.tsx +184 -0
  81. package/src/components/CompletionView.tsx +186 -0
  82. package/src/components/DoneBlock.tsx +37 -0
  83. package/src/components/ErrorBlock.tsx +42 -0
  84. package/src/components/Illustration.tsx +36 -0
  85. package/src/components/LoadingBlock.tsx +182 -0
  86. package/src/components/LoadingScreen.tsx +35 -0
  87. package/src/components/OnboardingScaffold.tsx +105 -0
  88. package/src/components/StepProgress.tsx +85 -0
  89. package/src/components/loaderChrome.ts +28 -0
  90. package/src/config/onboardingFlag.ts +39 -0
  91. package/src/config/wireConfigFromEnv.ts +52 -0
  92. package/src/device/deviceContext.ts +158 -0
  93. package/src/features/WireFeaturesProvider.tsx +81 -0
  94. package/src/features/cache.ts +92 -0
  95. package/src/features/defaults.ts +67 -0
  96. package/src/features/fetchWireFeatures.ts +86 -0
  97. package/src/features/index.ts +39 -0
  98. package/src/features/types.ts +61 -0
  99. package/src/features/useWireFeatures.ts +57 -0
  100. package/src/identity/userIdentity.ts +100 -0
  101. package/src/illustrations/defaultIllustrations.tsx +99 -0
  102. package/src/index.ts +159 -0
  103. package/src/motion/centeredModalMotion.ts +45 -0
  104. package/src/motion/motionSpec.ts +133 -0
  105. package/src/motion/useReducedMotion.ts +43 -0
  106. package/src/questionnaire/QuestionnaireGate.tsx +235 -0
  107. package/src/questionnaire/decision.ts +63 -0
  108. package/src/questionnaire/index.ts +54 -0
  109. package/src/questionnaire/runtime.ts +20 -0
  110. package/src/questionnaire/transport.ts +83 -0
  111. package/src/questionnaire/types.ts +182 -0
  112. package/src/questionnaire/useQuestionnaireGate.ts +159 -0
  113. package/src/reviews/ReviewGate.tsx +316 -0
  114. package/src/reviews/ReviewModal.tsx +13 -0
  115. package/src/reviews/decision.ts +120 -0
  116. package/src/reviews/equality.ts +54 -0
  117. package/src/reviews/expo-store-review.d.ts +16 -0
  118. package/src/reviews/feedbackForm.ts +41 -0
  119. package/src/reviews/index.ts +78 -0
  120. package/src/reviews/presentation.ts +29 -0
  121. package/src/reviews/runtime.ts +45 -0
  122. package/src/reviews/storeReview.ts +62 -0
  123. package/src/reviews/transport.ts +89 -0
  124. package/src/reviews/types.ts +172 -0
  125. package/src/reviews/useReviewGate.ts +137 -0
  126. package/src/session/persistedSession.ts +143 -0
  127. package/src/session-analytics/index.ts +18 -0
  128. package/src/session-analytics/reportSessionStart.ts +144 -0
  129. package/src/session-analytics/useSessionStart.ts +114 -0
  130. package/src/showcase/FeatureShowcase.tsx +233 -0
  131. package/src/showcase/index.ts +19 -0
  132. package/src/showcase/selectShowcaseSlides.ts +45 -0
  133. package/src/showcase/showcaseColors.ts +91 -0
  134. package/src/showcase/types.ts +74 -0
  135. package/src/theme/ThemeContext.tsx +38 -0
  136. package/src/theme/defaultTheme.ts +70 -0
  137. package/src/theme/themeFromBrand.ts +93 -0
  138. package/src/theme/types.ts +88 -0
  139. package/src/theme/typography.ts +46 -0
  140. package/src/types.ts +215 -0
  141. package/src/utils/deriveAnswers.ts +43 -0
  142. package/src/utils/readProgress.ts +33 -0
@@ -0,0 +1,922 @@
1
+ 'use strict';
2
+
3
+ var React4 = require('react');
4
+ var reactNative = require('react-native');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+ var reactNativeSafeAreaContext = require('react-native-safe-area-context');
7
+
8
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
+
10
+ var React4__default = /*#__PURE__*/_interopDefault(React4);
11
+
12
+ // src/questionnaire/QuestionnaireGate.tsx
13
+
14
+ // src/theme/defaultTheme.ts
15
+ var defaultOnboardingTheme = {
16
+ colors: {
17
+ background: "#FFFFFF",
18
+ surface: "#F7F7F8",
19
+ text: "#101114",
20
+ textMuted: "#6B7280",
21
+ primary: "#111111",
22
+ onPrimary: "#FFFFFF",
23
+ primarySoft: "rgba(17,17,17,0.06)",
24
+ border: "#E4E4E7",
25
+ borderStrong: "#C7C7CC",
26
+ disabled: "#EBEBEB",
27
+ success: "#16A34A",
28
+ warning: "#D97706",
29
+ error: "#DC2626",
30
+ info: "#2563EB"
31
+ },
32
+ fonts: {
33
+ regular: void 0,
34
+ medium: void 0,
35
+ bold: void 0,
36
+ baseSize: 16,
37
+ scale: 1
38
+ },
39
+ radius: {
40
+ sm: 8,
41
+ md: 12,
42
+ lg: 16,
43
+ full: 9999
44
+ },
45
+ spacing: {
46
+ xs: 4,
47
+ sm: 8,
48
+ md: 16,
49
+ lg: 24,
50
+ xl: 32
51
+ },
52
+ // Circled (pill) CTA by default; an app can pass `button: { radius: 12 }` for
53
+ // a rounded rectangle, or override height/paddingX to match its design system.
54
+ button: {
55
+ radius: 9999,
56
+ height: 52,
57
+ paddingX: 24
58
+ }
59
+ };
60
+ var mergeTheme = (override) => {
61
+ return defaultOnboardingTheme;
62
+ };
63
+ var OnboardingThemeContext = React4.createContext(null);
64
+ var useOnboardingTheme = () => {
65
+ const ctx = React4.useContext(OnboardingThemeContext);
66
+ return ctx != null ? ctx : mergeTheme();
67
+ };
68
+
69
+ // src/theme/typography.ts
70
+ var round = (n) => Math.round(n);
71
+ var bodyStyle = (f) => ({
72
+ fontFamily: f.regular,
73
+ fontSize: round(f.baseSize * f.scale),
74
+ lineHeight: round(f.baseSize * f.scale * 1.5)
75
+ });
76
+ var captionStyle = (f) => ({
77
+ fontFamily: f.regular,
78
+ fontSize: round(f.baseSize * 0.8125 * f.scale),
79
+ lineHeight: round(f.baseSize * 0.8125 * f.scale * 1.5)
80
+ });
81
+ var headingStyle = (f) => ({
82
+ fontFamily: f.bold,
83
+ fontSize: round(f.baseSize * 1.25 * f.scale),
84
+ lineHeight: round(f.baseSize * 1.25 * f.scale * 1.3),
85
+ fontWeight: f.bold ? void 0 : "700"
86
+ });
87
+ var buttonLabelStyle = (f) => {
88
+ var _a;
89
+ return {
90
+ fontFamily: (_a = f.bold) != null ? _a : f.medium,
91
+ fontSize: round(f.baseSize * f.scale),
92
+ fontWeight: f.bold ? void 0 : "600"
93
+ };
94
+ };
95
+
96
+ // src/motion/motionSpec.ts
97
+ var REDUCED_FADE_MS = 120;
98
+ var BUTTON_PRESS_SCALE = 0.975;
99
+ var REDUCED_PRESS_OPACITY = 0.85;
100
+ var BUTTON_PRESS_SPRING = { friction: 8, tension: 240 };
101
+ var durationScale = 1;
102
+ var scaledMs = (ms) => Math.round(ms * durationScale);
103
+ var lastKnown = false;
104
+ var useReducedMotion = () => {
105
+ const [reduced, setReduced] = React4.useState(lastKnown);
106
+ React4.useEffect(() => {
107
+ let mounted = true;
108
+ reactNative.AccessibilityInfo.isReduceMotionEnabled().then((value) => {
109
+ lastKnown = value;
110
+ if (mounted) setReduced(value);
111
+ }).catch(() => {
112
+ });
113
+ const sub = reactNative.AccessibilityInfo.addEventListener("reduceMotionChanged", (value) => {
114
+ lastKnown = value;
115
+ setReduced(value);
116
+ });
117
+ return () => {
118
+ mounted = false;
119
+ sub == null ? void 0 : sub.remove();
120
+ };
121
+ }, []);
122
+ return reduced;
123
+ };
124
+ var _Button = ({
125
+ title,
126
+ onPress,
127
+ variant = "primary",
128
+ disabled = false,
129
+ fullWidth = false
130
+ }) => {
131
+ const t = useOnboardingTheme();
132
+ const reduced = useReducedMotion();
133
+ const isPrimary = variant === "primary";
134
+ const pressT = React4.useRef(new reactNative.Animated.Value(0)).current;
135
+ const pressTo = React4.useCallback(
136
+ (to) => {
137
+ if (reduced) {
138
+ reactNative.Animated.timing(pressT, {
139
+ toValue: to,
140
+ duration: scaledMs(REDUCED_FADE_MS),
141
+ useNativeDriver: true
142
+ }).start();
143
+ return;
144
+ }
145
+ reactNative.Animated.spring(pressT, {
146
+ toValue: to,
147
+ friction: BUTTON_PRESS_SPRING.friction,
148
+ tension: BUTTON_PRESS_SPRING.tension,
149
+ useNativeDriver: true
150
+ }).start();
151
+ },
152
+ [pressT, reduced]
153
+ );
154
+ const handlePressIn = React4.useCallback(() => pressTo(1), [pressTo]);
155
+ const handlePressOut = React4.useCallback(() => pressTo(0), [pressTo]);
156
+ const scale = pressT.interpolate({
157
+ inputRange: [0, 1],
158
+ outputRange: [1, reduced ? 1 : BUTTON_PRESS_SCALE]
159
+ });
160
+ const opacity = pressT.interpolate({
161
+ inputRange: [0, 1],
162
+ outputRange: [1, reduced ? REDUCED_PRESS_OPACITY : 1]
163
+ });
164
+ return /* @__PURE__ */ jsxRuntime.jsx(
165
+ reactNative.Pressable,
166
+ {
167
+ onPress: disabled ? void 0 : onPress,
168
+ onPressIn: disabled ? void 0 : handlePressIn,
169
+ onPressOut: disabled ? void 0 : handlePressOut,
170
+ disabled,
171
+ accessibilityRole: "button",
172
+ accessibilityState: { disabled },
173
+ style: fullWidth ? styles.fill : void 0,
174
+ children: /* @__PURE__ */ jsxRuntime.jsx(
175
+ reactNative.Animated.View,
176
+ {
177
+ style: [
178
+ styles.base,
179
+ fullWidth && styles.fill,
180
+ {
181
+ borderRadius: t.button.radius,
182
+ minHeight: t.button.height,
183
+ paddingHorizontal: t.button.paddingX,
184
+ opacity,
185
+ transform: [{ scale }]
186
+ },
187
+ isPrimary ? { backgroundColor: disabled ? t.colors.disabled : t.colors.primary } : { borderWidth: 1.5, borderColor: disabled ? t.colors.border : t.colors.primary }
188
+ ],
189
+ children: /* @__PURE__ */ jsxRuntime.jsx(
190
+ reactNative.Text,
191
+ {
192
+ style: [
193
+ buttonLabelStyle(t.fonts),
194
+ {
195
+ color: disabled ? t.colors.textMuted : isPrimary ? t.colors.onPrimary : t.colors.primary
196
+ }
197
+ ],
198
+ children: title
199
+ }
200
+ )
201
+ }
202
+ )
203
+ }
204
+ );
205
+ };
206
+ var Button = React4__default.default.memo(_Button);
207
+ var styles = reactNative.StyleSheet.create({
208
+ base: { alignItems: "center", justifyContent: "center" },
209
+ fill: { width: "100%" }
210
+ });
211
+
212
+ // src/motion/centeredModalMotion.ts
213
+ var CENTERED_MODAL_ANIM = {
214
+ /** Spring config for the card scale/opacity entrance (mirrors CompletionView's pop). */
215
+ enterSpring: { friction: 7, tension: 80 },
216
+ /** Exit timing in ms (fade + shrink back out before unmount). */
217
+ exitMs: 160,
218
+ /** Card scale at rest (progress 0); grows to 1 at progress 1. */
219
+ scaleFrom: 0.9,
220
+ /** Peak dim of the backdrop at progress 1. */
221
+ backdropOpacity: 0.55
222
+ };
223
+ var _CenteredModal = React4.forwardRef(
224
+ ({ theme, dismissible = true, onDismiss, children }, ref) => {
225
+ const progress = React4.useRef(new reactNative.Animated.Value(0)).current;
226
+ const [open, setOpen] = React4.useState(true);
227
+ const closingRef = React4.useRef(false);
228
+ const reduced = useReducedMotion();
229
+ React4.useEffect(() => {
230
+ const anim = reduced ? reactNative.Animated.timing(progress, {
231
+ toValue: 1,
232
+ duration: REDUCED_FADE_MS,
233
+ useNativeDriver: true
234
+ }) : reactNative.Animated.spring(progress, {
235
+ toValue: 1,
236
+ useNativeDriver: true,
237
+ friction: CENTERED_MODAL_ANIM.enterSpring.friction,
238
+ tension: CENTERED_MODAL_ANIM.enterSpring.tension
239
+ });
240
+ anim.start();
241
+ return () => anim.stop();
242
+ }, [progress, reduced]);
243
+ const close = React4.useCallback(() => {
244
+ if (closingRef.current) return;
245
+ closingRef.current = true;
246
+ reactNative.Animated.timing(progress, {
247
+ toValue: 0,
248
+ duration: reduced ? REDUCED_FADE_MS : CENTERED_MODAL_ANIM.exitMs,
249
+ easing: reactNative.Easing.in(reactNative.Easing.cubic),
250
+ useNativeDriver: true
251
+ }).start(() => {
252
+ setOpen(false);
253
+ onDismiss();
254
+ });
255
+ }, [progress, onDismiss, reduced]);
256
+ React4.useImperativeHandle(ref, () => ({ close }), [close]);
257
+ React4.useEffect(() => {
258
+ const sub = reactNative.BackHandler.addEventListener("hardwareBackPress", () => {
259
+ if (dismissible) close();
260
+ return true;
261
+ });
262
+ return () => sub.remove();
263
+ }, [dismissible, close]);
264
+ const onBackdrop = React4.useCallback(() => {
265
+ if (dismissible) close();
266
+ }, [dismissible, close]);
267
+ const backdropOpacity = progress.interpolate({
268
+ inputRange: [0, 1],
269
+ outputRange: [0, CENTERED_MODAL_ANIM.backdropOpacity]
270
+ });
271
+ const cardScale = reduced ? 1 : progress.interpolate({
272
+ inputRange: [0, 1],
273
+ outputRange: [CENTERED_MODAL_ANIM.scaleFrom, 1]
274
+ });
275
+ return /* @__PURE__ */ jsxRuntime.jsxs(
276
+ reactNative.Modal,
277
+ {
278
+ visible: open,
279
+ transparent: true,
280
+ animationType: "none",
281
+ statusBarTranslucent: true,
282
+ onRequestClose: dismissible ? close : void 0,
283
+ children: [
284
+ /* @__PURE__ */ jsxRuntime.jsx(
285
+ reactNative.Animated.View,
286
+ {
287
+ style: [styles2.backdrop, { opacity: backdropOpacity }],
288
+ pointerEvents: "none"
289
+ }
290
+ ),
291
+ /* @__PURE__ */ jsxRuntime.jsx(
292
+ reactNative.Pressable,
293
+ {
294
+ style: styles2.fill,
295
+ onPress: onBackdrop,
296
+ accessibilityRole: "button",
297
+ accessibilityLabel: "Dismiss",
298
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNativeSafeAreaContext.SafeAreaView, { style: styles2.center, pointerEvents: "box-none", children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => {
299
+ }, style: styles2.cardWrap, children: /* @__PURE__ */ jsxRuntime.jsx(
300
+ reactNative.Animated.View,
301
+ {
302
+ accessibilityViewIsModal: true,
303
+ accessibilityRole: "alert",
304
+ style: [
305
+ styles2.card,
306
+ {
307
+ backgroundColor: theme.colors.surface,
308
+ borderRadius: theme.radius.lg,
309
+ padding: theme.spacing.lg,
310
+ gap: theme.spacing.md,
311
+ opacity: progress,
312
+ transform: [{ scale: cardScale }]
313
+ }
314
+ ],
315
+ children
316
+ }
317
+ ) }) })
318
+ }
319
+ )
320
+ ]
321
+ }
322
+ );
323
+ }
324
+ );
325
+ _CenteredModal.displayName = "CenteredModal";
326
+ var CenteredModal = React4__default.default.memo(_CenteredModal);
327
+ var styles2 = reactNative.StyleSheet.create({
328
+ fill: { flex: 1 },
329
+ backdrop: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "#000" },
330
+ center: { flex: 1, alignItems: "center", justifyContent: "center", padding: 24 },
331
+ cardWrap: { width: "100%", maxWidth: 420 },
332
+ card: {
333
+ width: "100%",
334
+ // Soft elevation so the popup reads as lifted above the app in both themes.
335
+ shadowColor: "#000",
336
+ shadowOffset: { width: 0, height: 8 },
337
+ shadowOpacity: 0.28,
338
+ shadowRadius: 24,
339
+ elevation: 12
340
+ }
341
+ });
342
+
343
+ // src/reviews/feedbackForm.ts
344
+ var FEEDBACK_MIN_LINES = 4;
345
+ var FEEDBACK_MAX_LINES = 5;
346
+ var feedbackInputHeights = (fonts, verticalPadding = 0) => {
347
+ var _a;
348
+ const lineHeight = (_a = bodyStyle(fonts).lineHeight) != null ? _a : Math.round(fonts.baseSize * fonts.scale * 1.5);
349
+ const pad = verticalPadding * 2;
350
+ return {
351
+ minHeight: lineHeight * FEEDBACK_MIN_LINES + pad,
352
+ maxHeight: lineHeight * FEEDBACK_MAX_LINES + pad
353
+ };
354
+ };
355
+
356
+ // src/reviews/decision.ts
357
+ var decideReview = (local, decision) => decision != null ? decision : local;
358
+ var resolveRules = (config) => {
359
+ var _a, _b, _c, _d, _e;
360
+ return {
361
+ enabled: (_a = config.enabled) != null ? _a : true,
362
+ minSessions: (_b = config.minSessions) != null ? _b : 0,
363
+ minEvents: (_c = config.minEvents) != null ? _c : 0,
364
+ cooldownDays: (_d = config.cooldownDays) != null ? _d : 0,
365
+ oncePerVersion: (_e = config.oncePerVersion) != null ? _e : true
366
+ };
367
+ };
368
+ var MS_PER_DAY = 864e5;
369
+ var evaluateGate = (rules, s) => {
370
+ if (!rules.enabled) return { fire: false, reason: "disabled" };
371
+ if (rules.oncePerVersion && s.seen) return { fire: false, reason: "already_shown" };
372
+ if (s.sessions < rules.minSessions) return { fire: false, reason: "min_sessions" };
373
+ if (s.events < rules.minEvents) return { fire: false, reason: "min_events" };
374
+ if (rules.cooldownDays > 0 && s.lastShownAt != null) {
375
+ const elapsedDays = (s.now - s.lastShownAt) / MS_PER_DAY;
376
+ if (elapsedDays < rules.cooldownDays) return { fire: false, reason: "cooldown" };
377
+ }
378
+ return { fire: true, reason: "local_rules_met" };
379
+ };
380
+
381
+ // src/questionnaire/decision.ts
382
+ var decideQuestionnaire = (local, decision) => decideReview(local, decision);
383
+ var buildQuestionnaireSubmission = (input) => {
384
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
385
+ const opinion = input.answers.opinion.trim();
386
+ const improve = (_a = input.answers.improve) == null ? void 0 : _a.trim();
387
+ const suggestions = (_b = input.answers.suggestions) == null ? void 0 : _b.trim();
388
+ return {
389
+ session_id: (_c = input.sessionId) != null ? _c : null,
390
+ answers: {
391
+ opinion,
392
+ improve: improve ? improve : null,
393
+ suggestions: suggestions ? suggestions : null
394
+ },
395
+ meta: {
396
+ user_id: (_e = (_d = input.meta) == null ? void 0 : _d.user_id) != null ? _e : null,
397
+ device_key: (_g = (_f = input.meta) == null ? void 0 : _f.device_key) != null ? _g : null,
398
+ session_count: (_i = (_h = input.meta) == null ? void 0 : _h.session_count) != null ? _i : null
399
+ }
400
+ };
401
+ };
402
+
403
+ // src/questionnaire/transport.ts
404
+ var submitQuestionnaireResponse = (target, id, submission) => {
405
+ if (!(target == null ? void 0 : target.serverUrl) || !id) return;
406
+ try {
407
+ const base = target.serverUrl.replace(/\/$/, "");
408
+ const url = `${base}/v1/questionnaires/${encodeURIComponent(id)}/responses`;
409
+ const headers = { "Content-Type": "application/json" };
410
+ if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
411
+ void fetch(url, {
412
+ method: "POST",
413
+ headers,
414
+ body: JSON.stringify(submission)
415
+ }).catch(() => {
416
+ });
417
+ } catch {
418
+ }
419
+ };
420
+ var fetchQuestionnaireDecision = async (target, options = {}) => {
421
+ if (!(target == null ? void 0 : target.serverUrl)) return null;
422
+ try {
423
+ const base = target.serverUrl.replace(/\/$/, "");
424
+ const params = new URLSearchParams();
425
+ if (options.sessionId) params.set("session_id", options.sessionId);
426
+ if (options.deviceKey) params.set("device_key", options.deviceKey);
427
+ const qs = params.toString();
428
+ const url = `${base}/v1/questionnaires/decision${qs ? `?${qs}` : ""}`;
429
+ const headers = {};
430
+ if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
431
+ const res = await fetch(url, { headers });
432
+ if (!res || !res.ok) return null;
433
+ const json = await res.json();
434
+ return json != null ? json : null;
435
+ } catch {
436
+ return null;
437
+ }
438
+ };
439
+ var mergeThemeOver = (base, override) => {
440
+ if (!override) return base;
441
+ return {
442
+ colors: { ...base.colors, ...override.colors },
443
+ fonts: { ...base.fonts, ...override.fonts },
444
+ radius: { ...base.radius, ...override.radius },
445
+ spacing: { ...base.spacing, ...override.spacing },
446
+ button: { ...base.button, ...override.button }
447
+ };
448
+ };
449
+ var QuestionnaireGate = ({
450
+ questionnaire,
451
+ target,
452
+ sessionId,
453
+ meta,
454
+ onEvent,
455
+ onResolved,
456
+ onShown,
457
+ theme: themeOverride,
458
+ dismissible = true
459
+ }) => {
460
+ var _a, _b, _c, _d;
461
+ const active = useOnboardingTheme();
462
+ const t = React4.useMemo(() => mergeThemeOver(active, themeOverride), [active, themeOverride]);
463
+ const modalRef = React4__default.default.useRef(null);
464
+ const id = questionnaire.id;
465
+ const [phase, setPhase] = React4.useState("form");
466
+ const [opinion, setOpinion] = React4.useState("");
467
+ const [improve, setImprove] = React4.useState("");
468
+ const [suggestions, setSuggestions] = React4.useState("");
469
+ const shownRef = React4__default.default.useRef(false);
470
+ const onShownRef = React4__default.default.useRef(onShown);
471
+ const onEventRef = React4__default.default.useRef(onEvent);
472
+ onShownRef.current = onShown;
473
+ onEventRef.current = onEvent;
474
+ React4__default.default.useEffect(() => {
475
+ var _a2, _b2;
476
+ if (shownRef.current) return;
477
+ shownRef.current = true;
478
+ (_a2 = onShownRef.current) == null ? void 0 : _a2.call(onShownRef);
479
+ (_b2 = onEventRef.current) == null ? void 0 : _b2.call(onEventRef, { name: "questionnaire_prompt_shown", id });
480
+ }, []);
481
+ const resolve = React4.useCallback(() => {
482
+ onResolved == null ? void 0 : onResolved();
483
+ }, [onResolved]);
484
+ const canSend = opinion.trim().length > 0;
485
+ const send = React4.useCallback(() => {
486
+ if (!canSend) return;
487
+ const submission = buildQuestionnaireSubmission({
488
+ answers: { opinion, improve, suggestions },
489
+ sessionId,
490
+ meta
491
+ });
492
+ submitQuestionnaireResponse(target, id, submission);
493
+ onEvent == null ? void 0 : onEvent({ name: "questionnaire_submitted", id });
494
+ setPhase("thanks");
495
+ resolve();
496
+ }, [canSend, opinion, improve, suggestions, sessionId, meta, target, id, onEvent, resolve]);
497
+ const handleDismissTap = React4.useCallback(() => {
498
+ var _a2;
499
+ onEvent == null ? void 0 : onEvent({ name: "questionnaire_dismissed", id });
500
+ (_a2 = modalRef.current) == null ? void 0 : _a2.close();
501
+ }, [onEvent, id]);
502
+ const title = (_a = questionnaire.title) != null ? _a : "How's it going so far?";
503
+ const opinionLabel = (_b = questionnaire.opinion_label) != null ? _b : "What do you think about the app?";
504
+ const improveLabel = (_c = questionnaire.improve_label) != null ? _c : "How can we improve? (optional)";
505
+ const suggestionsLabel = (_d = questionnaire.suggestions_label) != null ? _d : "Any suggestions? (optional)";
506
+ const opinionBox = feedbackInputHeights(t.fonts, t.spacing.md);
507
+ const content = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
508
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [headingStyle(t.fonts), { color: t.colors.text }], children: title }),
509
+ phase === "form" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
510
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [captionStyle(t.fonts), { color: t.colors.textMuted }], children: "A few words is plenty. Only the first box is required." }),
511
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [captionStyle(t.fonts), { color: t.colors.textMuted }], children: opinionLabel }),
512
+ /* @__PURE__ */ jsxRuntime.jsx(
513
+ reactNative.TextInput,
514
+ {
515
+ value: opinion,
516
+ onChangeText: setOpinion,
517
+ placeholder: "e.g. it feels fast and clear",
518
+ placeholderTextColor: t.colors.textMuted,
519
+ multiline: true,
520
+ textAlignVertical: "top",
521
+ style: [
522
+ bodyStyle(t.fonts),
523
+ styles3.input,
524
+ {
525
+ color: t.colors.text,
526
+ backgroundColor: t.colors.background,
527
+ borderRadius: t.radius.md,
528
+ borderColor: t.colors.border,
529
+ padding: t.spacing.md,
530
+ minHeight: opinionBox.minHeight,
531
+ maxHeight: opinionBox.maxHeight
532
+ }
533
+ ]
534
+ }
535
+ ),
536
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [captionStyle(t.fonts), { color: t.colors.textMuted }], children: improveLabel }),
537
+ /* @__PURE__ */ jsxRuntime.jsx(
538
+ reactNative.TextInput,
539
+ {
540
+ value: improve,
541
+ onChangeText: setImprove,
542
+ placeholder: "e.g. fewer steps to start",
543
+ placeholderTextColor: t.colors.textMuted,
544
+ style: [
545
+ bodyStyle(t.fonts),
546
+ styles3.input,
547
+ {
548
+ color: t.colors.text,
549
+ backgroundColor: t.colors.background,
550
+ borderRadius: t.radius.md,
551
+ borderColor: t.colors.border,
552
+ paddingHorizontal: t.spacing.md,
553
+ paddingVertical: t.spacing.sm,
554
+ minHeight: t.button.height
555
+ }
556
+ ]
557
+ }
558
+ ),
559
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [captionStyle(t.fonts), { color: t.colors.textMuted }], children: suggestionsLabel }),
560
+ /* @__PURE__ */ jsxRuntime.jsx(
561
+ reactNative.TextInput,
562
+ {
563
+ value: suggestions,
564
+ onChangeText: setSuggestions,
565
+ placeholder: "e.g. a dark theme",
566
+ placeholderTextColor: t.colors.textMuted,
567
+ style: [
568
+ bodyStyle(t.fonts),
569
+ styles3.input,
570
+ {
571
+ color: t.colors.text,
572
+ backgroundColor: t.colors.background,
573
+ borderRadius: t.radius.md,
574
+ borderColor: t.colors.border,
575
+ paddingHorizontal: t.spacing.md,
576
+ paddingVertical: t.spacing.sm,
577
+ minHeight: t.button.height
578
+ }
579
+ ]
580
+ }
581
+ ),
582
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { title: "Send", onPress: send, variant: "primary", fullWidth: true, disabled: !canSend }),
583
+ 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" }) })
584
+ ] }),
585
+ phase === "thanks" && /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted }], children: "Thank you. This helps us make it better." })
586
+ ] });
587
+ return /* @__PURE__ */ jsxRuntime.jsx(CenteredModal, { ref: modalRef, theme: t, dismissible, onDismiss: resolve, children: content });
588
+ };
589
+ QuestionnaireGate.displayName = "QuestionnaireGate";
590
+ var styles3 = reactNative.StyleSheet.create({
591
+ input: { borderWidth: 1 },
592
+ link: { textAlign: "center", textDecorationLine: "underline" }
593
+ });
594
+
595
+ // src/coachmarks/runtime.ts
596
+ var storageSingleton = null;
597
+ var testing = false;
598
+ var getCoachmarkStorage = () => storageSingleton;
599
+ var isCoachmarkTesting = () => testing;
600
+ var SEEN_VALUE = "1";
601
+ var hasSeenGate = (key, storageOverride, testingOverride) => {
602
+ if (testingOverride != null ? testingOverride : testing) return false;
603
+ const storage = storageOverride != null ? storageOverride : storageSingleton;
604
+ if (!storage) return false;
605
+ try {
606
+ return storage.getItem(key) === SEEN_VALUE;
607
+ } catch {
608
+ return false;
609
+ }
610
+ };
611
+ var markSeenGate = (key, storageOverride, testingOverride) => {
612
+ if (testingOverride != null ? testingOverride : testing) return;
613
+ const storage = storageOverride != null ? storageOverride : storageSingleton;
614
+ if (!storage) return;
615
+ try {
616
+ storage.setItem(key, SEEN_VALUE);
617
+ } catch {
618
+ }
619
+ };
620
+
621
+ // src/features/defaults.ts
622
+ var defaultWireFeatures = Object.freeze({
623
+ coachmarks: Object.freeze({ enabled: true }),
624
+ showcase: Object.freeze({ enabled: true }),
625
+ onboarding: Object.freeze({ mode: "ai" }),
626
+ review: Object.freeze({ enabled: true }),
627
+ questionnaire: Object.freeze({ enabled: true }),
628
+ announcements: Object.freeze({ enabled: true })
629
+ });
630
+ var VALID_MODES = ["ai", "static", "off"];
631
+ var readEnabled = (raw) => {
632
+ if (raw && typeof raw === "object" && typeof raw.enabled === "boolean") {
633
+ return { enabled: raw.enabled };
634
+ }
635
+ return { enabled: true };
636
+ };
637
+ var parseWireFeatures = (raw) => {
638
+ var _a;
639
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return defaultWireFeatures;
640
+ const src = raw;
641
+ const modeRaw = (_a = src.onboarding) == null ? void 0 : _a.mode;
642
+ const mode = typeof modeRaw === "string" && VALID_MODES.includes(modeRaw) ? modeRaw : "ai";
643
+ return {
644
+ coachmarks: readEnabled(src.coachmarks),
645
+ showcase: readEnabled(src.showcase),
646
+ onboarding: { mode },
647
+ review: readEnabled(src.review),
648
+ questionnaire: readEnabled(src.questionnaire),
649
+ announcements: readEnabled(src.announcements)
650
+ };
651
+ };
652
+ var featuresEqual = (a, b) => a === b || a.coachmarks.enabled === b.coachmarks.enabled && a.showcase.enabled === b.showcase.enabled && a.onboarding.mode === b.onboarding.mode && a.review.enabled === b.review.enabled && a.questionnaire.enabled === b.questionnaire.enabled && a.announcements.enabled === b.announcements.enabled;
653
+
654
+ // src/features/cache.ts
655
+ var READ_TIMEOUT_MS = 1500;
656
+ var featuresCacheKey = (appId) => `wireai:features:${appId && appId.length > 0 ? appId : "default"}`;
657
+ var withTimeout = (p, ms) => {
658
+ let timer;
659
+ const timeout = new Promise((resolve) => {
660
+ timer = setTimeout(() => resolve(void 0), ms);
661
+ });
662
+ return Promise.race([p, timeout]).finally(() => clearTimeout(timer));
663
+ };
664
+ var readCachedFeatures = async (storage, key) => {
665
+ let raw;
666
+ try {
667
+ raw = await withTimeout(storage.getItem(key), READ_TIMEOUT_MS);
668
+ } catch {
669
+ return void 0;
670
+ }
671
+ if (!raw) return void 0;
672
+ try {
673
+ const parsed = JSON.parse(raw);
674
+ if (parsed && typeof parsed === "object" && typeof parsed.ts === "number") {
675
+ const ts = parsed.ts;
676
+ return {
677
+ features: parseWireFeatures(parsed.features),
678
+ ageMs: Date.now() - ts
679
+ };
680
+ }
681
+ } catch {
682
+ }
683
+ return void 0;
684
+ };
685
+ var writeCachedFeatures = (storage, key, features) => {
686
+ try {
687
+ void storage.setItem(key, JSON.stringify({ features, ts: Date.now() })).catch(() => {
688
+ });
689
+ } catch {
690
+ }
691
+ };
692
+
693
+ // src/features/fetchWireFeatures.ts
694
+ var DEFAULT_TIMEOUT_MS = 4e3;
695
+ var failOpen = async (storage, key) => {
696
+ var _a;
697
+ if (!storage) return defaultWireFeatures;
698
+ const cached = await readCachedFeatures(storage, key);
699
+ return (_a = cached == null ? void 0 : cached.features) != null ? _a : defaultWireFeatures;
700
+ };
701
+ var fetchWithTimeout = async (url, apiKey, timeoutMs) => {
702
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
703
+ const timer = setTimeout(() => controller == null ? void 0 : controller.abort(), timeoutMs);
704
+ try {
705
+ return await fetch(url, {
706
+ method: "GET",
707
+ headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
708
+ signal: controller == null ? void 0 : controller.signal
709
+ });
710
+ } finally {
711
+ clearTimeout(timer);
712
+ }
713
+ };
714
+ var fetchWireFeatures = async (config) => {
715
+ const key = featuresCacheKey(config == null ? void 0 : config.appId);
716
+ const storage = config == null ? void 0 : config.storage;
717
+ if (!(config == null ? void 0 : config.serverUrl) || !(config == null ? void 0 : config.apiKey)) return failOpen(storage, key);
718
+ const url = `${config.serverUrl.replace(/\/$/, "")}/v1/features`;
719
+ const timeoutMs = config.timeoutMs && config.timeoutMs > 0 ? config.timeoutMs : DEFAULT_TIMEOUT_MS;
720
+ try {
721
+ const res = await fetchWithTimeout(url, config.apiKey, timeoutMs);
722
+ if (!res || !res.ok) return failOpen(storage, key);
723
+ const json = await res.json();
724
+ const features = parseWireFeatures(json);
725
+ if (storage) writeCachedFeatures(storage, key, features);
726
+ return features;
727
+ } catch {
728
+ return failOpen(storage, key);
729
+ }
730
+ };
731
+
732
+ // src/features/useWireFeatures.ts
733
+ var useStableValue = (value, isEqual) => {
734
+ const ref = React4.useRef(value);
735
+ if (!isEqual(ref.current, value)) ref.current = value;
736
+ return ref.current;
737
+ };
738
+ 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);
739
+ var useWireFeatures = (config) => {
740
+ const stableConfig = useStableValue(config, sameFetchKey);
741
+ const [flags, setFlags] = React4.useState(defaultWireFeatures);
742
+ const canFetch = !!(stableConfig == null ? void 0 : stableConfig.serverUrl) && !!(stableConfig == null ? void 0 : stableConfig.apiKey);
743
+ React4.useEffect(() => {
744
+ if (!canFetch) {
745
+ setFlags((prev) => featuresEqual(prev, defaultWireFeatures) ? prev : defaultWireFeatures);
746
+ return;
747
+ }
748
+ let alive = true;
749
+ void fetchWireFeatures(stableConfig).then((next) => {
750
+ if (!alive) return;
751
+ setFlags((prev) => featuresEqual(prev, next) ? prev : next);
752
+ });
753
+ return () => {
754
+ alive = false;
755
+ };
756
+ }, [canFetch, stableConfig]);
757
+ return flags;
758
+ };
759
+ var WireFeaturesContext = React4.createContext(null);
760
+ var useWireFeaturesContext = () => React4.useContext(WireFeaturesContext);
761
+ var useResolvedFeatures = (options) => {
762
+ var _a, _b, _c;
763
+ const ctx = useWireFeaturesContext();
764
+ const shouldFetch = !(options == null ? void 0 : options.flags) && ctx == null;
765
+ const fetched = useWireFeatures(shouldFetch ? options == null ? void 0 : options.config : void 0);
766
+ return (_c = (_b = (_a = options == null ? void 0 : options.flags) != null ? _a : ctx) != null ? _b : fetched) != null ? _c : defaultWireFeatures;
767
+ };
768
+
769
+ // src/reviews/equality.ts
770
+ var shallowEqual = (a, b) => {
771
+ if (Object.is(a, b)) return true;
772
+ if (a == null || b == null || typeof a !== "object" || typeof b !== "object") {
773
+ return false;
774
+ }
775
+ const ak = Object.keys(a);
776
+ const bk = Object.keys(b);
777
+ if (ak.length !== bk.length) return false;
778
+ for (const k of ak) {
779
+ if (!Object.is(a[k], b[k])) {
780
+ return false;
781
+ }
782
+ }
783
+ return true;
784
+ };
785
+ var sameDecision = (a, b) => {
786
+ if (a == null || b == null) return a == b;
787
+ return a.fire === b.fire && a.reason === b.reason;
788
+ };
789
+
790
+ // src/reviews/runtime.ts
791
+ var resolveStorage = (override) => override != null ? override : getCoachmarkStorage();
792
+ var readInt = (storage, key) => {
793
+ if (!storage) return 0;
794
+ try {
795
+ const raw = storage.getItem(key);
796
+ const n = raw == null ? 0 : parseInt(raw, 10);
797
+ return Number.isFinite(n) ? n : 0;
798
+ } catch {
799
+ return 0;
800
+ }
801
+ };
802
+ var writeInt = (storage, key, value) => {
803
+ if (!storage) return;
804
+ try {
805
+ storage.setItem(key, String(value));
806
+ } catch {
807
+ }
808
+ };
809
+
810
+ // src/questionnaire/runtime.ts
811
+ var questionnaireSeenKey = (id, version) => version ? `wire_questionnaire_${id}_${version}_seen` : `wire_questionnaire_${id}_seen`;
812
+ var questionnaireLastShownKey = (id) => `wire_questionnaire_${id}_last`;
813
+ var questionnaireSessionsKey = (id) => `wire_questionnaire_${id}_sessions`;
814
+
815
+ // src/questionnaire/useQuestionnaireGate.ts
816
+ var useStableValue2 = (value, isEqual) => {
817
+ const ref = React4.useRef(value);
818
+ if (!isEqual(ref.current, value)) ref.current = value;
819
+ return ref.current;
820
+ };
821
+ var useQuestionnaireGate = ({
822
+ config: configProp,
823
+ decision: decisionProp,
824
+ events = 0,
825
+ storage,
826
+ isTesting,
827
+ features,
828
+ featuresConfig
829
+ }) => {
830
+ const config = useStableValue2(configProp, shallowEqual);
831
+ const decision = useStableValue2(decisionProp, (a, b) => sameDecision(a, b));
832
+ const featureEnabled = useResolvedFeatures({ flags: features, config: featuresConfig }).questionnaire.enabled;
833
+ const testing2 = isTesting != null ? isTesting : isCoachmarkTesting();
834
+ const seenKey = questionnaireSeenKey(
835
+ config.id,
836
+ config.oncePerVersion === false ? void 0 : config.appVersion
837
+ );
838
+ const lastKey = questionnaireLastShownKey(config.id);
839
+ const sessionsKey = questionnaireSessionsKey(config.id);
840
+ const sessions = React4.useState(() => {
841
+ const store = resolveStorage(storage);
842
+ const next = readInt(store, sessionsKey) + 1;
843
+ writeInt(store, sessionsKey, next);
844
+ return next;
845
+ })[0];
846
+ const hasServerDecision = decision != null;
847
+ const timeoutMs = config.timeoutFallbackMs;
848
+ const waits = !hasServerDecision && timeoutMs != null && timeoutMs > 0;
849
+ const [elapsed, setElapsed] = React4.useState(!waits);
850
+ React4.useEffect(() => {
851
+ if (!waits) {
852
+ setElapsed(true);
853
+ return;
854
+ }
855
+ const timer = setTimeout(() => setElapsed(true), timeoutMs);
856
+ return () => clearTimeout(timer);
857
+ }, [waits, timeoutMs]);
858
+ const { visible, verdict } = React4.useMemo(() => {
859
+ if (!featureEnabled)
860
+ return {
861
+ visible: false,
862
+ verdict: { fire: false, reason: "feature_disabled" }
863
+ };
864
+ if (testing2)
865
+ return {
866
+ visible: true,
867
+ verdict: { fire: true, reason: "testing" }
868
+ };
869
+ const store = resolveStorage(storage);
870
+ const local = evaluateGate(resolveRules(config), {
871
+ sessions,
872
+ events,
873
+ lastShownAt: readInt(store, lastKey) || null,
874
+ seen: hasSeenGate(seenKey, storage, isTesting),
875
+ now: Date.now()
876
+ });
877
+ const resolved = decideQuestionnaire(local, decision);
878
+ const alreadySeen = hasSeenGate(seenKey, storage, isTesting);
879
+ const ready = hasServerDecision || elapsed;
880
+ return { visible: ready && resolved.fire && !alreadySeen, verdict: resolved };
881
+ }, [
882
+ featureEnabled,
883
+ testing2,
884
+ config,
885
+ decision,
886
+ events,
887
+ sessions,
888
+ elapsed,
889
+ hasServerDecision,
890
+ seenKey,
891
+ lastKey,
892
+ storage,
893
+ isTesting
894
+ ]);
895
+ const shownRef = React4.useRef(false);
896
+ const markShown = React4.useCallback(() => {
897
+ if (shownRef.current) return;
898
+ shownRef.current = true;
899
+ writeInt(resolveStorage(storage), lastKey, Date.now());
900
+ }, [storage, lastKey]);
901
+ const markResolved = React4.useCallback(() => {
902
+ markSeenGate(seenKey, storage, isTesting);
903
+ }, [seenKey, storage, isTesting]);
904
+ return React4.useMemo(
905
+ () => ({ visible, decision: verdict, markShown, markResolved }),
906
+ [visible, verdict, markShown, markResolved]
907
+ );
908
+ };
909
+
910
+ exports.QuestionnaireGate = QuestionnaireGate;
911
+ exports.buildQuestionnaireSubmission = buildQuestionnaireSubmission;
912
+ exports.decideQuestionnaire = decideQuestionnaire;
913
+ exports.evaluateGate = evaluateGate;
914
+ exports.fetchQuestionnaireDecision = fetchQuestionnaireDecision;
915
+ exports.questionnaireLastShownKey = questionnaireLastShownKey;
916
+ exports.questionnaireSeenKey = questionnaireSeenKey;
917
+ exports.questionnaireSessionsKey = questionnaireSessionsKey;
918
+ exports.resolveRules = resolveRules;
919
+ exports.submitQuestionnaireResponse = submitQuestionnaireResponse;
920
+ exports.useQuestionnaireGate = useQuestionnaireGate;
921
+ //# sourceMappingURL=index.js.map
922
+ //# sourceMappingURL=index.js.map