@wireai/activation 0.13.2 → 0.13.5

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 (82) hide show
  1. package/AGENTS.md +6 -3
  2. package/CHANGELOG.md +291 -4
  3. package/INTEGRATION_PROMPT.md +7 -1
  4. package/README.md +68 -2
  5. package/dist/analytics/index.d.mts +86 -6
  6. package/dist/analytics/index.d.ts +86 -6
  7. package/dist/analytics/index.js +271 -472
  8. package/dist/analytics/index.js.map +1 -1
  9. package/dist/analytics/index.mjs +269 -471
  10. package/dist/analytics/index.mjs.map +1 -1
  11. package/dist/coachmarks/index.d.mts +15 -2
  12. package/dist/coachmarks/index.d.ts +15 -2
  13. package/dist/coachmarks/index.js +58 -14
  14. package/dist/coachmarks/index.js.map +1 -1
  15. package/dist/coachmarks/index.mjs +58 -14
  16. package/dist/coachmarks/index.mjs.map +1 -1
  17. package/dist/{currentSession-ClkLjcJ0.d.mts → currentSession-DgJf0fRz.d.mts} +4 -4
  18. package/dist/{currentSession-DOVZEWJl.d.ts → currentSession-EOHU64QD.d.ts} +4 -4
  19. package/dist/{decision-Bl_M2y3r.d.mts → decision-Bgo17oH7.d.mts} +1 -1
  20. package/dist/{decision-Cau5KmP6.d.ts → decision-Bkh_LigV.d.ts} +1 -1
  21. package/dist/index.d.mts +27 -4
  22. package/dist/index.d.ts +27 -4
  23. package/dist/index.js +224 -575
  24. package/dist/index.js.map +1 -1
  25. package/dist/index.mjs +221 -572
  26. package/dist/index.mjs.map +1 -1
  27. package/dist/questionnaire/index.d.mts +43 -11
  28. package/dist/questionnaire/index.d.ts +43 -11
  29. package/dist/questionnaire/index.js +45 -13
  30. package/dist/questionnaire/index.js.map +1 -1
  31. package/dist/questionnaire/index.mjs +45 -13
  32. package/dist/questionnaire/index.mjs.map +1 -1
  33. package/dist/reviews/index.d.mts +5 -5
  34. package/dist/reviews/index.d.ts +5 -5
  35. package/dist/reviews/index.js +31 -9
  36. package/dist/reviews/index.js.map +1 -1
  37. package/dist/reviews/index.mjs +31 -9
  38. package/dist/reviews/index.mjs.map +1 -1
  39. package/dist/showcase/index.d.mts +1 -1
  40. package/dist/showcase/index.d.ts +1 -1
  41. package/dist/showcase/index.js +7 -2
  42. package/dist/showcase/index.js.map +1 -1
  43. package/dist/showcase/index.mjs +7 -2
  44. package/dist/showcase/index.mjs.map +1 -1
  45. package/dist/{transport-CF_eHwzC.d.mts → transport-B_0SgCBe.d.ts} +44 -5
  46. package/dist/{transport-DsRe4epC.d.ts → transport-j5gFfJhK.d.mts} +44 -5
  47. package/dist/{types-CKFhyrMu.d.mts → types-BpwiRpA8.d.mts} +1 -1
  48. package/dist/{types-CKFhyrMu.d.ts → types-BpwiRpA8.d.ts} +1 -1
  49. package/dist/{types-CNUqMK0D.d.mts → types-Cju-1_jT.d.mts} +42 -2
  50. package/dist/{types-Buj9Lw9t.d.ts → types-h2BZvl1t.d.ts} +42 -2
  51. package/llms.txt +1 -1
  52. package/package.json +6 -1
  53. package/src/OnboardingFlow.tsx +44 -15
  54. package/src/WireOnboarding.tsx +7 -6
  55. package/src/activation/revalidation.ts +9 -1
  56. package/src/activation/wireActivation.ts +3 -3
  57. package/src/analytics/analyticsFacade.ts +2 -2
  58. package/src/analytics/currentSession.ts +10 -0
  59. package/src/analytics/eventQueue.ts +88 -20
  60. package/src/analytics/index.ts +6 -0
  61. package/src/analytics/reportClientEvent.ts +29 -7
  62. package/src/analytics/wireDoctor.ts +330 -0
  63. package/src/cards/CardGridSelectCard.tsx +1 -1
  64. package/src/cards/ChipSelectCard.tsx +2 -2
  65. package/src/cards/SelectionCard.tsx +1 -1
  66. package/src/coachmarks/runtime.ts +72 -3
  67. package/src/coachmarks/useCoachmarkTour.ts +62 -14
  68. package/src/context/deviceId.ts +14 -5
  69. package/src/features/WireFeaturesProvider.tsx +12 -1
  70. package/src/identity/identityRecord.ts +9 -1
  71. package/src/identity/userIdentity.ts +1 -1
  72. package/src/questionnaire/QuestionnaireGate.tsx +90 -10
  73. package/src/questionnaire/index.ts +9 -1
  74. package/src/questionnaire/transport.ts +46 -11
  75. package/src/reviews/ReviewGate.tsx +61 -6
  76. package/src/reviews/index.ts +10 -1
  77. package/src/reviews/runtime.ts +11 -1
  78. package/src/reviews/transport.ts +58 -12
  79. package/src/session-analytics/useLifecycleEvents.ts +1 -1
  80. package/src/theme/ThemeContext.tsx +49 -1
  81. package/src/types.ts +1 -1
  82. package/src/utils/submitResult.ts +39 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/theme/defaultTheme.ts","../../src/theme/ThemeContext.tsx","../../src/theme/typography.ts","../../src/motion/motionSpec.ts","../../src/motion/useReducedMotion.ts","../../src/components/Button.tsx","../../src/components/DoneCheck.tsx","../../src/components/CardHandoff.tsx","../../src/components/ModalPaneHost.tsx","../../src/haptics/haptics.ts","../../src/theme/mergeThemeOver.ts","../../src/reviews/decision.ts","../../src/reviews/feedbackForm.ts","../../src/motion/centeredModalMotion.ts","../../src/reviews/presentation.ts","../../src/components/CenteredModal.tsx","../../src/reviews/storeReview.ts","../../src/analytics/reportClientEvent.ts","../../src/utils/warnInDev.ts","../../src/analytics/currentSession.ts","../../src/reviews/transport.ts","../../src/reviews/ReviewGate.tsx","../../src/coachmarks/runtime.ts","../../src/features/defaults.ts","../../src/features/cache.ts","../../src/features/fetchWireFeatures.ts","../../src/features/useWireFeatures.ts","../../src/features/WireFeaturesProvider.tsx","../../src/reviews/equality.ts","../../src/reviews/runtime.ts","../../src/reviews/useReviewGate.ts"],"names":["createContext","useContext","useState","useEffect","AccessibilityInfo","useRef","Animated","useCallback","jsx","Pressable","Text","React","StyleSheet","styles","Easing","jsxs","Fragment","easeWire","View","forwardRef","useImperativeHandle","BackHandler","Modal","SafeAreaView","Platform","Linking","useMemo","TextInput","useStableValue"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * defaultOnboardingTheme — a neutral, light, brand-agnostic baseline. Consumers\n * pass a `Partial<OnboardingTheme>` that is deep-merged over this (see\n * `mergeTheme`), so they only override what differs from neutral.\n */\nimport type { OnboardingTheme } from \"./types\";\n\nexport const defaultOnboardingTheme: OnboardingTheme = {\n colors: {\n background: \"#FFFFFF\",\n surface: \"#F7F7F8\",\n text: \"#101114\",\n textMuted: \"#6B7280\",\n primary: \"#111111\",\n onPrimary: \"#FFFFFF\",\n primarySoft: \"rgba(17,17,17,0.06)\",\n border: \"#E4E4E7\",\n borderStrong: \"#C7C7CC\",\n disabled: \"#EBEBEB\",\n success: \"#16A34A\",\n warning: \"#D97706\",\n error: \"#DC2626\",\n info: \"#2563EB\",\n },\n fonts: {\n regular: undefined,\n medium: undefined,\n bold: undefined,\n baseSize: 16,\n scale: 1,\n },\n radius: {\n sm: 8,\n md: 12,\n lg: 16,\n full: 9999,\n },\n spacing: {\n xs: 4,\n sm: 8,\n md: 16,\n lg: 24,\n xl: 32,\n },\n // Circled (pill) CTA by default; an app can pass `button: { radius: 12 }` for\n // a rounded rectangle, or override height/paddingX to match its design system.\n button: {\n radius: 9999,\n height: 52,\n paddingX: 24,\n },\n};\n\n/**\n * Deep-merge a partial theme over the default. Two levels deep is enough — the\n * theme is intentionally shallow (colors/fonts/radius/spacing), so we merge each\n * known sub-object and leave scalars to overwrite.\n */\nexport const mergeTheme = (\n override?: Partial<OnboardingTheme>,\n): OnboardingTheme => {\n if (!override) return defaultOnboardingTheme;\n return {\n colors: { ...defaultOnboardingTheme.colors, ...override.colors },\n fonts: { ...defaultOnboardingTheme.fonts, ...override.fonts },\n radius: { ...defaultOnboardingTheme.radius, ...override.radius },\n spacing: { ...defaultOnboardingTheme.spacing, ...override.spacing },\n button: { ...defaultOnboardingTheme.button, ...override.button },\n };\n};\n","/**\n * ThemeContext — provides the resolved OnboardingTheme to every shell element and\n * themed card via `useOnboardingTheme()`. WHY a context rather than prop-drilling:\n * the cards are mounted by the SDK's ComponentRenderer (not by us), so we can't\n * pass them props directly — they must read the theme from context.\n */\nimport React, { createContext, useContext, useMemo } from \"react\";\nimport { mergeTheme } from \"./defaultTheme\";\nimport type { OnboardingTheme } from \"./types\";\n\nconst OnboardingThemeContext = createContext<OnboardingTheme | null>(null);\n\nexport type OnboardingThemeProviderProps = {\n /** Partial overrides merged over the neutral default theme. */\n theme?: Partial<OnboardingTheme>;\n children: React.ReactNode;\n};\n\nexport const OnboardingThemeProvider: React.FC<OnboardingThemeProviderProps> = ({\n theme,\n children,\n}) => {\n const resolved = useMemo(() => mergeTheme(theme), [theme]);\n return (\n <OnboardingThemeContext.Provider value={resolved}>\n {children}\n </OnboardingThemeContext.Provider>\n );\n};\n\n/**\n * Read the active theme. Falls back to the default theme when used outside a\n * provider so a stray card never crashes — it just renders neutral.\n */\nexport const useOnboardingTheme = (): OnboardingTheme => {\n const ctx = useContext(OnboardingThemeContext);\n return ctx ?? mergeTheme();\n};\n","/**\n * typography — derive concrete RN text styles from the theme's font config, so\n * every label/title/caption in the kit scales from one `baseSize` + `scale` and\n * picks the right family. Cards call these instead of hardcoding fontSize/weight.\n */\nimport type { TextStyle } from \"react-native\";\nimport type { OnboardingFonts } from \"./types\";\n\nconst round = (n: number) => Math.round(n);\n\n/** Body text — the reference size. */\nexport const bodyStyle = (f: OnboardingFonts): TextStyle => ({\n fontFamily: f.regular,\n fontSize: round(f.baseSize * f.scale),\n lineHeight: round(f.baseSize * f.scale * 1.5),\n});\n\n/** Slightly smaller secondary/hint text. */\nexport const captionStyle = (f: OnboardingFonts): TextStyle => ({\n fontFamily: f.regular,\n fontSize: round(f.baseSize * 0.8125 * f.scale),\n lineHeight: round(f.baseSize * 0.8125 * f.scale * 1.5),\n});\n\n/** Emphasised label (e.g. a card question). */\nexport const labelStyle = (f: OnboardingFonts): TextStyle => ({\n fontFamily: f.medium ?? f.bold,\n fontSize: round(f.baseSize * f.scale),\n lineHeight: round(f.baseSize * f.scale * 1.4),\n fontWeight: f.medium ? undefined : (\"600\" as const),\n});\n\n/** Heading (status title, done block). */\nexport const headingStyle = (f: OnboardingFonts): TextStyle => ({\n fontFamily: f.bold,\n fontSize: round(f.baseSize * 1.25 * f.scale),\n lineHeight: round(f.baseSize * 1.25 * f.scale * 1.3),\n fontWeight: f.bold ? undefined : (\"700\" as const),\n});\n\n/** Button label. */\nexport const buttonLabelStyle = (f: OnboardingFonts): TextStyle => ({\n fontFamily: f.bold ?? f.medium,\n fontSize: round(f.baseSize * f.scale),\n fontWeight: f.bold ? undefined : (\"600\" as const),\n});\n","/**\n * motionSpec — the single source of truth for every animation value in the kit,\n * extracted 1:1 from the Wire Activation card-interactions design canvas\n * (see design-refs/EXTRACTED-SPEC.md for the row-by-row mapping).\n *\n * Pure constants + pure helpers only — NO react-native import — so the spec is\n * unit-testable under node:test and components can't drift from the design\n * without a red test.\n */\n\n/** The design system's \"ease-wire\" curve: cubic-bezier(0.16, 1, 0.3, 1). */\nexport const WIRE_BEZIER = [0.16, 1, 0.3, 1] as const;\n\n/** Design-system duration tokens (colors_and_type.css --duration-*). */\nexport const DURATION_FAST_MS = 150;\nexport const DURATION_BASE_MS = 300;\nexport const DURATION_SLOW_MS = 500;\n\n/** Reduce-motion fallback: a short fade to the final frame, never a frozen mid-state. */\nexport const REDUCED_FADE_MS = 120;\n\n// ── Press feedback (spec rows 1, 4, 20) ─────────────────────────────────────\nexport const ROW_PRESS_SCALE = 0.975;\nexport const CHIP_PRESS_SCALE = 0.94;\nexport const BUTTON_PRESS_SCALE = 0.975;\n/** Reduce-motion press feedback: opacity dip instead of scale. */\nexport const REDUCED_PRESS_OPACITY = 0.85;\nexport const PRESS_SPRING = { friction: 8, tension: 220 } as const;\nexport const BUTTON_PRESS_SPRING = { friction: 8, tension: 240 } as const;\n\n// ── Selection state changes (spec rows 2, 3, 5, 6) ──────────────────────────\nexport const SELECT_FILL_MS = 180;\nexport const CHIP_FILL_MS = 160;\nexport const CHECK_SPRING = { friction: 6, tension: 180 } as const;\nexport const CHECK_SCALE_FROM = 0.6;\n\n// ── Card-to-card handoff (spec rows 7-10) ───────────────────────────────────\nexport const CARD_ENTER_MS = 220;\nexport const CARD_EXIT_MS = 180;\n/** The exit starts this long BEFORE the enter (enter is delayed by this). */\nexport const CARD_EXIT_LEAD_MS = 80;\nexport const CARD_SLIDE_PX = 26;\nexport const CARD_ENTER_SCALE_FROM = 0.96;\n/** The interstitial keeps the shell's springier value-beat (AnimatedCard idiom). */\nexport const CARD_SPRING = { damping: 14, stiffness: 180, mass: 1 } as const;\n/** Loader-to-card handoff: the loader just fades out. */\nexport const LOADER_HANDOFF_FADE_MS = 120;\n\n// ── InterstitialCard reveal (spec rows 11-13) ───────────────────────────────\nexport const INTERSTITIAL_HEAD_MS = 500;\nexport const INTERSTITIAL_HEAD_STAGGER_MS = 100;\nexport const INTERSTITIAL_ITEM_MS = 300;\nexport const INTERSTITIAL_ITEM_STAGGER_MS = 90;\nexport const INTERSTITIAL_ITEM_BASE_DELAY_MS = 200;\nexport const INTERSTITIAL_CHECK_DELAY_MS = 200;\nexport const INTERSTITIAL_CHECK_SCALE_FROM = 0.4;\n\n/** Delay before work item `index` begins its reveal (row lands after the head). */\nexport const interstitialItemDelay = (index: number): number =>\n INTERSTITIAL_ITEM_BASE_DELAY_MS + index * INTERSTITIAL_ITEM_STAGGER_MS;\n\n/** Delay before work item `index`'s check pops (200ms after its row starts). */\nexport const interstitialCheckDelay = (index: number): number =>\n interstitialItemDelay(index) + INTERSTITIAL_CHECK_DELAY_MS;\n\n// ── Status / completion payoff (spec rows 14-16) ────────────────────────────\nexport const STATUS_POP_SPRING = { friction: 6, tension: 200 } as const;\nexport const STATUS_POP_SCALE_FROM = 0.4;\nexport const STATUS_BURST_MS = 500;\nexport const STATUS_BURST_SCALE_FROM = 0.4;\nexport const STATUS_BURST_SCALE_TO = 1.5;\nexport const STATUS_BURST_OPACITY_FROM = 0.5;\nexport const STATUS_COPY_MS = 300;\nexport const STATUS_COPY_STAGGER_MS = 80;\n/** Copy starts settling once the badge pop is visibly underway. */\nexport const STATUS_COPY_BASE_DELAY_MS = 200;\n\n/** Delay before payoff copy line `index` fades up. */\nexport const statusCopyDelay = (index: number): number =>\n STATUS_COPY_BASE_DELAY_MS + index * STATUS_COPY_STAGGER_MS;\n\n// ── Loading treatment (spec rows 17-19) ─────────────────────────────────────\nexport const LOADER_PULSE_MS = 1600;\nexport const LOADER_PULSE_MIN_OPACITY = 0.35;\nexport const LOADER_ORBIT_MS = 2400;\n/** Skeleton lines appear only when the fetch runs past this. */\nexport const SKELETON_DELAY_MS = 300;\nexport const SKELETON_PULSE_MS = 1500;\nexport const SKELETON_MIN_OPACITY = 0.45;\n\n// ── Progress bar (spec row 21) ──────────────────────────────────────────────\nexport const PROGRESS_MS = 400;\n\n// ── Dev/QA duration scale (slow-mo for judging motion) ──────────────────────\n// A module-level multiplier applied to every timing DURATION (and stagger\n// delay) at animation start. 1 = design speed; 2 = half-speed slow-mo. Springs\n// are physics-based (friction/tension) and do NOT scale. This is a dev/QA tool\n// (the playground's tweak panel drives it) — production code never calls the\n// setter, so the default 1 keeps shipped motion exactly on spec.\n\nlet durationScale = 1;\n\n/** Set the global duration multiplier (clamped to 0.1-10; invalid resets to 1). */\nexport const setMotionDurationScale = (scale: number): void => {\n durationScale = Number.isFinite(scale) && scale > 0 ? Math.min(10, Math.max(0.1, scale)) : 1;\n};\n\nexport const getMotionDurationScale = (): number => durationScale;\n\n/** Apply the current duration scale to a spec duration/delay (rounded ms). */\nexport const scaledMs = (ms: number): number => Math.round(ms * durationScale);\n\n// ── Reduce-motion helpers (pure, shared by every animated component) ────────\n\nexport type EntranceSpec = {\n /** Duration to animate with (falls back to the short fade under reduce motion). */\n duration: number;\n /** Whether transforms (translate/scale) may move; false = opacity-only fade. */\n useTransform: boolean;\n};\n\n/** Entrances: full motion normally; a 120ms opacity-only fade under reduce motion. */\nexport const entranceSpec = (reduced: boolean, durationMs: number): EntranceSpec =>\n reduced\n ? { duration: REDUCED_FADE_MS, useTransform: false }\n : { duration: durationMs, useTransform: true };\n\n/** State changes (select fills etc.): snap instantly under reduce motion. */\nexport const stateChangeDuration = (reduced: boolean, durationMs: number): number =>\n reduced ? 0 : durationMs;\n\n/** Looping/ambient animations run only when motion is allowed. */\nexport const loopEnabled = (reduced: boolean): boolean => !reduced;\n","/**\n * useReducedMotion — the kit's ONE reduce-motion gate. Reads the OS setting via\n * AccessibilityInfo and stays subscribed to `reduceMotionChanged`, so every\n * animated component lands on its final frame (or a ~120ms fade) the moment the\n * user flips the setting — no restart needed.\n *\n * This closes the gap documented in ai_rules/rules/performance.md §3: every\n * animation in the kit must consume this hook (usually through the pure helpers\n * in motionSpec.ts: `entranceSpec` / `stateChangeDuration` / `loopEnabled`).\n */\nimport { useEffect, useState } from \"react\";\nimport { AccessibilityInfo } from \"react-native\";\n\n// Last-known value, module-level: cards remount every turn (keyed on message id),\n// and a `useState(false)` default would flash one animated frame per turn for a\n// reduce-motion user before the async read resolves.\nlet lastKnown = false;\n\nexport const useReducedMotion = (): boolean => {\n const [reduced, setReduced] = useState(lastKnown);\n\n useEffect(() => {\n let mounted = true;\n AccessibilityInfo.isReduceMotionEnabled()\n .then((value) => {\n lastKnown = value;\n if (mounted) setReduced(value);\n })\n .catch(() => {\n // Unavailable (e.g. some test envs): keep the last-known value.\n });\n const sub = AccessibilityInfo.addEventListener(\"reduceMotionChanged\", (value) => {\n lastKnown = value;\n setReduced(value);\n });\n return () => {\n mounted = false;\n sub?.remove();\n };\n }, []);\n\n return reduced;\n};\n","/**\n * Button — the kit's themed primary/outline button, replacing the SDK's hardcoded\n * `Btn`. Every color/radius/font comes from the active theme.\n *\n * Motion (design-refs/EXTRACTED-SPEC.md row 20): \"a footer button that answers\n * the tap\" — scale 1→0.975 spring on press-in, back on release, native driver.\n * Reduce motion: an opacity dip to 0.85 while pressed, no scale. Disabled keeps\n * the `disabled` token fill with no press handler (unchanged).\n */\nimport React, { useCallback, useRef } from \"react\";\nimport { Animated, Pressable, StyleSheet, Text } from \"react-native\";\nimport { useOnboardingTheme } from \"../theme/ThemeContext\";\nimport { buttonLabelStyle } from \"../theme/typography\";\nimport {\n BUTTON_PRESS_SCALE,\n BUTTON_PRESS_SPRING,\n REDUCED_FADE_MS,\n REDUCED_PRESS_OPACITY,\n scaledMs,\n} from \"../motion/motionSpec\";\nimport { useReducedMotion } from \"../motion/useReducedMotion\";\n\ntype Variant = \"primary\" | \"outline\";\n\ntype ButtonProps = {\n title: string;\n onPress?: () => void;\n variant?: Variant;\n disabled?: boolean;\n /** Stretch to the container width — for form-style CTAs (e.g. the review form's\n * \"Send feedback\"). Default false keeps the content-width pill used by cards. */\n fullWidth?: boolean;\n};\n\nconst _Button: React.FC<ButtonProps> = ({\n title,\n onPress,\n variant = \"primary\",\n disabled = false,\n fullWidth = false,\n}) => {\n const t = useOnboardingTheme();\n const reduced = useReducedMotion();\n const isPrimary = variant === \"primary\";\n\n // 0 = at rest, 1 = pressed. Scale normally; opacity dip under reduce motion.\n const pressT = useRef(new Animated.Value(0)).current;\n const pressTo = useCallback(\n (to: number) => {\n if (reduced) {\n Animated.timing(pressT, {\n toValue: to,\n duration: scaledMs(REDUCED_FADE_MS),\n useNativeDriver: true,\n }).start();\n return;\n }\n Animated.spring(pressT, {\n toValue: to,\n friction: BUTTON_PRESS_SPRING.friction,\n tension: BUTTON_PRESS_SPRING.tension,\n useNativeDriver: true,\n }).start();\n },\n [pressT, reduced],\n );\n const lastPress = useRef<number>(0);\n const handlePress = useCallback(() => {\n if (!onPress) return;\n const now = Date.now();\n if (now - lastPress.current < 500) return;\n lastPress.current = now;\n onPress();\n }, [onPress]);\n\n const handlePressIn = useCallback(() => pressTo(1), [pressTo]);\n const handlePressOut = useCallback(() => pressTo(0), [pressTo]);\n\n const scale = pressT.interpolate({\n inputRange: [0, 1],\n outputRange: [1, reduced ? 1 : BUTTON_PRESS_SCALE],\n });\n const opacity = pressT.interpolate({\n inputRange: [0, 1],\n outputRange: [1, reduced ? REDUCED_PRESS_OPACITY : 1],\n });\n\n return (\n <Pressable\n onPress={disabled ? undefined : handlePress}\n onPressIn={disabled ? undefined : handlePressIn}\n onPressOut={disabled ? undefined : handlePressOut}\n disabled={disabled}\n accessibilityRole=\"button\"\n accessibilityState={{ disabled }}\n style={fullWidth ? styles.fill : undefined}\n >\n <Animated.View\n style={[\n styles.base,\n fullWidth && styles.fill,\n {\n borderRadius: t.button.radius,\n minHeight: t.button.height,\n paddingHorizontal: t.button.paddingX,\n opacity,\n transform: [{ scale }],\n },\n isPrimary\n ? { backgroundColor: disabled ? t.colors.disabled : t.colors.primary }\n : { borderWidth: 1.5, borderColor: disabled ? t.colors.border : t.colors.primary },\n ]}\n >\n <Text\n style={[\n buttonLabelStyle(t.fonts),\n {\n color: disabled\n ? t.colors.textMuted\n : isPrimary\n ? t.colors.onPrimary\n : t.colors.primary,\n },\n ]}\n >\n {title}\n </Text>\n </Animated.View>\n </Pressable>\n );\n};\n\nexport const Button = React.memo(_Button);\n\nconst styles = StyleSheet.create({\n base: { alignItems: \"center\", justifyContent: \"center\" },\n fill: { width: \"100%\" },\n});\n","/**\n * DoneCheck — the animated \"done\" payoff glyph shared by the review gate's and the\n * questionnaire's thank-you panes. One component, two call sites, one feel.\n *\n * The beat is the house success beat, lifted from StatusCard (design-refs/\n * EXTRACTED-SPEC.md rows 14/16): the glyph springs in with a slight overshoot\n * (STATUS_POP_SPRING, scale STATUS_POP_SCALE_FROM→1) while the ring settles behind it.\n * No new motion values are invented here — every number comes from motionSpec.\n *\n * It is a Text glyph in a themed circle, NOT an SVG path: `react-native-svg` is not a\n * peer of this kit, and adding a NATIVE dependency to draw a checkmark would force every\n * host into a rebuild. The five review stars are already glyphs, so this matches the\n * house idiom.\n *\n * Reduce motion: the final frame, instantly — never a frozen mid-pop.\n *\n * Accessibility: purely decorative. The thank-you copy next to it carries the meaning,\n * so the glyph is hidden from screen readers rather than announced as \"heavy check mark\".\n */\nimport React, { useEffect, useRef } from \"react\";\nimport { Animated, StyleSheet } from \"react-native\";\n\nimport { STATUS_POP_SCALE_FROM, STATUS_POP_SPRING } from \"../motion/motionSpec\";\nimport { useReducedMotion } from \"../motion/useReducedMotion\";\nimport type { OnboardingTheme } from \"../theme/types\";\n\nexport type DoneCheckProps = {\n /** The (already merged) active theme — the ring reads `colors.success`. */\n theme: OnboardingTheme;\n /** Diameter of the ring. Defaults to a size that balances the modal heading. */\n size?: number;\n};\n\nconst _DoneCheck: React.FC<DoneCheckProps> = ({ theme, size = 48 }) => {\n const reduced = useReducedMotion();\n const pop = useRef(new Animated.Value(reduced ? 1 : 0)).current;\n\n useEffect(() => {\n if (reduced) {\n pop.setValue(1);\n return;\n }\n const anim = Animated.spring(pop, {\n toValue: 1,\n friction: STATUS_POP_SPRING.friction,\n tension: STATUS_POP_SPRING.tension,\n useNativeDriver: true,\n });\n anim.start();\n return () => anim.stop();\n }, [pop, reduced]);\n\n const scale = pop.interpolate({\n inputRange: [0, 1],\n outputRange: [STATUS_POP_SCALE_FROM, 1],\n });\n\n return (\n <Animated.View\n accessibilityElementsHidden\n importantForAccessibility=\"no-hide-descendants\"\n style={[\n styles.ring,\n {\n width: size,\n height: size,\n borderRadius: theme.radius.full,\n backgroundColor: theme.colors.success,\n opacity: pop,\n transform: [{ scale }],\n },\n ]}\n >\n <Animated.Text\n style={[\n styles.glyph,\n { color: theme.colors.onPrimary, fontSize: Math.round(size * 0.5), fontFamily: theme.fonts.bold },\n ]}\n >\n ✓\n </Animated.Text>\n </Animated.View>\n );\n};\n\nexport const DoneCheck = React.memo(_DoneCheck);\nDoneCheck.displayName = \"DoneCheck\";\n\nconst styles = StyleSheet.create({\n ring: { alignItems: \"center\", justifyContent: \"center\", alignSelf: \"center\" },\n glyph: { includeFontPadding: false, textAlign: \"center\" },\n});\n\nexport default DoneCheck;\n","/**\n * CardHandoff — the card-to-card transition from the Wire Activation design:\n * \"cards hand off, they don't cut\". When `transitionKey` changes, the outgoing\n * child leaves left (translateX 0→-26, fade, 180ms) in an absolute layer while\n * the incoming child arrives from the right (translateX +26→0, fade, 220ms)\n * delayed by the 80ms exit lead — one moving surface, ~300ms total.\n *\n * Variants (per child):\n * - \"slide\": questions — the directional stepper motion above.\n * - \"spring\": the InterstitialCard value beat — scale 0.96→1 spring pop\n * (the shell's springier variant, kept from AnimatedCard).\n * - \"fade\": loaders — enter/exit as a plain 120ms fade so the \"thinking\"\n * state hands off to the first card without a hard cut.\n *\n * `direction` mirrors the whole slide for BACK navigation, so a step the user\n * returns to comes back from the side it left toward. Spatial consistency: if\n * \"next\" pushes left, \"back\" must pull right, or the stack loses its geography.\n *\n * `layout` picks how the moving surface is sized. \"fill\" (default) is the\n * full-screen flow, where the pane owns the viewport. \"auto\" is for a pane\n * inside an auto-height container (the CenteredModal card): `flex: 1` there\n * resolves to a ZERO-height child under Yoga — flex-basis 0 with no free space\n * to grow into — so the in-flow layer must size to its own content instead.\n *\n * Reduce motion: every handoff becomes a 120ms crossfade, no translate/scale.\n * All values run on the native driver (transform/opacity only); in-flight\n * animations are stopped when superseded and on unmount.\n *\n * This SUPERSEDES the enter-only AnimatedCard: same keyed-remount contract from\n * OnboardingFlow (the child renderer stays keyed on the message id), plus the\n * paired exit layer the design calls for.\n */\nimport React, { useEffect, useRef, useState } from \"react\";\nimport { Animated, Easing, StyleSheet } from \"react-native\";\nimport {\n CARD_ENTER_MS,\n CARD_ENTER_SCALE_FROM,\n CARD_EXIT_LEAD_MS,\n CARD_EXIT_MS,\n CARD_SLIDE_PX,\n CARD_SPRING,\n LOADER_HANDOFF_FADE_MS,\n REDUCED_FADE_MS,\n WIRE_BEZIER,\n scaledMs,\n} from \"../motion/motionSpec\";\nimport { useReducedMotion } from \"../motion/useReducedMotion\";\n\nconst easeWire = Easing.bezier(...WIRE_BEZIER);\n\nexport type CardHandoffVariant = \"slide\" | \"spring\" | \"fade\";\n\n/** Which way the stack is moving. \"backward\" mirrors the slide (see the header note). */\nexport type CardHandoffDirection = \"forward\" | \"backward\";\n\n/** How the moving surface is sized. \"auto\" = a pane in an auto-height container. */\nexport type CardHandoffLayout = \"fill\" | \"auto\";\n\nexport type CardHandoffProps = {\n /** Identity of the current child (e.g. the message id). A change runs the handoff. */\n transitionKey: string;\n /** How the CURRENT child enters (and, snapshotted, how it will later exit). */\n variant?: CardHandoffVariant;\n /** Travel direction of THIS transition. Default \"forward\". */\n direction?: CardHandoffDirection;\n /** Sizing mode. Default \"fill\" (the full-screen flow). */\n layout?: CardHandoffLayout;\n children: React.ReactNode;\n};\n\ntype Snapshot = { key: string; node: React.ReactNode; variant: CardHandoffVariant };\n\nconst _CardHandoff: React.FC<CardHandoffProps> = ({\n transitionKey,\n variant = \"slide\",\n direction = \"forward\",\n layout = \"fill\",\n children,\n}) => {\n const reduced = useReducedMotion();\n const [leaving, setLeaving] = useState<Snapshot | null>(null);\n\n const enterT = useRef(new Animated.Value(0)).current;\n const exitT = useRef(new Animated.Value(0)).current;\n // The previous render's child, snapshotted so it can play the exit layer.\n const prev = useRef<Snapshot>({ key: transitionKey, node: children, variant });\n const running = useRef<{ enter?: Animated.CompositeAnimation; exit?: Animated.CompositeAnimation }>({});\n const mounted = useRef(false);\n\n // One orchestration effect, run every render: refresh the snapshot while the key\n // is stable; on a key change, snapshot the old child into the exit layer and run\n // the paired exit+enter. (No deps array on purpose — `children` is a fresh node\n // every parent render and must keep the snapshot current.)\n useEffect(() => {\n const last = prev.current;\n prev.current = { key: transitionKey, node: children, variant };\n\n // First mount: enter only (the design's initial reveal), nothing exits.\n if (!mounted.current) {\n mounted.current = true;\n running.current.enter = startEnter(enterT, variant, reduced, 0);\n return;\n }\n if (last.key === transitionKey) return;\n\n running.current.enter?.stop();\n running.current.exit?.stop();\n setLeaving(last);\n enterT.setValue(0);\n exitT.setValue(0);\n\n const exitDuration = reduced\n ? REDUCED_FADE_MS\n : last.variant === \"fade\"\n ? LOADER_HANDOFF_FADE_MS\n : CARD_EXIT_MS;\n const exit = Animated.timing(exitT, {\n toValue: 1,\n duration: scaledMs(exitDuration),\n easing: easeWire,\n useNativeDriver: true,\n });\n running.current.exit = exit;\n exit.start();\n\n const enter = startEnter(enterT, variant, reduced, reduced ? 0 : CARD_EXIT_LEAD_MS, () =>\n setLeaving(null),\n );\n running.current.enter = enter;\n });\n\n // Stop anything in flight on unmount (leaked animations drain the JS thread).\n useEffect(\n () => () => {\n running.current.enter?.stop();\n running.current.exit?.stop();\n },\n [],\n );\n\n const enterStyle = {\n opacity: enterT,\n transform: transformFor(variant, enterT, reduced, \"enter\", direction),\n };\n\n return (\n <>\n {leaving ? (\n <Animated.View\n pointerEvents=\"none\"\n style={[\n StyleSheet.absoluteFill,\n {\n opacity: exitT.interpolate({ inputRange: [0, 1], outputRange: [1, 0] }),\n // The CURRENT direction drives BOTH layers: one transition, one geography.\n transform: transformFor(leaving.variant, exitT, reduced, \"exit\", direction),\n },\n ]}\n >\n {leaving.node}\n </Animated.View>\n ) : null}\n <Animated.View key={variant} style={[layout === \"fill\" ? styles.fill : null, enterStyle]}>\n {children}\n </Animated.View>\n </>\n );\n};\n\n/** Kick off the enter animation for a variant (spring for the value beat, timing otherwise). */\nconst startEnter = (\n value: Animated.Value,\n variant: CardHandoffVariant,\n reduced: boolean,\n delay: number,\n onDone?: () => void,\n): Animated.CompositeAnimation => {\n value.setValue(0);\n const anim =\n !reduced && variant === \"spring\"\n ? Animated.spring(value, {\n toValue: 1,\n delay: scaledMs(delay),\n damping: CARD_SPRING.damping,\n stiffness: CARD_SPRING.stiffness,\n mass: CARD_SPRING.mass,\n useNativeDriver: true,\n })\n : Animated.timing(value, {\n toValue: 1,\n delay: scaledMs(delay),\n duration: scaledMs(\n reduced\n ? REDUCED_FADE_MS\n : variant === \"fade\"\n ? LOADER_HANDOFF_FADE_MS\n : CARD_ENTER_MS,\n ),\n easing: easeWire,\n useNativeDriver: true,\n });\n anim.start(({ finished }) => {\n if (finished) onDone?.();\n });\n return anim;\n};\n\n/**\n * Transform track per variant/layer; reduce motion = opacity-only crossfade.\n *\n * `travel` mirrors the X axis: forward pushes the stack left (new arrives from the\n * right), backward pulls it right (new arrives from the left).\n */\nconst transformFor = (\n variant: CardHandoffVariant,\n t: Animated.Value,\n reduced: boolean,\n layer: \"enter\" | \"exit\",\n travel: CardHandoffDirection,\n) => {\n if (reduced || variant === \"fade\") return [];\n if (variant === \"spring\" && layer === \"enter\") {\n return [{ scale: t.interpolate({ inputRange: [0, 1], outputRange: [CARD_ENTER_SCALE_FROM, 1] }) }];\n }\n const slide = travel === \"forward\" ? CARD_SLIDE_PX : -CARD_SLIDE_PX;\n return [\n {\n translateX: t.interpolate({\n inputRange: [0, 1],\n outputRange: layer === \"enter\" ? [slide, 0] : [0, -slide],\n }),\n },\n ];\n};\n\nexport const CardHandoff = React.memo(_CardHandoff);\n\nconst styles = StyleSheet.create({\n fill: { flex: 1 },\n});\n","/**\n * ModalPaneHost — \"slide between panes inside a CenteredModal\", the shared primitive\n * behind BOTH the review gate's phase changes and the questionnaire's step changes.\n * One primitive, two call sites: the two gates must never drift apart on feel.\n *\n * It does NOT reimplement the slide. `CardHandoff` already owns the design's\n * \"cards hand off, they don't cut\" motion (exit left 180ms / enter right 220ms, 80ms\n * lead, reduce-motion crossfade), so this delegates to it in `layout=\"auto\"` mode and\n * adds the ONE thing a modal needs that a full-screen flow does not: a card that\n * resizes instead of snapping.\n *\n * WHY the height animation exists: the CenteredModal card is auto-height, so the moment\n * a taller/shorter pane mounts, the card would jump to its new height in a single frame\n * while the old pane is still sliding out — the exact \"jarring change\" the slide was\n * added to remove. Measuring the pane and easing the card to it keeps one continuous\n * surface.\n *\n * WHY it is the only non-native-driven value in the kit: height is a layout prop, so it\n * cannot ride the native driver. That is a deliberate, bounded exception — ONE property\n * on ONE small card, for ~220ms, while nothing is scrolling. The panes' own opacity and\n * translate (inside CardHandoff) stay on the native driver, and the two values live on\n * different views, so they never contend. Everything else in the kit stays transform/\n * opacity-only.\n *\n * Height is eased ONLY on a pane change. A multiline box growing as the user types also\n * fires onLayout, and animating that would make typing feel laggy — those resize\n * instantly. Reduce motion: the height snaps too (movement removed, crossfade kept).\n *\n * `header` is the STATIC slot: chrome that belongs to the card rather than to any one pane\n * (the questionnaire's back icon + step dots). It sits inside the measured container but\n * OUTSIDE the CardHandoff, which buys two things a caller cannot get by rendering it above\n * the host:\n * 1. It never slides, so it is never DUPLICATED mid-handoff. Anything inside CardHandoff\n * exists twice for ~300ms (the outgoing layer and the incoming one) — fine for a\n * question that is genuinely changing, wrong for chrome that is not. Two dot rows\n * cross-fading past each other is a bug, not a transition.\n * 2. Its size is part of the measured height, so dropping the header (form → thanks)\n * rides the same eased resize as the pane swap instead of snapping the card.\n */\nimport React, { useCallback, useEffect, useRef, useState } from \"react\";\nimport { Animated, Easing, StyleSheet, View, type LayoutChangeEvent } from \"react-native\";\n\nimport { CARD_ENTER_MS, WIRE_BEZIER, scaledMs } from \"../motion/motionSpec\";\nimport { useReducedMotion } from \"../motion/useReducedMotion\";\nimport { CardHandoff, type CardHandoffDirection } from \"./CardHandoff\";\n\nconst easeWire = Easing.bezier(...WIRE_BEZIER);\n\nexport type ModalPaneHostProps = {\n /** Identity of the pane on screen (phase name / step key). A change runs the handoff. */\n paneKey: string;\n /** Travel direction — \"backward\" mirrors the slide when the user steps back. */\n direction?: CardHandoffDirection;\n /**\n * Card-level chrome pinned above the sliding pane: measured with it, but never moved\n * and never duplicated by the handoff. Omit it (or pass null) and nothing renders —\n * dropping it between pane changes animates the card's height rather than snapping it.\n * The node owns its own bottom spacing; the host adds none (it has no theme).\n */\n header?: React.ReactNode;\n children: React.ReactNode;\n};\n\nconst _ModalPaneHost: React.FC<ModalPaneHostProps> = ({\n paneKey,\n direction = \"forward\",\n header,\n children,\n}) => {\n const reduced = useReducedMotion();\n const height = useRef(new Animated.Value(0)).current;\n /** Last height we settled on. null until the first real measurement lands. */\n const measured = useRef<number | null>(null);\n /** Drives the height style on only AFTER a real measurement, so nothing renders at 0. */\n const [sized, setSized] = useState(false);\n /** Set when the pane changed, cleared by the next measurement it causes. */\n const paneChanged = useRef(false);\n const running = useRef<Animated.CompositeAnimation | null>(null);\n\n // Runs on mount and on every pane change, always BEFORE the resulting onLayout (which\n // arrives from native after the commit) — so the next measurement knows why it fired.\n useEffect(() => {\n paneChanged.current = true;\n }, [paneKey]);\n\n useEffect(() => () => running.current?.stop(), []);\n\n const onLayout = useCallback(\n (event: LayoutChangeEvent) => {\n const next = Math.round(event.nativeEvent.layout.height);\n if (!next) return;\n const previous = measured.current;\n if (previous !== null && Math.abs(previous - next) < 1) return;\n\n const first = previous === null;\n const ease = !first && !reduced && paneChanged.current;\n paneChanged.current = false;\n measured.current = next;\n running.current?.stop();\n\n // First measurement, a reduce-motion user, or a plain content resize (typing):\n // take the new height immediately.\n if (!ease) {\n height.setValue(next);\n if (first) setSized(true);\n return;\n }\n\n const anim = Animated.timing(height, {\n toValue: next,\n duration: scaledMs(CARD_ENTER_MS),\n easing: easeWire,\n // height is layout, not transform — see the header note.\n useNativeDriver: false,\n });\n running.current = anim;\n anim.start();\n },\n [height, reduced],\n );\n\n return (\n <Animated.View style={sized ? { height, overflow: \"hidden\" } : undefined}>\n <View onLayout={onLayout} style={styles.pane}>\n {header}\n <CardHandoff transitionKey={paneKey} direction={direction} layout=\"auto\" variant=\"slide\">\n {children}\n </CardHandoff>\n </View>\n </Animated.View>\n );\n};\n\nexport const ModalPaneHost = React.memo(_ModalPaneHost);\nModalPaneHost.displayName = \"ModalPaneHost\";\n\nconst styles = StyleSheet.create({\n pane: { width: \"100%\" },\n});\n\nexport default ModalPaneHost;\n","/**\n * haptics.ts — the physical half of the kit's feedback layer: a tap you feel.\n *\n * `expo-haptics` is an OPTIONAL peer, loaded lazily via a guarded dynamic import so the\n * core kit never depends on it — the exact contract `reviews/storeReview.ts` uses for\n * `expo-store-review`. A host that has not installed it (or a platform without a taptic\n * engine) simply feels nothing: every call resolves to `false` and NOTHING throws. A\n * missed haptic must never break a gate.\n *\n * The module is deliberately fire-and-forget. Callers `void play(...)` from a press\n * handler and never await it, so a slow/absent native module can't delay a tap.\n *\n * Tones map to intent, not to a device API:\n * • \"selection\" — a value changed (picking a star).\n * • \"light\" — a light impact (moving to the next question).\n * • \"success\" — a task completed (the submit payoff, under the done check).\n *\n * NOT gated on reduce-motion: `prefers-reduced-motion` is a VESTIBULAR accommodation about\n * on-screen movement. Haptics are a non-visual channel and are often the accessibility\n * affordance a reduce-motion user relies on, so silencing them here would be the wrong\n * trade. Hosts that want no haptics leave `expo-haptics` uninstalled.\n */\n\n/** What a haptic is for, in product terms. Mapped to the native API inside `play`. */\nexport type HapticTone = \"selection\" | \"light\" | \"success\";\n\n/**\n * Play a haptic when the optional peer is available. Resolves `true` when a haptic was\n * actually requested, `false` when the peer is absent or the native call failed. NEVER\n * throws and never rejects — the result is advisory (tests/analytics), not control flow.\n */\nexport const play = async (tone: HapticTone): Promise<boolean> => {\n try {\n const mod = await import(\"expo-haptics\");\n if (!mod) return false;\n if (tone === \"selection\") {\n await mod.selectionAsync();\n return true;\n }\n if (tone === \"light\") {\n await mod.impactAsync(mod.ImpactFeedbackStyle.Light);\n return true;\n }\n await mod.notificationAsync(mod.NotificationFeedbackType.Success);\n return true;\n } catch {\n // Peer not installed, unsupported platform, or the native call failed — stay silent.\n return false;\n }\n};\n\n/**\n * Fire-and-forget `play`. The call site reads as the intent (\"a star was tapped\"), and a\n * rejected/absent native module can never surface as an unhandled rejection.\n */\nexport const playHaptic = (tone: HapticTone): void => {\n void play(tone);\n};\n","/**\n * mergeThemeOver — merge a partial theme over an ARBITRARY base theme.\n *\n * Sibling of `mergeTheme` (defaultTheme.ts), which merges over the DEFAULT theme only.\n * Gates need the other shape: a `theme` prop merged over whatever the host's active\n * ThemeContext currently is. That helper was copy-pasted verbatim into ReviewGate and\n * QuestionnaireGate; it lives here once instead, so a new theme section can never be\n * added to one gate and forgotten in the other.\n *\n * Two levels deep, matching `mergeTheme`: the theme is intentionally shallow, so each\n * known sub-object is merged and scalars overwrite.\n */\nimport type { OnboardingTheme } from \"./types\";\n\nexport const mergeThemeOver = (\n base: OnboardingTheme,\n override?: Partial<OnboardingTheme>,\n): OnboardingTheme => {\n if (!override) return base;\n return {\n colors: { ...base.colors, ...override.colors },\n fonts: { ...base.fonts, ...override.fonts },\n radius: { ...base.radius, ...override.radius },\n spacing: { ...base.spacing, ...override.spacing },\n button: { ...base.button, ...override.button },\n };\n};\n","/**\n * decision.ts — the pure decision logic for the review gate.\n *\n * `decideReview` is the AI seam, mirroring `selectTourSteps` exactly: ONE function that\n * turns an optional injected decision into the live verdict. No injected decision → the\n * local rules stand; a server decision present → it OVERRIDES them. Building the seam now\n * is the whole cost of being AI-ready: today the host passes local rules only; later the\n * Wire server computes `{fire, reason}` from the onboarding learnings and the host passes\n * it straight in — zero app changes.\n *\n * // v1 (local rules only)\n * const verdict = decideReview(evaluateGate(rules, signals));\n *\n * // AI on (server-provided decision wins)\n * const verdict = decideReview(evaluateGate(rules, signals), serverDecision);\n *\n * `evaluateGate`, `routeRating`, and `buildReviewSubmission` are pure so the gate logic\n * is verifiable in isolation (the kit has no test runner; correctness lives in these).\n */\nimport type {\n RatingRoute,\n ReviewDecision,\n ReviewSubmission,\n} from \"./types\";\n\n/**\n * The seam. `decision` absent → the local verdict stands; present → it overrides.\n * Same contract philosophy as `selectTourSteps(catalog, selection?)`.\n */\nexport const decideReview = (\n local: ReviewDecision,\n decision?: ReviewDecision,\n): ReviewDecision => decision ?? local;\n\n/** The signals the local rules evaluate against (read from injected sync storage + host). */\nexport interface GateSignals {\n /** Sessions observed on this device (incl. the current one). */\n sessions: number;\n /** Tracked app events observed (host-owned). */\n events: number;\n /** Epoch ms of the last time the gate was shown, or null if never. */\n lastShownAt: number | null;\n /** Whether the once-per-version gate is already satisfied. */\n seen: boolean;\n /** Current epoch ms (injected so the function stays pure). */\n now: number;\n}\n\n/** The resolved local rules (defaults applied). */\nexport interface GateRules {\n enabled: boolean;\n minSessions: number;\n minEvents: number;\n cooldownDays: number;\n oncePerVersion: boolean;\n}\n\n/** Fill a partial `ReviewConfig` with the rule defaults. */\nexport const resolveRules = (config: {\n enabled?: boolean;\n minSessions?: number;\n minEvents?: number;\n cooldownDays?: number;\n oncePerVersion?: boolean;\n}): GateRules => ({\n enabled: config.enabled ?? true,\n // FAIL-CLOSED on the first session (behavior change, 0.11.0). An unconfigured host with no\n // server decision must NOT prompt on the very first mount: `minSessions` defaults to 2 so the\n // gate needs at least a second session before the LOCAL rules can fire. This is the client-side\n // floor only — a server `decision` (from `fetchReviewDecision`) still OVERRIDES everything, and a\n // host that genuinely wants first-session prompting can set `minSessions: 1` (or `0`) explicitly.\n // Mirrors the 2026-07-16 incident: a first-session user, no server decision, got the prompt and\n // left 1 star. See `evaluateGate` (`min_sessions` reason) and the CHANGELOG.\n minSessions: config.minSessions ?? 2,\n minEvents: config.minEvents ?? 0,\n cooldownDays: config.cooldownDays ?? 0,\n oncePerVersion: config.oncePerVersion ?? true,\n});\n\nconst MS_PER_DAY = 86_400_000;\n\n/**\n * Deterministic v1 local decision. Order: disabled → once-gate → min-sessions →\n * min-events → cooldown → fire. Reasons mirror the server evaluator's vocabulary.\n */\nexport const evaluateGate = (rules: GateRules, s: GateSignals): ReviewDecision => {\n if (!rules.enabled) return { fire: false, reason: \"disabled\" };\n if (rules.oncePerVersion && s.seen) return { fire: false, reason: \"already_shown\" };\n if (s.sessions < rules.minSessions) return { fire: false, reason: \"min_sessions\" };\n if (s.events < rules.minEvents) return { fire: false, reason: \"min_events\" };\n if (rules.cooldownDays > 0 && s.lastShownAt != null) {\n const elapsedDays = (s.now - s.lastShownAt) / MS_PER_DAY;\n if (elapsedDays < rules.cooldownDays) return { fire: false, reason: \"cooldown\" };\n }\n return { fire: true, reason: \"local_rules_met\" };\n};\n\n/**\n * Route a rating. 5 stars → the native store review; 1-4 → the feedback form. The\n * sentiment question itself is neutral — this is the ONLY place 5 is treated specially,\n * never a button labeled \"rate us 5 stars\" (see the store-policy notes in the README).\n */\nexport const routeRating = (stars: number): RatingRoute =>\n stars >= 5 ? \"store\" : \"feedback\";\n\n/** Build the `POST /v1/reviews` body. Feedback text/contact belong ONLY here, never in events. */\nexport const buildReviewSubmission = (input: {\n stars: number;\n feedbackText?: string;\n suggestion?: string;\n contact?: string;\n sessionId?: string;\n source?: string;\n meta?: Record<string, unknown>;\n}): ReviewSubmission => {\n const body: ReviewSubmission = { stars: input.stars };\n const text = input.feedbackText?.trim();\n if (text) body.feedback_text = text;\n const suggestion = input.suggestion?.trim();\n if (suggestion) body.suggestion = suggestion;\n const contact = input.contact?.trim();\n if (contact) body.contact = contact;\n if (input.sessionId) body.session_id = input.sessionId;\n if (input.source) body.source = input.source;\n if (input.meta && Object.keys(input.meta).length > 0) body.meta = input.meta;\n return body;\n};\n","/**\n * feedbackForm — pure sizing helpers for the 1–4★ feedback form, kept out of the\n * React component so the \"≈4 lines tall, capped at 5\" rule is verifiable in\n * isolation (the reviews module's correctness lives in these pure functions, the\n * same convention as `decision.ts` / `presentation.ts`).\n *\n * Malik's ask: the feedback field should be a multiline box \"around 3 to 5 lines\"\n * tall, not a single-line input. We size it from the theme's body line-height so\n * it scales with the app's font config.\n */\nimport type { OnboardingFonts } from \"../theme/types\";\nimport { bodyStyle } from \"../theme/typography\";\n\n/** Lines the box shows at rest, and the cap it can grow to. */\nexport const FEEDBACK_MIN_LINES = 4;\nexport const FEEDBACK_MAX_LINES = 5;\n\nexport interface FeedbackInputHeights {\n /** Resting height — the box opens at ~4 lines so it reads as \"write a few words\". */\n minHeight: number;\n /** Growth cap — it never balloons past ~5 lines; scroll takes over beyond that. */\n maxHeight: number;\n}\n\n/**\n * Derive the multiline feedback box's min/max height from the theme's body\n * line-height. `minHeight = MIN_LINES × lineHeight`, capped at\n * `MAX_LINES × lineHeight`, plus the field's vertical padding (top + bottom) so\n * the requested number of text lines is actually visible inside the padding.\n */\nexport const feedbackInputHeights = (\n fonts: OnboardingFonts,\n verticalPadding = 0,\n): FeedbackInputHeights => {\n const lineHeight = bodyStyle(fonts).lineHeight ?? Math.round(fonts.baseSize * fonts.scale * 1.5);\n const pad = verticalPadding * 2;\n return {\n minHeight: lineHeight * FEEDBACK_MIN_LINES + pad,\n maxHeight: lineHeight * FEEDBACK_MAX_LINES + pad,\n };\n};\n","/**\n * centeredModalMotion - the pure entrance spec for the content-agnostic CenteredModal shell.\n *\n * WHY split out: the shell UI (CenteredModal) is RN and cannot run under the kit's node:test\n * runner, but the entrance math is pure and worth pinning. Keep the animation spec here so both\n * the component and the tests read one source, and so any module (reviews, questionnaire) can\n * reuse the shell without depending on another feature module.\n *\n * The centered popup uses RN's built-in `Animated` (native driver), the same idiom as\n * CompletionView/CardHandoff, so the shell stays dependency-free (no reanimated).\n *\n * NOTE: `reviews/presentation.ts` re-exports these under their original names\n * (`REVIEW_MODAL_ANIM`, `entranceFrame`) for back-compat with the reviews public API.\n */\n\n/**\n * The entrance spec for the centered popup. Values chosen to match the boilerplate's\n * `animated-modal` feel (a springy zoom-in) with the kit's own native-driver idiom:\n * • backdrop fades 0 -> `backdropOpacity`\n * • card springs from `scaleFrom` -> 1 and fades 0 -> 1\n */\nexport const CENTERED_MODAL_ANIM = {\n /** Spring config for the card scale/opacity entrance (mirrors CompletionView's pop). */\n enterSpring: { friction: 7, tension: 80 },\n /** Exit timing in ms (fade + shrink back out before unmount). */\n exitMs: 160,\n /** Card scale at rest (progress 0); grows to 1 at progress 1. */\n scaleFrom: 0.9,\n /** Peak dim of the backdrop at progress 1. */\n backdropOpacity: 0.55,\n} as const;\n\n/** A single frame of the entrance, for a progress value in [0..1] (clamped). Pure, testable. */\nexport const centeredModalFrame = (\n progress: number,\n backdropMax: number = CENTERED_MODAL_ANIM.backdropOpacity,\n): { cardOpacity: number; cardScale: number; backdropOpacity: number } => {\n const p = progress < 0 ? 0 : progress > 1 ? 1 : progress;\n const { scaleFrom } = CENTERED_MODAL_ANIM;\n return {\n cardOpacity: p,\n cardScale: scaleFrom + (1 - scaleFrom) * p,\n backdropOpacity: backdropMax * p,\n };\n};\n","/**\n * presentation.ts — the pure presentation + entrance-animation logic for the review gate.\n *\n * WHY split out: the gate UI (ReviewGate/ReviewModal) is RN and can't run under the kit's\n * node:test runner, but the presentation DEFAULT and the entrance math are pure and worth\n * pinning. Keep the animation spec here so both the component and the tests read one source.\n *\n * The centered popup uses RN's built-in `Animated` (native driver) — the same idiom as\n * CompletionView/CardHandoff — so the reviews module stays dependency-free (no reanimated).\n */\nimport type { ReviewPresentation } from \"./types\";\n\n/** Centered popup by default (Malik's ruling: the bottom-anchored inline card read as wrong). */\nexport const DEFAULT_PRESENTATION: ReviewPresentation = \"modal\";\n\n/** Resolve the presentation prop → the effective mode. Absent → the centered modal. */\nexport const resolvePresentation = (p?: ReviewPresentation): ReviewPresentation =>\n p ?? DEFAULT_PRESENTATION;\n\n/**\n * The centered-popup entrance spec + pure entrance-frame math now live in the neutral\n * `../motion/centeredModalMotion` (the shell was extracted to `components/CenteredModal` so the\n * questionnaire module can reuse it without importing reviews/). Re-exported here under their\n * original names so the reviews public API (reviews/index.ts) and presentation.test.ts stay valid.\n */\nexport {\n CENTERED_MODAL_ANIM as REVIEW_MODAL_ANIM,\n centeredModalFrame as entranceFrame,\n} from \"../motion/centeredModalMotion\";\n","/**\n * CenteredModal - the content-agnostic centered-popup shell.\n *\n * Renders its children as an alert-style card in the MIDDLE of the screen, over a dimmed\n * backdrop, with a spring/fade entrance. It knows nothing about what it presents: the reviews\n * gate uses it (via the `ReviewModal` back-compat re-export) and the questionnaire module reuses\n * the same shell, neither depends on the other.\n *\n * Adapted from the mobile-launcher boilerplate's `animated-modal` (transparent RN Modal +\n * centered container + backdrop-tap dismiss + content stop-propagation + springy zoom-in), but\n * reimplemented on RN's built-in `Animated` (native driver) so the shell stays dependency-free:\n * the boilerplate uses react-native-reanimated, which the kit does not.\n *\n * Dismissal (backdrop tap / Android back / the imperative `close()` handle) animates the card\n * out, then fires `onDismiss`. It never auto-closes on resolve: the host keeps the card showing\n * whatever payoff it wants (a thank-you, a summary) until the user taps the backdrop to close,\n * exactly like CompletionView.\n */\nimport React, {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from \"react\";\nimport { Animated, BackHandler, Easing, Modal, Pressable, StyleSheet } from \"react-native\";\nimport { SafeAreaView } from \"react-native-safe-area-context\";\n\nimport type { OnboardingTheme } from \"../theme/types\";\nimport { REDUCED_FADE_MS } from \"../motion/motionSpec\";\nimport { useReducedMotion } from \"../motion/useReducedMotion\";\nimport { CENTERED_MODAL_ANIM } from \"../motion/centeredModalMotion\";\n\n/** Imperative handle so an in-card \"Not now\"/\"Skip\" link can trigger the exit animation. */\nexport type CenteredModalHandle = { close: () => void };\n\nexport type CenteredModalProps = {\n /** The (already merged) active theme - card surface/radius/spacing come from here. */\n theme: OnboardingTheme;\n /** Whether a backdrop tap / back press dismisses. Default true (never a dead end). */\n dismissible?: boolean;\n /** Fired once, AFTER the exit animation, on any dismiss. Wire to the host's resolve. */\n onDismiss: () => void;\n children: React.ReactNode;\n};\n\nconst _CenteredModal = forwardRef<CenteredModalHandle, CenteredModalProps>(\n ({ theme, dismissible = true, onDismiss, children }, ref) => {\n const progress = useRef(new Animated.Value(0)).current;\n const [open, setOpen] = useState(true);\n const closingRef = useRef(false);\n const reduced = useReducedMotion();\n\n // Entrance: spring the card in (scale + fade); the backdrop fades up in lock-step.\n // Reduce motion: a short fade to the settled card, no spring/scale.\n useEffect(() => {\n const anim = reduced\n ? Animated.timing(progress, {\n toValue: 1,\n duration: REDUCED_FADE_MS,\n useNativeDriver: true,\n })\n : Animated.spring(progress, {\n toValue: 1,\n useNativeDriver: true,\n friction: CENTERED_MODAL_ANIM.enterSpring.friction,\n tension: CENTERED_MODAL_ANIM.enterSpring.tension,\n });\n anim.start();\n return () => anim.stop();\n }, [progress, reduced]);\n\n const close = useCallback(() => {\n if (closingRef.current) return;\n closingRef.current = true;\n Animated.timing(progress, {\n toValue: 0,\n duration: reduced ? REDUCED_FADE_MS : CENTERED_MODAL_ANIM.exitMs,\n easing: Easing.in(Easing.cubic),\n useNativeDriver: true,\n }).start(() => {\n setOpen(false);\n onDismiss();\n });\n }, [progress, onDismiss, reduced]);\n\n useImperativeHandle(ref, () => ({ close }), [close]);\n\n // Android hardware back -> dismiss (when allowed). Always consume so it never pops a route.\n useEffect(() => {\n const sub = BackHandler.addEventListener(\"hardwareBackPress\", () => {\n if (dismissible) close();\n return true;\n });\n return () => sub.remove();\n }, [dismissible, close]);\n\n const onBackdrop = useCallback(() => {\n if (dismissible) close();\n }, [dismissible, close]);\n\n const backdropOpacity = progress.interpolate({\n inputRange: [0, 1],\n outputRange: [0, CENTERED_MODAL_ANIM.backdropOpacity],\n });\n // Reduce motion: the card fades in already settled (no zoom).\n const cardScale = reduced\n ? 1\n : progress.interpolate({\n inputRange: [0, 1],\n outputRange: [CENTERED_MODAL_ANIM.scaleFrom, 1],\n });\n\n return (\n <Modal\n visible={open}\n transparent\n animationType=\"none\"\n statusBarTranslucent\n onRequestClose={dismissible ? close : undefined}\n >\n {/* Dimmed backdrop (fades with the entrance). */}\n <Animated.View\n style={[styles.backdrop, { opacity: backdropOpacity }]}\n pointerEvents=\"none\"\n />\n\n {/* Full-screen tap target: a tap outside the card dismisses. */}\n <Pressable\n style={styles.fill}\n onPress={onBackdrop}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Dismiss\"\n >\n <SafeAreaView style={styles.center} pointerEvents=\"box-none\">\n {/* Inner Pressable swallows taps so the card itself never dismisses. */}\n <Pressable onPress={() => {}} style={styles.cardWrap}>\n <Animated.View\n accessibilityViewIsModal\n accessibilityRole=\"alert\"\n style={[\n styles.card,\n {\n backgroundColor: theme.colors.surface,\n borderRadius: theme.radius.lg,\n padding: theme.spacing.lg,\n gap: theme.spacing.md,\n opacity: progress,\n transform: [{ scale: cardScale }],\n },\n ]}\n >\n {children}\n </Animated.View>\n </Pressable>\n </SafeAreaView>\n </Pressable>\n </Modal>\n );\n },\n);\n\n_CenteredModal.displayName = \"CenteredModal\";\n\nexport const CenteredModal = React.memo(_CenteredModal);\n\nconst styles = StyleSheet.create({\n fill: { flex: 1 },\n backdrop: { position: \"absolute\", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: \"#000\" },\n center: { flex: 1, alignItems: \"center\", justifyContent: \"center\", padding: 24 },\n cardWrap: { width: \"100%\", maxWidth: 420 },\n card: {\n width: \"100%\",\n // Soft elevation so the popup reads as lifted above the app in both themes.\n shadowColor: \"#000\",\n shadowOffset: { width: 0, height: 8 },\n shadowOpacity: 0.28,\n shadowRadius: 24,\n elevation: 12,\n },\n});\n\nexport default CenteredModal;\n","/**\n * storeReview.ts — the 5-star route: ask for a native in-app review, or fall back to\n * opening the store listing.\n *\n * `expo-store-review` is an OPTIONAL peer, loaded lazily via a guarded dynamic import so\n * the core kit never depends on it. When present + available, we call\n * `StoreReview.requestReview()` (the OS decides whether to actually show the dialog — it is\n * quota'd, ~3/year on iOS, and NEVER guaranteed). When absent/unavailable, we open the\n * configured store URL as a graceful fallback. Both paths are best-effort and never throw.\n *\n * Store-policy: call this ONLY on the honest 5-star path, from a NEUTRAL prompt — never a\n * button labeled \"rate us 5 stars\". See the README store-policy notes.\n */\nimport { Linking, Platform } from \"react-native\";\n\nimport type { ReviewStoreConfig } from \"./types\";\n\n/** The store deep-link for the current platform, or null if none is configured. */\nexport const storeUrl = (store: ReviewStoreConfig | undefined): string | null => {\n if (!store) return null;\n if (Platform.OS === \"ios\" && store.iosAppId) {\n return `itms-apps://itunes.apple.com/app/id${store.iosAppId}?action=write-review`;\n }\n if (Platform.OS === \"android\" && store.androidPackage) {\n return `market://details?id=${store.androidPackage}`;\n }\n return store.storeUrl ?? null;\n};\n\n/** Open the configured store listing. Best-effort; resolves false if there was nothing to open. */\nexport const openStoreListing = async (\n store: ReviewStoreConfig | undefined,\n): Promise<boolean> => {\n const url = storeUrl(store);\n if (!url) return false;\n try {\n await Linking.openURL(url);\n return true;\n } catch {\n return false;\n }\n};\n\n/**\n * Request the native in-app review when the optional peer is available; otherwise open the\n * store listing. Returns the path taken so the caller can fire the right analytics.\n */\nexport const requestStoreReview = async (\n store?: ReviewStoreConfig,\n): Promise<\"native\" | \"store_url\" | \"none\"> => {\n try {\n const mod = await import(\"expo-store-review\");\n if (mod && (await mod.isAvailableAsync())) {\n await mod.requestReview();\n return \"native\";\n }\n } catch {\n // Peer not installed or the native call failed — fall through to the URL fallback.\n }\n const opened = await openStoreListing(store);\n return opened ? \"store_url\" : \"none\";\n};\n","/**\n * reportClientEvent — forward DEVICE-ONLY onboarding events to the Wire AI analytics\n * backend (`POST {serverUrl}/v1/events`), completing the funnel for events the server\n * can't observe on its own.\n *\n * The backend already records the server-observable funnel during the A2A flow\n * (`session_started`, `screen_shown`, `answer_submitted`, `completed`, `llm_fallback`,\n * and even `screen_skipped` — it derives that from the kit's skip sentinel). The one\n * event no server request can capture is `dropped`: the user closing the app / unmounting\n * the flow without finishing. That's what this reporter is for.\n *\n * Contract (server: routers/onboarding.py → analytics/events.py):\n * POST {serverUrl}/v1/events\n * Authorization: Bearer {apiKey}\n * { \"events\": [ { event_type, session_id, screen_index?, component?, question_key?,\n * latency_ms?, meta?, device?, user_context? } ] }\n * The server fills `app_id` + `environment` from the resolving key (never send app_id),\n * and silently skips malformed events — one bad payload never fails the batch.\n *\n * ⚠️ Correlation: `session_id` MUST equal the A2A `contextId` the server uses to key the\n * server-side events, or the funnel report (which groups by `session_id`) treats this as a\n * phantom session. See `makeSessionId` + WireOnboarding for how the kit seeds it.\n *\n * Fire-and-forget: this never throws into the UI and never awaits — analytics must never\n * be able to break onboarding.\n */\nimport type { DeviceContext } from \"../device/deviceContext\";\n\n/** Event types a CLIENT may report. The rest of the funnel is server-side; sending those\n * here would double-count. `screen_skipped` is included for completeness, but the kit does\n * NOT emit it — the backend already derives it from the skip sentinel (see OnboardingFlow).\n * `client_fallback` is emitted by the kit when the AI flow degrades to the static fallback,\n * so the dashboard's fallback-rate counts the whole-flow case (distinct from the server's\n * per-turn `llm_fallback`). The server back-fills a `session_started` for it if unseen.\n * This is the SINGLE fallback signal — hosts must NOT also report their own.\n * `identify` binds the host's opaque `user_id` to this `session_id` (late binding — the user\n * registered during/after onboarding). It carries no funnel weight; the server maps the\n * session to the user and back-fills a `session_started` if it never saw the session.\n * `app_event` is the host's own in-app event (the `createAnalytics` façade's `track`/`screen`\n * route through the offline queue as first-class `ClientEvent`s). The server already ingests\n * it — `reportAppEvent` (reviews/transport) has fired `event_type='app_event'` all along; this\n * widening just lets the same shape flow through the durable queue instead of a blind POST. */\nexport type ClientEventType =\n | \"screen_skipped\"\n | \"dropped\"\n | \"client_fallback\"\n | \"identify\"\n | \"app_event\";\n\n/** One client-reported event. Mirrors the server's `OnboardingEvent` (client-settable fields). */\nexport type ClientEvent = {\n event_type: ClientEventType;\n /** Must match the server-side A2A contextId for this onboarding (see makeSessionId). */\n session_id: string;\n /** 0-based index of the screen the event refers to (matches server `screen_shown`). */\n screen_index?: number;\n component?: string;\n question_key?: string;\n latency_ms?: number;\n /** JSON-stringified extras; the server stores it verbatim. */\n meta?: string;\n /**\n * Privacy-label-neutral device snapshot (platform / form factor / locale / host appVersion).\n * Sent as an object; the server sanitizes + persists it and derives a coarse country. Old\n * servers ignore this unknown field — fully backward compatible. See device/deviceContext.ts.\n */\n device?: DeviceContext;\n /**\n * Host-injected, non-PII context (signup method, referral, plan, hashed user id). Old servers\n * ignore it. MUST NOT contain PII like raw emails — see the README `userContext` section.\n */\n user_context?: Record<string, string | number | boolean>;\n /**\n * The host's OPAQUE PSEUDONYMOUS user id (their internal id, NOT an email/name). Required on\n * `identify`, optional (rides along) on other events. Trimmed + capped at 128 chars host-side.\n * Lets the backend reconcile onboarding sessions to real users. Old servers ignore it.\n */\n user_id?: string;\n /**\n * Client-stamped timestamp of when the event was ENQUEUED on the device. Optional and ADDITIVE.\n *\n * TWO REPRESENTATIONS, on purpose:\n * - INTERNAL (epoch-ms `number`): what the offline queue stamps at enqueue time (see\n * `createEventQueue`), so two otherwise byte-identical events fired seconds apart (a genuine\n * repeat, e.g. the user taps \"share\" twice) are NOT collapsed by the queue's identical-JSON\n * de-dup — while two truly simultaneous re-enqueues of the same instant (a redundant\n * re-render) still share a `ts` and collapse. The de-dup signature depends on this number.\n * - WIRE (ISO8601 UTC `string`): what actually leaves the device. {@link buildEventsRequest}\n * converts the number on its way out, because the server declares `ts: str | None` and\n * pydantic v2 does NOT coerce a number into it — a numeric `ts` made the server answer HTTP\n * 200 with `{written: 0, skipped: N, errors: [{field: \"ts\", reason: \"validation_error\"}]}`,\n * silently discarding EVERY `app_event` through 0.13.0.\n *\n * A caller-set ISO string is passed through as-is. Never a wall-clock the server trusts (it\n * derives its own receive time); an old/strict server that does not model it ignores the field.\n */\n ts?: number | string;\n};\n\n/** Where to POST. Derived from `WireOnboardingConfig` (`serverUrl` + `apiKey`). */\nexport type ClientEventTarget = {\n /** Base server URL (same as `WireOnboardingConfig.serverUrl`); `/v1/events` is appended. */\n serverUrl: string;\n /** Tenant API key; sent as `Authorization: Bearer`. */\n apiKey: string;\n};\n\n/**\n * A unique-per-onboarding session id. Used both as the client event `session_id` AND as the\n * seed the kit forwards to the backend so the SERVER adopts it as the A2A `contextId` — making\n * client and server agree (see WireOnboarding + the SDK-correlation note in the kit docs).\n * No crypto dependency: timestamp + random is collision-safe for a single device's onboarding.\n */\nexport const makeSessionId = (): string =>\n `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;\n\n/**\n * Serialize the internal epoch-ms `ts` to the ISO8601 UTC string the WIRE requires.\n *\n * The server's event model declares `ts: str | None` and pydantic v2 does NOT coerce int → str, so\n * a numeric `ts` fails per-event validation: the endpoint still answers HTTP 200, but with\n * `{written: 0, skipped: N, errors: [{index, reason: \"validation_error\", field: \"ts\"}]}` — every\n * `app_event` from `track()`/`screen()` silently discarded behind a green response.\n *\n * WHY HERE and not at the queue's `stamp()`: this is the single choke point all FOUR send paths go\n * through (offline queue, fire-and-forget, awaitable, session-start). Converting here leaves the\n * queue's numeric `ts` — and therefore its identical-JSON de-dup signature — exactly as it was, and\n * it also converts the persisted 0.13.0 backlogs (which hold a numeric `ts`) on their way out.\n *\n * NEVER mutates the caller's event: an event that needs a change is copied. A string `ts` (a\n * caller-set ISO stamp) and an absent `ts` pass through untouched. A non-finite (`NaN`/`Infinity`)\n * or out-of-range number — the latter makes `toISOString` throw — drops the `ts` field from the\n * copy rather than killing the whole batch.\n */\nconst toWireEvents = (events: ClientEvent[]): ClientEvent[] =>\n events.map((event) => {\n if (typeof event.ts !== \"number\") return event;\n const { ts, ...rest } = event;\n if (!Number.isFinite(ts)) return rest;\n try {\n return { ...rest, ts: new Date(ts).toISOString() };\n } catch {\n // Out-of-range epoch-ms — send the event WITHOUT a ts rather than lose the batch.\n return rest;\n }\n });\n\n/**\n * The ONE place the `/v1/events` POST is described (url + method + headers + body). Both the\n * fire-and-forget {@link reportClientEvents} and the awaitable {@link reportClientEventsAwait}\n * build their request here so there is a SINGLE definition of the events transport — no second\n * copy of the endpoint path, headers, or envelope shape to drift. Returns `null` when there is\n * nothing to send (no target / no events) or serialization throws, so callers just bail.\n *\n * It is also where the internal epoch-ms `ts` becomes the wire's ISO8601 string — see\n * {@link toWireEvents} for why the conversion belongs at this choke point.\n */\nexport const buildEventsRequest = (\n target: { serverUrl: string; apiKey?: string } | undefined,\n events: ClientEvent[],\n): { url: string; init: RequestInit } | null => {\n if (!target?.serverUrl || events.length === 0) return null;\n try {\n const url = `${target.serverUrl.replace(/\\/$/, \"\")}/v1/events`;\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;\n const body = JSON.stringify({ events: toWireEvents(events) });\n return { url, init: { method: \"POST\", headers, body } };\n } catch {\n // URL construction or JSON serialization failed — nothing to send.\n return null;\n }\n};\n\n/** What the `/v1/events` endpoint says in its 200 body. The deployed server DOES send `errors[]`,\n * each entry carrying `{index, reason, field}` — `field` names the property that failed validation\n * (it is what identified the `ts` rejection), so it is folded into the reason string here. Still\n * read defensively: an older server sends no `errors` at all. */\ntype EventsAck = { written?: number; skipped: number; reasons: string[] };\n\n/**\n * Read the `/v1/events` ACK body. Resolves `undefined` when there is nothing readable — no `.json`\n * (an old server, a bare test mock), an already-consumed body, or a hostile response object. NEVER\n * throws, and NEVER dev-gated: the `skipped` count is a RETURN VALUE for the awaitable path, not only\n * a warning, so it has to be read in production too.\n *\n * ⚠️ The body can be read exactly once, so a caller that both decides on `skipped` AND warns must do\n * both from ONE call to this.\n */\nconst readEventsAck = async (res: unknown): Promise<EventsAck | undefined> => {\n try {\n const json = (res as { json?: () => Promise<unknown> } | null | undefined)?.json;\n if (typeof json !== \"function\") return undefined;\n const body = (await Promise.resolve(json.call(res))) as\n | { written?: unknown; skipped?: unknown; errors?: unknown }\n | null\n | undefined;\n const skipped = body?.skipped;\n if (typeof skipped !== \"number\" || !Number.isFinite(skipped)) return undefined;\n const reasons = Array.isArray(body?.errors)\n ? body.errors\n .map((e) => {\n const entry = e as { reason?: unknown; field?: unknown } | null;\n const reason = entry?.reason;\n if (typeof reason !== \"string\") return undefined;\n // `field` is the whole point of a validation error — a bare \"validation_error\" sends the\n // reader hunting; \"validation_error (field: ts)\" names the property the server refused.\n const field = entry?.field;\n return typeof field === \"string\" && field.length > 0\n ? `${reason} (field: ${field})`\n : reason;\n })\n .filter((r): r is string => typeof r === \"string\")\n : [];\n return { written: typeof body?.written === \"number\" ? body.written : undefined, skipped, reasons };\n } catch {\n // Unreadable / already-consumed body / hostile object — best-effort, treat as \"no ack\".\n return undefined;\n }\n};\n\n/**\n * The DISCARDED warning text, built from what the server ACTUALLY said.\n *\n * It used to staple a cause onto a bare integer — \"the usual cause is a missing or empty session_id\".\n * Since `ensureCurrentSessionId` shipped, no kit path can emit an event without a `session_id`, so\n * that is now the LEAST likely explanation, and naming it sent every reader looking in the one place\n * the problem is not. The endpoint folds four real failures and two idempotent no-ops into one\n * integer, so unless the server volunteers reasons, the honest thing to report is the count and where\n * the reason lives.\n */\nconst describeDiscarded = (ack: EventsAck): string =>\n `[wireai] the server ACCEPTED the /v1/events POST but DISCARDED ${ack.skipped} event(s) ` +\n \"(skipped in the response body) — they are gone, not retried. The server reported: \" +\n `skipped=${ack.skipped}${ack.written !== undefined ? `, written=${ack.written}` : \"\"}` +\n (ack.reasons.length > 0\n ? `, reasons: ${ack.reasons.join(\", \")}.`\n : \". It gave no reason (the endpoint folds every rejection into one count), so check the \" +\n \"server's ingest log for this request rather than guessing.\");\n\n/** RN sets this global; absent under node/SSR. Read defensively inside {@link warnOnSkippedEvents}. */\ndeclare const __DEV__: boolean | undefined;\n\n/**\n * Read the `/v1/events` ACK body and warn (dev builds only) when the server DISCARDED events.\n *\n * The endpoint answers HTTP **200** with `{ ok, written, skipped }` — an event it refuses is counted\n * in `skipped`, never surfaced in the status code. Every send path here reads `res.ok` alone, so a\n * whole batch can evaporate behind a green response. As of 0.13.0 ALL FOUR send paths consume the ack:\n * the offline queue, session-start, the fire-and-forget POST, and the awaitable one (which also acts\n * on it — see {@link reportClientEventsAwait}).\n *\n * LOG ONLY: returns immediately, never throws, and never influences retry / dequeue / return values.\n * A response with no usable `.json` (an old server, a test mock) is silently ignored.\n *\n * DEV-GATED FIRST: the `__DEV__` check is the FIRST statement, before the body is even looked at.\n * The check used to sit inside the `.then`, so a release build parsed the JSON of every\n * persistent-path POST to build a warning no one would ever read. Nothing here runs in production.\n */\nexport const warnOnSkippedEvents = (res: unknown): void => {\n if (typeof __DEV__ === \"undefined\" || !__DEV__) return;\n if (typeof console === \"undefined\" || !console.warn) return;\n void readEventsAck(res).then((ack) => {\n if (!ack || ack.skipped <= 0) return;\n console.warn(describeDiscarded(ack));\n });\n};\n\n/**\n * POST one or more client events, fire-and-forget. A missing/invalid target, a build error,\n * a missing `fetch`, or a network failure is swallowed — the call returns immediately and the\n * request (if any) runs in the background.\n */\nexport const reportClientEvents = (\n target: ClientEventTarget | undefined,\n events: ClientEvent[],\n): void => {\n try {\n const req = buildEventsRequest(target, events);\n if (!req) return;\n void fetch(req.url, req.init)\n .then((res) => {\n // Still fire-and-forget — nothing is retried, nothing is returned. But a batch that\n // evaporated behind a 200 is exactly what this path used to make invisible, so in dev it\n // now says so, like the queue and session-start paths already did.\n warnOnSkippedEvents(res);\n })\n .catch(() => {\n // Network/transport error — analytics is best-effort, swallow.\n });\n } catch {\n // A missing `fetch` — swallow.\n }\n};\n\n/** Convenience single-event wrapper around {@link reportClientEvents}. */\nexport const reportClientEvent = (\n target: ClientEventTarget | undefined,\n event: ClientEvent,\n): void => reportClientEvents(target, [event]);\n\n/**\n * AWAITABLE sibling of {@link reportClientEvents}: POST one or more client events through the SAME\n * `/v1/events` path, but resolve only once the server has RESPONDED — so a decision re-fetch fired\n * immediately after is guaranteed to see the event in the session stream (this is the guarantee\n * `wire.track` needs before it triggers decision revalidation). Never throws: a missing/invalid\n * target, a missing `fetch`, a network error, or a non-2xx status all resolve to `false`.\n *\n * ⚠️ 0.13.0: a 2xx is NO LONGER SUFFICIENT. The endpoint answers HTTP 200 with `{ ok, written,\n * skipped }` and counts an event it refuses in `skipped`, so this used to resolve `true` for an event\n * the server had thrown away — and `wire.track` then bumped decision revalidation, making every\n * subscribed gate re-fetch against a stream the action never entered. It now reads the ack and\n * resolves `false` when the server reports a positive `skipped`.\n *\n * BACKWARD COMPATIBLE BY CONSTRUCTION: only an explicit positive `skipped` demotes a 200. An old\n * server that sends no such field, a body that cannot be parsed, or a response with no `.json` at all\n * resolves `true` exactly as before — the change can produce no false negatives.\n */\nexport const reportClientEventsAwait = async (\n target: ClientEventTarget | undefined,\n events: ClientEvent[],\n): Promise<boolean> => {\n try {\n const req = buildEventsRequest(target, events);\n if (!req) return false;\n const res = await fetch(req.url, req.init);\n if (!res || !res.ok) return false;\n // ONE body read, used for both the verdict and the dev warning — it can only be read once.\n const ack = await readEventsAck(res);\n if (!ack || ack.skipped <= 0) return true;\n if (typeof __DEV__ !== \"undefined\" && __DEV__ && typeof console !== \"undefined\" && console.warn) {\n console.warn(describeDiscarded(ack));\n }\n return false;\n } catch {\n // Unreachable / missing-fetch / network — best-effort, report failure.\n return false;\n }\n};\n\n/** Convenience single-event wrapper around {@link reportClientEventsAwait}. */\nexport const reportClientEventAwait = (\n target: ClientEventTarget | undefined,\n event: ClientEvent,\n): Promise<boolean> => reportClientEventsAwait(target, [event]);\n","/**\n * warnInDev — the ONE developer-warning primitive.\n *\n * The kit warns a developer in four places (the onboarding host, the env-config helper, the analytics\n * façade, the current-session registry) and until 0.13.0 each carried its own copy of the same five\n * lines. Three were byte-identical; the fourth differed only by returning whether it actually warned.\n * Four copies of a guard is four chances for one of them to drift out of the `__DEV__` gate, which is\n * the failure that matters: a warning that runs in production is a string built for nobody.\n *\n * ON THE TREE-SHAKING NOTE THIS REPLACES: `analytics/currentSession` used to justify its copy as\n * keeping the module import-free for the tree-shaken `./analytics` bundle. That rationale had already\n * lapsed — the module imports `makeSessionId` from `./reportClientEvent` — and this module has no\n * imports of its own, so it adds one leaf to the graph and nothing to the bundle. The `treeShake`\n * canary still holds the real guarantee (the analytics graph reaches no UI module).\n */\n\n/** RN sets this global; absent under node/SSR. Read defensively, never assumed. */\ndeclare const __DEV__: boolean | undefined;\n\n/**\n * Emit a one-line developer warning, but ONLY in a dev build (RN `__DEV__`). No-op in prod/tests.\n *\n * Returns whether it ACTUALLY warned, which a caller holding a once-flag must honour: marking\n * \"already warned\" after a no-op would burn the single warning in production, and the one dev build\n * that needed it would then run silent.\n */\nexport const warnInDev = (message: string): boolean => {\n if (typeof __DEV__ !== \"undefined\" && __DEV__ && typeof console !== \"undefined\" && console.warn) {\n console.warn(message);\n return true;\n }\n return false;\n};\n","/**\n * currentSession — a tiny registry of the CURRENT per-open `session_id`.\n *\n * WHY it exists (kills the phantom-session): the per-open emitters (`reportSessionStart` and the\n * `useSessionStart` / `useLifecycleEvents` hooks) mint a fresh `session_id` for each app-open and\n * post `app.session_started` with it — so the SERVER knows that id. But other client paths\n * (`identify`, host `app_event`s through the analytics façade) used to reference a DIFFERENT id\n * (a frozen per-instance id), which the server had never seen, so it back-filled a synthetic\n * `session_started` — inflating session counts (the \"phantom-session\" bug).\n *\n * This registry is the single seam that lets those paths reuse the LIVE per-open session id the\n * server already ingested. `reportSessionStart` writes the current id here on every open; the façade\n * reads it so `identify`/app-events correlate to the real session instead of minting a phantom.\n *\n * ── WHY A globalThis SLOT, NOT A PLAIN MODULE VARIABLE ────────────────────────────────────────\n * This module is exported from TWO package entry points — the main `.` bundle (`src/index.ts`) and\n * the `./analytics` subpath (`src/analytics/index.ts`). Under `dist` resolution (node `import`/\n * `require`, which is how tests, SSR and some tooling load the kit) tsup inlines a SEPARATE copy of\n * this module into each bundle, so a plain `let` would give the SETTER (reached via `.` →\n * `reportSessionStart`) and the READER (reached via `./analytics` → façade / `userIdentity`) TWO\n * different variables: the reader would see `undefined` even after an open set the id, and gating\n * would fire under a null session id. On-device this was masked only because Metro's `react-native`\n * export condition resolves both subpaths back to this one `src/` file (a single instance) — a\n * bundler accident, not a guarantee.\n *\n * The bundler-agnostic fix: keep the ONE live value in a well-known `globalThis` slot keyed by a\n * `Symbol.for(...)`. `Symbol.for` uses the runtime-global symbol registry, so every inlined copy of\n * this module resolves the SAME symbol and reads/writes the SAME slot — one identity no matter how\n * many times the module is duplicated across bundles. `globalThis` is present and identical in\n * Hermes/React Native, Node and SSR (we never touch `window`), so this is safe on every host.\n *\n * PROCESS-LOCAL, NOT PERSISTED: the slot lives on the runtime global, so it tracks the CURRENT\n * process's open and a fresh open overwrites it. There is no cross-launch state.\n * `resetCurrentSessionId` clears the slot so a unit test starts from a clean registry.\n *\n * ── WHY `ensureCurrentSessionId` EXISTS (the silent-drop contract) ─────────────────────────────\n * The server's event model declares `session_id: str = Field(min_length=1)` — REQUIRED, non-empty.\n * `POST /v1/events` validates each event inside a try/except that increments a `skipped` counter and\n * still returns HTTP 200. So an event posted without a `session_id` is accepted by the wire and\n * DISCARDED by the server, and a fire-and-forget client can never learn it happened. That is the\n * worst of both: no error, no data. Screen tracking in a host that never mounted the lifecycle hook\n * fell into exactly that hole — every screen view posted, 200'd, and dropped.\n *\n * `ensureCurrentSessionId` closes it: it returns the registered id when an open HAS been registered\n * (unchanged behaviour for every host that fires `reportSessionStart` first), and otherwise mints one,\n * REGISTERS it, and returns it — so every later event in the process correlates to that same id\n * instead of each emitting its own orphan. A minted id is a fallback, not a substitute for a real\n * app-open: it warns once in dev, naming the fix.\n */\nimport { makeSessionId } from \"./reportClientEvent\";\n// The shared primitive returns whether it ACTUALLY warned, which the once-flag below depends on:\n// marking \"already warned\" after a prod no-op would burn the single warning and leave the one dev\n// build that needed it silent. (The old local copy justified itself as keeping this module\n// import-free for the tree-shaken analytics bundle; that had already lapsed — it imports\n// `makeSessionId` right here — and `utils/warnInDev` has no imports of its own.)\nimport { warnInDev } from \"../utils/warnInDev\";\n\n/**\n * Well-known key into the runtime-global symbol registry. `Symbol.for` (NOT a plain `Symbol()`) is\n * what makes this cross-bundle: it returns the SAME symbol for the same string across every copy of\n * this module, so duplicated inlined copies all address one slot.\n */\nconst CURRENT_SESSION_ID_SLOT: unique symbol = Symbol.for(\n \"@wireai/activation:currentSessionId\",\n);\n\ntype GlobalWithSlot = typeof globalThis & {\n [CURRENT_SESSION_ID_SLOT]?: string | undefined;\n};\n\nconst globalSlot = globalThis as GlobalWithSlot;\n\n/**\n * Record the current per-open `session_id`. Called by `reportSessionStart` when it emits an\n * app-open. A blank / non-string id is ignored (the previous id stays current). Idempotent.\n */\nexport const setCurrentSessionId = (id: string | undefined): void => {\n if (typeof id === \"string\" && id.length > 0) {\n globalSlot[CURRENT_SESSION_ID_SLOT] = id;\n }\n};\n\n/** The current per-open `session_id`, or `undefined` when no app-open has been registered yet. */\nexport const getCurrentSessionId = (): string | undefined =>\n globalSlot[CURRENT_SESSION_ID_SLOT];\n\n/** Test-only: forget the current session id so a unit test starts from a clean registry. */\nexport const resetCurrentSessionId = (): void => {\n globalSlot[CURRENT_SESSION_ID_SLOT] = undefined;\n};\n\n/** The one-time message. Hoisted so a prod mint does not rebuild a string nobody will read. */\nconst MINT_WARNING =\n \"[wireai] No app-open session was registered, so a session id was minted for this event \" +\n \"(the server drops an event that has no session_id, and still answers 200). Mount \" +\n \"useLifecycleEvents at your app root so events correlate to a real app-open.\";\n\n/**\n * \"Have we already warned about a minted session id?\" — its OWN `Symbol.for` slot, for the same\n * cross-bundle reason as the id itself: a plain module `let` would warn once per inlined copy, i.e.\n * once per bundle, not once per process. NOT cleared by `resetCurrentSessionId`: \"warn once\" is a\n * process-lifetime promise, and a test that resets the id between mints is still one process.\n */\nconst MINT_WARNED_SLOT: unique symbol = Symbol.for(\n \"@wireai/activation:currentSessionIdMintWarned\",\n);\n\ntype GlobalWithWarnSlot = typeof globalThis & { [MINT_WARNED_SLOT]?: boolean };\n\nconst warnSlot = globalThis as GlobalWithWarnSlot;\n\n/**\n * The current per-open `session_id`, MINTING and registering one when no app-open has been\n * registered yet. Always returns a non-empty string. Idempotent (a second call returns the same id)\n * and never throws.\n *\n * Use this on every path that puts a `session_id` on the wire. The server REQUIRES a non-empty\n * `session_id` and drops the event otherwise while still answering 200 (see the module header), so\n * \"no id yet\" must never mean \"send it without one\".\n *\n * BACKWARD-COMPATIBLE BY CONSTRUCTION: when `reportSessionStart` / `useLifecycleEvents` has already\n * registered the real per-open id, this is `getCurrentSessionId()` and nothing changes. It only ever\n * mints in the case that used to produce a silently discarded event.\n *\n * A mint means the host never registered an app-open, so the minted id is one the server has not\n * seen a `session_started` for — the events land, but the session is thinner than a real open.\n * Hence the one-time dev warning naming the fix (mount `useLifecycleEvents` at the app root).\n */\nexport const ensureCurrentSessionId = (): string => {\n const existing = globalSlot[CURRENT_SESSION_ID_SLOT];\n if (typeof existing === \"string\" && existing.length > 0) return existing;\n const minted = makeSessionId();\n globalSlot[CURRENT_SESSION_ID_SLOT] = minted;\n if (!warnSlot[MINT_WARNED_SLOT] && warnInDev(MINT_WARNING)) {\n warnSlot[MINT_WARNED_SLOT] = true;\n }\n return minted;\n};\n","/**\n * transport.ts — kit → Wire server requests for the review module, all fire-and-forget\n * (analytics/reviews must never break the app). Mirrors analytics/reportClientEvent: a\n * thin fetch wrapper, Bearer tenant key, swallow every error.\n *\n * • submitReview → POST {serverUrl}/v1/reviews (the review row)\n * • fetchReviewDecision → GET {serverUrl}/v1/reviews/decision (best-effort, the AI seam)\n * • reportAppEvent → POST {serverUrl}/v1/events (generic app.* namespace)\n *\n * `reportAppEvent` is the strategic extension: it lets a host report arbitrary in-app\n * events through the SAME transport (stored server-side as event_type='app_event',\n * question_key=<name>), which is what the backend review-firing rules evaluate on — and\n * it seeds the broader app-analytics stream. Keep payloads minimal + non-PII.\n */\nimport { ensureCurrentSessionId } from \"../analytics/currentSession\";\nimport { buildEventsRequest, type ClientEvent } from \"../analytics/reportClientEvent\";\nimport type { ReviewDecisionResponse, ReviewSubmission, ReviewTarget } from \"./types\";\n\n/**\n * POST a review (the 1-4 feedback path). Fire-and-forget: a missing target, a build error,\n * a missing `fetch`, or a network failure is swallowed and the call returns immediately.\n */\nexport const submitReview = (\n target: ReviewTarget | undefined,\n review: ReviewSubmission,\n): void => {\n if (!target?.serverUrl) return;\n try {\n const url = `${target.serverUrl.replace(/\\/$/, \"\")}/v1/reviews`;\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;\n void fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(review),\n }).catch(() => {\n /* best-effort, swallow */\n });\n } catch {\n /* URL/JSON/missing-fetch — swallow */\n }\n};\n\n/**\n * Options for the best-effort review decision fetch.\n *\n * ── WHAT THE DEPLOYED SERVER ACTUALLY READS (verified 2026-07-17) ────────────────────────\n *\n * `GET /v1/reviews/decision` declares exactly two query params — `session_id` and\n * `device_key` — plus the `Authorization` header. That is the whole wire. Verified against\n * the deployed OpenAPI schema, not against intent.\n *\n * This type is CLOSED on purpose. Hosts that hand-rolled this fetch invented `user_id` and\n * `session_count` query params believing \"the server ignores what it doesn't read, so passing\n * it is always safe\". Both are no-ops: the route declares neither. `session_count` is real, but\n * only on the questionnaire POST body — which is precisely how it copy-pasted its way into a\n * reviews call site and sat there doing nothing. They cost a wire lie — a call site that reads as though\n * identity and a session counter reach the firing brain when neither does. So they are not\n * offered here. Pass identity as `deviceKey`; the session count the server reasons about is the\n * one IT derives from the event stream keyed by `deviceKey`, not one the client asserts.\n *\n * The client-side session floor is a LOCAL rule, not a wire param: use `ReviewConfig.minSessions`\n * (evaluated by `useReviewGate` against the kit's own `wire_review_<id>_sessions` counter).\n */\nexport interface FetchReviewDecisionOptions {\n /**\n * The onboarding session id, when there IS one. OPTIONAL on purpose: the review gate lives on\n * the home feed, where a user legitimately has no onboarding session. `deviceKey` is the real\n * identity for this call. (The server relaxed `session_id` to optional on 2026-07-16 and the\n * relaxation IS deployed — the route no longer 422s without it. Permissive wire: do NOT make\n * this required here.)\n */\n sessionId?: string;\n /**\n * A stable, non-PII device id — THE identity for this call. The decision endpoint reads it for\n * cooldown + min-sessions, and it is the key the server groups a device's events under. A host\n * whose own identity is a user id passes that id here rather than reaching for a `user_id`\n * param the route does not declare.\n */\n deviceKey?: string;\n}\n\n/**\n * Best-effort fetch of the SERVER's review firing decision. The mirror of\n * `fetchQuestionnaireDecision`, and the primitive whose absence caused a live incident.\n *\n * ── WHY THIS LIVES IN THE KIT (Malik, 2026-07-16) ────────────────────────────────────────\n *\n * The questionnaire module has always had its decision fetch; reviews never did. So a host\n * hand-rolled one, and got it subtly wrong: its helper collapsed an explicit `{fire:false}`\n * into `undefined`. `decideReview` is `(local, decision) => decision ?? local`, so `undefined`\n * means \"the server has no opinion, use the local rules\" — and the local timer fired. The\n * server could therefore only ever turn review prompts ON, never OFF. A real user was asked to\n * rate the app ~3 minutes into their FIRST session, having seen nothing yet, and left 1 star.\n *\n * The bug was not that the host was careless. It was that the kit made every host invent this.\n * So the primitive moves here and hosts keep a thin call site.\n *\n * ── THE CONTRACT THAT MATTERS ────────────────────────────────────────────────────────────\n *\n * • 2xx → the FULL `{fire, reason, arm}`, INCLUDING `fire:false`. Never collapse a false\n * into null. That collapse IS the bug: a false must reach `decideReview` intact so\n * it can override the local rules and keep the gate shut.\n * • else → null, and ONLY then. Null means \"the server genuinely has no opinion\", which is\n * the one case where falling back to local rules is correct.\n *\n * Never throws: unreachable, non-2xx, bad JSON, or a missing `fetch` all resolve to null. The\n * kit does not call this internally; a host awaits it and passes the result straight to\n * `useReviewGate({ decision })`.\n *\n * ── DON'T RACE THIS AGAINST A LOCAL TIMER ────────────────────────────────────────────────\n *\n * The return type is `ReviewDecisionResponse`, which carries `arm` alongside `{fire, reason}`.\n * Hand the WHOLE object to the gate and echo `arm` into the submission's `meta.firing_arm`;\n * narrowing it to `{fire, reason}` on the way through silently kills per-arm attribution\n * across a reweighting of the experiment.\n *\n * A host that starts its own dwell timer in parallel with this fetch has built a race a slow\n * server loses: the timer fires, the local rules show the prompt, and the `{fire:false}` still\n * in flight arrives too late to stop it. Do not hand-roll that. `useReviewGate` already owns\n * the wait — set `ReviewConfig.timeoutFallbackMs` and the local rules stay parked until either\n * the decision lands or the window expires, whichever comes first.\n *\n * const decision = await fetchReviewDecision(target, { deviceKey });\n * const gate = useReviewGate({\n * config: { id: \"home\", minSessions: 2, timeoutFallbackMs: 3000 },\n * decision: decision ?? undefined, // pass it whole — keep `arm`\n * storage,\n * });\n */\nexport const fetchReviewDecision = async (\n target: ReviewTarget | undefined,\n options: FetchReviewDecisionOptions = {},\n): Promise<ReviewDecisionResponse | null> => {\n if (!target?.serverUrl) return null;\n try {\n const base = target.serverUrl.replace(/\\/$/, \"\");\n const params = new URLSearchParams();\n if (options.sessionId) params.set(\"session_id\", options.sessionId);\n if (options.deviceKey) params.set(\"device_key\", options.deviceKey);\n const qs = params.toString();\n const url = `${base}/v1/reviews/decision${qs ? `?${qs}` : \"\"}`;\n const headers: Record<string, string> = {};\n if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;\n const res = await fetch(url, { headers });\n if (!res || !res.ok) return null;\n const json = (await res.json()) as ReviewDecisionResponse | null;\n // A body without a boolean `fire` is not a decision. Guard it explicitly rather than\n // letting `{}` through as a truthy object that `decideReview` would treat as a verdict\n // (`{}.fire === undefined` is falsy, so it would silently read as \"never fire\").\n if (!json || typeof json.fire !== \"boolean\") return null;\n return json;\n } catch {\n /* unreachable / non-2xx / bad JSON / missing-fetch → the server has no opinion */\n return null;\n }\n};\n\n/** Options for a reported app event. `deviceKey` groups a device's sessions server-side. */\nexport interface ReportAppEventOptions {\n /**\n * The onboarding/session id to correlate with, when known. Optional: when omitted the event\n * still carries the CURRENT per-open session id (`ensureCurrentSessionId()`), because an event\n * with no `session_id` is dropped server-side behind a 200. Pass one only to override.\n */\n sessionId?: string;\n /** A stable, non-PII device id — the review-decision endpoint reads it for min-sessions. */\n deviceKey?: string;\n /** Small non-PII extras. */\n meta?: Record<string, unknown>;\n}\n\n/**\n * Report a generic in-app event through the existing events transport. Stored server-side\n * as `event_type='app_event'`, `question_key=<name>`. Fire-and-forget. Keep `name` a short\n * stable identifier and `meta` small + non-PII.\n *\n * reportAppEvent(target, \"content_share\", { sessionId, deviceKey });\n *\n * ── `session_id` IS NON-NEGOTIABLE ON THE WIRE ───────────────────────────────────────────\n * The server's event model declares `session_id` required + non-empty, and `POST /v1/events`\n * validates per event inside a try/except that counts the failure as `skipped` and STILL returns\n * HTTP 200. An event sent without a `session_id` is therefore accepted and discarded, and a\n * fire-and-forget caller never finds out. This used to be reachable through the ordinary API:\n * `options.sessionId` was optional, so a host calling `reportAppEvent(target, \"screen\", { deviceKey })`\n * posted every screen view into that hole. So the id is no longer conditional — an explicit\n * `sessionId` wins, otherwise the CURRENT per-open id is used (minted + registered if no app-open\n * has been registered yet).\n */\nexport const reportAppEvent = (\n target: ReviewTarget | undefined,\n name: string,\n options: ReportAppEventOptions = {},\n): void => {\n if (!target?.serverUrl || !name) return;\n try {\n // An explicit id wins; a missing OR BLANK one falls back to the current per-open id. A bare\n // `??` would let `sessionId: \"\"` through, and the server rejects an empty string exactly like\n // a missing key (`min_length=1`), so the blank case has to fall back too.\n const supplied = options.sessionId ?? \"\";\n const event: Record<string, unknown> = {\n event_type: \"app_event\",\n question_key: name,\n session_id: supplied.trim().length > 0 ? supplied : ensureCurrentSessionId(),\n };\n // device_key rides in the non-PII user_context bucket the server sanitizes; the\n // review-decision endpoint reads it to group a device's sessions.\n if (options.deviceKey) event.user_context = { device_key: options.deviceKey };\n if (options.meta && Object.keys(options.meta).length > 0) {\n event.meta = JSON.stringify(options.meta);\n }\n // Route through the ONE canonical /v1/events builder (url + headers + body) instead of\n // re-describing the endpoint here; still fire-and-forget.\n const req = buildEventsRequest(target, [event as unknown as ClientEvent]);\n if (!req) return;\n void fetch(req.url, req.init).catch(() => {\n /* best-effort, swallow */\n });\n } catch {\n /* swallow */\n }\n};\n","/**\n * ReviewGate — the in-app review sentiment gate UI.\n *\n * A NEUTRAL prompt (\"Enjoying <app>?\") over five tappable stars. The honest split:\n * • 5 stars → the native store review (expo-store-review when available, else the\n * configured store URL). Fires `store_review_requested`, and POSTs a text-less\n * `stars=5` row carrying `meta.store_route` so promoters reach the server too\n * (before 2026-07-16 this route posted nothing — see `pickRating`).\n * • 1-4 stars → a feedback form: the \"why this rating\" opinion box (~4 lines, MANDATORY\n * as of Malik's 2026-07-16 ruling) and a smaller optional \"what can you suggest\" box,\n * POSTed to the Wire server (`feedback_text` + `suggestion`). Warm thank-you state,\n * never a dead end.\n *\n * EXACTLY ONE row is POSTed per gate, at the last responsible moment — see `postOnce`. A\n * detractor who abandons instead of typing still has their rating posted (`meta.abandoned`),\n * so forcing the text can never cost us the detractor data it exists to collect.\n *\n * The form NEVER asks for identity (no name / email / contact field): the host already\n * identifies the user via `sessionId` + `meta` (e.g. user_id, device_key), which is all\n * the POST body carries alongside the rating and the free text.\n *\n * Motion (2026-07-16): the three phases used to swap IN PLACE with no transition. They now\n * ride `ModalPaneHost`, the same pane-slide primitive the questionnaire steps use, and the\n * thank-you plays the shared `DoneCheck` payoff before the existing 1500ms auto-close.\n * Picking a star fires a haptic through the lazy, optional `expo-haptics` peer — silently\n * absent when the host has not installed it. The popup's own entrance is unchanged: it is\n * the CenteredModal shell's spring, which already animates.\n *\n * Every visual value comes from the OnboardingTheme (useOnboardingTheme). Analytics are\n * callback-based (mirroring the coachmark convention): the host wires `onEvent` to its own\n * analytics. No PII in events — feedback text goes ONLY in the POST body.\n *\n * Store-policy: `requestReview` is quota'd and never guaranteed to show; it is called ONLY\n * on the 5-star path, never from a \"rate us 5 stars\" button. See the README notes.\n */\nimport React, { useCallback, useMemo, useState } from \"react\";\nimport { Pressable, StyleSheet, Text, TextInput, View } from \"react-native\";\n\nimport { Button } from \"../components/Button\";\nimport { DoneCheck } from \"../components/DoneCheck\";\nimport { ModalPaneHost } from \"../components/ModalPaneHost\";\nimport { playHaptic } from \"../haptics/haptics\";\nimport { mergeThemeOver } from \"../theme/mergeThemeOver\";\nimport { useOnboardingTheme } from \"../theme/ThemeContext\";\nimport { bodyStyle, captionStyle, headingStyle } from \"../theme/typography\";\nimport { buildReviewSubmission, routeRating } from \"./decision\";\nimport { feedbackInputHeights } from \"./feedbackForm\";\nimport { resolvePresentation } from \"./presentation\";\nimport { ReviewModal, type ReviewModalHandle } from \"./ReviewModal\";\nimport { requestStoreReview } from \"./storeReview\";\nimport { submitReview } from \"./transport\";\nimport type { ReviewGateProps, ReviewSubmission } from \"./types\";\n\ntype Phase = \"rating\" | \"feedback\" | \"thanks\";\n\nconst _ReviewGate: React.FC<ReviewGateProps> = ({\n appName,\n store,\n target,\n sessionId,\n meta,\n onEvent,\n onResolved,\n onShown,\n theme: themeOverride,\n dismissible = true,\n presentation,\n id = \"default\",\n}) => {\n const active = useOnboardingTheme();\n const t = useMemo(() => mergeThemeOver(active, themeOverride), [active, themeOverride]);\n const mode = resolvePresentation(presentation);\n const modalRef = React.useRef<ReviewModalHandle>(null);\n\n const [phase, setPhase] = useState<Phase>(\"rating\");\n const [stars, setStars] = useState(0);\n const [hovered, setHovered] = useState(0);\n const [feedback, setFeedback] = useState(\"\");\n const [suggestion, setSuggestion] = useState(\"\");\n\n // Fire review_prompt_shown exactly ONCE per mount. The callbacks are read through\n // refs (not effect deps) so an inline `onShown`/`onEvent` — recreated every render,\n // as real hosts idiomatically pass — never re-runs the effect. Empty deps + the\n // shownRef guard make this a true fire-once, immune to prop-identity churn.\n const shownRef = React.useRef(false);\n const onShownRef = React.useRef(onShown);\n const onEventRef = React.useRef(onEvent);\n onShownRef.current = onShown;\n onEventRef.current = onEvent;\n React.useEffect(() => {\n if (shownRef.current) return;\n shownRef.current = true;\n onShownRef.current?.();\n onEventRef.current?.({ name: \"review_prompt_shown\", id });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const resolveTimer = React.useRef<ReturnType<typeof setTimeout> | null>(null);\n\n React.useEffect(() => {\n return () => {\n if (resolveTimer.current) clearTimeout(resolveTimer.current);\n };\n }, []);\n\n const resolve = useCallback(() => {\n onResolved?.();\n }, [onResolved]);\n\n /**\n * EXACTLY ONE review row per gate, ever — the latch that makes \"force feedback\" safe.\n *\n * WHY ONE POST AND NOT TWO: the tempting design is to post the rating at star-tap and then\n * post again with the text, enriching the first row. That needs the server to accept a\n * client-supplied id so the second write UPSERTS. It does not, as deployed today:\n * `create_review` mints its own id unconditionally (`app/reviews.py` `_new_id()`) and\n * `CreateReviewRequest` (`app/schemas.py`) has no id field — so two posts = two rows, which\n * double-counts `count` and corrupts `avg`. That is the SAME class of data corruption this\n * whole change exists to fix. And `submitReview` is fire-and-forget (transport.ts: the\n * response is never read), so the kit CANNOT detect a rejection and repair it: a wrong guess\n * fails silently and permanently. So we post once, at the last responsible moment, and never\n * depend on an unmerged/undeployed server capability.\n */\n const postedRef = React.useRef(false);\n const postOnce = useCallback(\n (body: ReviewSubmission) => {\n if (postedRef.current) return;\n postedRef.current = true;\n submitReview(target, body);\n },\n [target],\n );\n\n /**\n * The abandonment safety net, and the reason forcing feedback does not COST us detractor data.\n *\n * Malik's ruling: a 1-4 rater must leave text. Implemented naively (block Send, remove Skip),\n * that would LOSE the ratings of everyone who bails rather than types — the detractors we most\n * need — because today the 1-4 branch posts only from `sendFeedback`. So: if the gate goes away\n * while a rating is pending and unposted, we post the rating anyway, with whatever text was\n * typed, flagged `meta.abandoned`. The user is pushed to explain; the rating is never the price.\n *\n * Read through a ref and fired from an unmount-only effect, so it always sees the latest\n * state without re-subscribing (the same ref convention `review_prompt_shown` above uses).\n */\n const abandonRef = React.useRef<() => void>(() => {});\n abandonRef.current = () => {\n if (postedRef.current || stars < 1) return;\n postOnce(\n buildReviewSubmission({\n stars,\n feedbackText: feedback,\n suggestion,\n sessionId,\n meta: { ...meta, abandoned: true },\n }),\n );\n };\n React.useEffect(() => () => abandonRef.current(), []);\n\n const resolveWithDelay = useCallback(() => {\n if (resolveTimer.current) clearTimeout(resolveTimer.current);\n resolveTimer.current = setTimeout(() => {\n if (mode === \"modal\") {\n modalRef.current?.close();\n } else {\n onResolved?.();\n }\n }, 1500);\n }, [mode, onResolved]);\n\n const pickRating = useCallback(\n async (value: number) => {\n setStars(value);\n // The tap you feel. Fire-and-forget: an absent peer must never delay the route below.\n playHaptic(\"selection\");\n onEvent?.({ name: \"review_rating_selected\", id, stars: value });\n if (routeRating(value) === \"store\") {\n onEvent?.({ name: \"store_review_requested\", id, stars: value });\n const route = await requestStoreReview(store);\n // The 5-star row. Until 2026-07-16 this branch POSTed NOTHING: submitReview was\n // reachable only from the feedback phase, which only the 1-4 route enters. Every\n // promoter was silently dropped, so server-side `by_stars[\"5\"]` and `store_routed`\n // were permanently 0 and `avg` averaged detractors only. The four onEvent moments\n // above go to the HOST's analytics, never to Wire, so nothing else covered this.\n //\n // The body is `stars` + `meta` with NO feedback text — exactly the shape the server\n // already documents as the store route (\"A 5-star tap posts stars=5 with no text\")\n // and the shape its `store_routed` aggregate counts (stars == 5 and not\n // feedback_text). So this is valid against prod as deployed, with no server change.\n //\n // `store_route` records WHICH route we took (native dialog / store URL / neither).\n // Honest ceiling: requestStoreReview is OS-quota'd and reports nothing about whether\n // the user actually left a review — this only ever means we ASKED. Never read it as\n // a review count. We await first so the row carries the route it actually took.\n postOnce(\n buildReviewSubmission({\n stars: value,\n sessionId,\n meta: { ...meta, store_route: route },\n }),\n );\n setPhase(\"thanks\");\n resolveWithDelay();\n } else {\n // 1-4: no post yet. The row lands either from `sendFeedback` (with the forced text) or\n // from the abandonment net above — one row, whichever happens first.\n setPhase(\"feedback\");\n }\n },\n [onEvent, id, store, postOnce, sessionId, meta, resolveWithDelay],\n );\n\n /**\n * Malik's ruling (2026-07-16): a 1-4 rater is FORCED to leave feedback. Enforced HERE, on the\n * client, and deliberately NOT on the wire: `feedback_text` stays optional in\n * `CreateReviewRequest`, because the transport is fire-and-forget and every already-shipped\n * client still posts text-less rows — tightening the server would silently 422 them into\n * oblivion. Permissive wire, strict client.\n *\n * Mirrors the questionnaire contract exactly: the opinion box is the mandatory one, the\n * suggestion box stays optional. Trimmed — whitespace is not feedback.\n */\n const feedbackReady = feedback.trim().length > 0;\n\n const sendFeedback = useCallback(() => {\n // The forced-text gate. Also guarded here, not just on the button's `disabled`, so the\n // rule holds no matter who calls this.\n if (!feedbackReady) return;\n // Identity is NEVER collected here — the host already sends user_id / device /\n // session via `sessionId` + `meta`. We POST only the rating + the free text.\n const body = buildReviewSubmission({\n stars,\n feedbackText: feedback,\n suggestion,\n sessionId,\n meta,\n });\n postOnce(body);\n onEvent?.({ name: \"review_feedback_submitted\", id, stars });\n playHaptic(\"success\");\n setPhase(\"thanks\");\n resolveWithDelay();\n }, [feedbackReady, stars, feedback, suggestion, sessionId, meta, postOnce, onEvent, id, resolveWithDelay]);\n\n const dismiss = useCallback(() => {\n setPhase(\"thanks\");\n resolveWithDelay();\n }, [resolveWithDelay]);\n\n // The in-card \"Not now\"/\"Skip\" link. In modal mode it plays the popup's exit animation\n // (which resolves on completion, the same path a backdrop tap takes); inline keeps the\n // legacy behavior of swapping to the thank-you in place.\n const handleDismissTap = useCallback(() => {\n if (mode === \"modal\") modalRef.current?.close();\n else dismiss();\n }, [mode, dismiss]);\n\n const surface = {\n backgroundColor: t.colors.surface,\n borderRadius: t.radius.lg,\n padding: t.spacing.lg,\n gap: t.spacing.md,\n } as const;\n\n // A multiline box ~4 lines tall (capped at ~5), derived from the theme's body\n // line-height so it scales with the app's font config. The field's own vertical\n // padding is folded in so the requested lines are visible inside the padding.\n const feedbackBox = feedbackInputHeights(t.fonts, t.spacing.md);\n\n const linkStyle = [captionStyle(t.fonts), styles.link, { color: t.colors.textMuted }];\n\n // Each phase is a self-contained PANE, title included, so the heading travels with its\n // content instead of swapping under a static header — one moving surface, not two.\n const ratingPane = (\n <View style={{ gap: t.spacing.md }}>\n <Text style={[headingStyle(t.fonts), { color: t.colors.text }]}>\n {appName ? `Enjoying ${appName}?` : \"How's your experience?\"}\n </Text>\n <Text style={[captionStyle(t.fonts), { color: t.colors.textMuted }]}>\n Tap a star to let us know.\n </Text>\n <View style={styles.stars}>\n {[1, 2, 3, 4, 5].map((n) => {\n const filled = n <= (hovered || stars);\n return (\n <Pressable\n key={n}\n onPressIn={() => setHovered(n)}\n onPressOut={() => setHovered(0)}\n onPress={() => void pickRating(n)}\n accessibilityRole=\"button\"\n accessibilityLabel={`${n} star${n > 1 ? \"s\" : \"\"}`}\n hitSlop={8}\n >\n <Text style={[styles.star, { color: filled ? t.colors.primary : t.colors.border }]}>\n {filled ? \"★\" : \"☆\"}\n </Text>\n </Pressable>\n );\n })}\n </View>\n {dismissible && (\n <Pressable\n onPress={handleDismissTap}\n hitSlop={8}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Dismiss the review prompt\"\n >\n <Text style={linkStyle}>Not now</Text>\n </Pressable>\n )}\n </View>\n );\n\n const feedbackPane = (\n <View style={{ gap: t.spacing.md }}>\n <Text style={[headingStyle(t.fonts), { color: t.colors.text }]}>What should we improve?</Text>\n <Text style={[captionStyle(t.fonts), { color: t.colors.textMuted }]}>\n A few words is plenty — tell us what went wrong and we'll fix it.\n </Text>\n {/* No name / email / contact field: the host already identifies the user via\n sessionId + meta, so we never ask for identity here. As of Malik's 2026-07-16\n ruling the FIRST box is mandatory (mirroring the questionnaire's `opinion`);\n the suggestion box stays optional. */}\n <Text style={[captionStyle(t.fonts), { color: t.colors.textMuted }]}>\n Why this rating?\n </Text>\n <TextInput\n value={feedback}\n onChangeText={setFeedback}\n placeholder=\"e.g. too many steps\"\n placeholderTextColor={t.colors.textMuted}\n accessibilityLabel=\"Why this rating?\"\n multiline\n textAlignVertical=\"top\"\n style={[\n bodyStyle(t.fonts),\n styles.input,\n {\n color: t.colors.text,\n backgroundColor: t.colors.background,\n borderRadius: t.radius.md,\n borderColor: t.colors.border,\n padding: t.spacing.md,\n minHeight: feedbackBox.minHeight,\n maxHeight: feedbackBox.maxHeight,\n },\n ]}\n />\n <Text style={[captionStyle(t.fonts), { color: t.colors.textMuted }]}>\n What can we do better? (optional)\n </Text>\n <TextInput\n value={suggestion}\n onChangeText={setSuggestion}\n placeholder=\"e.g. save my place between steps\"\n placeholderTextColor={t.colors.textMuted}\n accessibilityLabel=\"What can we do better? (optional)\"\n style={[\n bodyStyle(t.fonts),\n styles.input,\n {\n color: t.colors.text,\n backgroundColor: t.colors.background,\n borderRadius: t.radius.md,\n borderColor: t.colors.border,\n paddingHorizontal: t.spacing.md,\n paddingVertical: t.spacing.sm,\n minHeight: t.button.height,\n },\n ]}\n />\n <Button\n title=\"Send feedback\"\n onPress={sendFeedback}\n variant=\"primary\"\n fullWidth\n disabled={!feedbackReady}\n />\n {/* No Skip here — this step is mandatory (Malik, 2026-07-16). The rating itself is\n still never held hostage: bailing out of the gate entirely (backdrop / Android\n back / navigation) posts it through the abandonment net above. We do NOT trap the\n user in the modal; forcing the TEXT is the rule, imprisoning them is not. */}\n </View>\n );\n\n const thanksPane = (\n <View style={{ gap: t.spacing.md }}>\n <DoneCheck theme={t} />\n <Text style={[headingStyle(t.fonts), styles.centerText, { color: t.colors.text }]}>\n Thank you\n </Text>\n <Text style={[bodyStyle(t.fonts), styles.centerText, { color: t.colors.textMuted }]}>\n We appreciate you. This helps us make it better.\n </Text>\n </View>\n );\n\n // The phase content, shared by both presentations. In \"modal\" mode ReviewModal supplies\n // the centered, dimmed, animated card surface; in \"inline\" mode the bare card below does.\n const content = (\n <ModalPaneHost paneKey={phase}>\n {phase === \"rating\" ? ratingPane : phase === \"feedback\" ? feedbackPane : thanksPane}\n </ModalPaneHost>\n );\n\n // Default: a centered popup over a dimmed backdrop with a spring/fade entrance. A backdrop\n // tap (or Android back) resolves via the same path onResolved already tracks.\n if (mode === \"modal\") {\n return (\n <ReviewModal ref={modalRef} theme={t} dismissible={dismissible} onDismiss={resolve}>\n {content}\n </ReviewModal>\n );\n }\n\n // Legacy: the bare card, positioned by wherever the host mounts it.\n return <View style={[styles.card, surface]}>{content}</View>;\n};\n\nexport const ReviewGate = React.memo(_ReviewGate);\nReviewGate.displayName = \"ReviewGate\";\n\nconst styles = StyleSheet.create({\n card: { width: \"100%\" },\n stars: { flexDirection: \"row\", justifyContent: \"center\", gap: 8 },\n star: { fontSize: 40, lineHeight: 46 },\n input: { borderWidth: 1 },\n link: { textAlign: \"center\", textDecorationLine: \"underline\" },\n centerText: { textAlign: \"center\" },\n});\n\nexport default ReviewGate;\n","/**\n * Module-level runtime for the coachmark engine: the injected sync storage, the\n * global `isTestingCoachmark` flag, and the seen-gate helpers built on top.\n *\n * WHY module singletons (not just context): the tour hooks and the anchor\n * registry are driven imperatively from anywhere in the tree (a screen deep\n * under the navigator), while the storage + testing flag are configured once at\n * the root by `CoachmarkProvider`. Singletons let a hook read the gate without\n * every call site threading the storage through props. The provider is still the\n * single writer — it calls `setCoachmarkStorage` / `setCoachmarkTesting` on mount.\n *\n * ── WHY A globalThis SLOT, NOT PLAIN MODULE VARIABLES ─────────────────────────────────────────\n * These three values used to be plain module `let`s, and this module is inlined by tsup into FOUR\n * dist bundles (`./coachmarks`, `./reviews`, `./questionnaire`, `./showcase`). Under `dist`\n * resolution — node `import`/`require`, SSR, jest, RN-web, anything that is not Metro — a host that\n * mounts `CoachmarkProvider` from `@wireai/activation/coachmarks` wrote the COACHMARKS copy, while\n * `useReviewGate` from `@wireai/activation/reviews` read the REVIEWS copy, which was still `null`.\n * Everything downstream then failed silently: `readInt(null, …)` returns 0, so `bumpSessionCount`\n * returns 1 forever and the fail-closed `minSessions: 2` default is unsatisfiable (the gate NEVER\n * fires); the `seen` once-gate never persists; and a tenant's coachmarks kill switch never reaches\n * the reviews bundle. Metro masked it by collapsing every subpath back to one `src/` file through\n * the `react-native` export condition, which is a bundler accident, not a guarantee.\n *\n * Same remedy this repo already applies to `currentSession`, the auto device-key registry and the\n * activation revalidation counter: ONE record in a `globalThis` slot keyed by `Symbol.for(...)`, so\n * every inlined copy of this module addresses the SAME state.\n */\nimport type { CoachmarkStorage } from \"./types\";\n\n/** Well-known key into the runtime-global symbol registry — one coachmark runtime across bundles. */\nconst COACHMARK_RUNTIME_SLOT: unique symbol = Symbol.for(\"@wireai/activation:coachmarkRuntime\");\n\n/** The shared runtime: the provider-injected storage, the QA replay flag, and the kill switch. */\ntype CoachmarkRuntime = {\n storage: CoachmarkStorage | null;\n testing: boolean;\n // The coachmarks feature kill switch (from GET /v1/features). Default true = fail-open: with no\n // flags fetched, coachmarks behave exactly as before. CoachmarkProvider writes it from the\n // resolved flags. Disabled → the tour never ARMS and the overlay `show()` is a no-op, so nothing\n // paints and — critically — no once-gate is written, so re-enabling replays the tour correctly.\n coachmarksEnabled: boolean;\n};\n\ntype GlobalWithCoachmarkRuntime = typeof globalThis & {\n [COACHMARK_RUNTIME_SLOT]?: CoachmarkRuntime;\n};\n\nconst runtimeGlobal = globalThis as GlobalWithCoachmarkRuntime;\n\nconst coachmarkRuntime = (): CoachmarkRuntime => {\n const existing = runtimeGlobal[COACHMARK_RUNTIME_SLOT];\n if (existing) return existing;\n const created: CoachmarkRuntime = { storage: null, testing: false, coachmarksEnabled: true };\n runtimeGlobal[COACHMARK_RUNTIME_SLOT] = created;\n return created;\n};\n\n/**\n * Set the coachmarks master switch (from the resolved feature flags). Default true (fail-open).\n * Written by CoachmarkProvider; read imperatively by the tour arm + the overlay store.\n */\nexport const setCoachmarksEnabled = (value: boolean): void => {\n coachmarkRuntime().coachmarksEnabled = value;\n};\n\n/** Whether the coachmarks module is enabled. False → tours/overlays are silently skipped. */\nexport const areCoachmarksEnabled = (): boolean => coachmarkRuntime().coachmarksEnabled;\n\n/** Set (or clear) the injected sync gate storage. Called by CoachmarkProvider. */\nexport const setCoachmarkStorage = (storage: CoachmarkStorage | null): void => {\n coachmarkRuntime().storage = storage;\n};\n\n/** The currently-injected gate storage, or null if no provider is mounted. */\nexport const getCoachmarkStorage = (): CoachmarkStorage | null => coachmarkRuntime().storage;\n\n/**\n * Toggle the global QA replay flag. When true, EVERY seen-gate reads as unseen\n * and every seen-write is suppressed, so every tour + showcase replays on each\n * mount. The app flips ONE boolean to QA the whole coachmark surface.\n */\nexport const setCoachmarkTesting = (value: boolean): void => {\n coachmarkRuntime().testing = value;\n};\n\n/** Whether replay-everything QA mode is on. */\nexport const isCoachmarkTesting = (): boolean => coachmarkRuntime().testing;\n\n/** Gate key for a tour: `wire_coachmark_<tourId>_seen`. */\nexport const coachmarkGateKey = (tourId: string): string =>\n `wire_coachmark_${tourId}_seen`;\n\n/** Gate key for a feature showcase: `wire_showcase_<id>_seen`. */\nexport const showcaseGateKey = (showcaseId: string): string =>\n `wire_showcase_${showcaseId}_seen`;\n\n/** Marker value written for a seen gate. */\nconst SEEN_VALUE = \"1\";\n\n/**\n * Read a seen gate. Testing mode ALWAYS reports unseen (so everything replays).\n * A missing storage (no provider) also reports unseen — the safe default is to\n * show the hint rather than silently swallow it. `storageOverride` /\n * `testingOverride` let a self-contained component (e.g. FeatureShowcase) pass\n * its own storage without relying on the provider singleton.\n */\nexport const hasSeenGate = (\n key: string,\n storageOverride?: CoachmarkStorage | null,\n testingOverride?: boolean,\n): boolean => {\n if (testingOverride ?? isCoachmarkTesting()) return false;\n const storage = storageOverride ?? getCoachmarkStorage();\n if (!storage) return false;\n try {\n return storage.getItem(key) === SEEN_VALUE;\n } catch {\n return false;\n }\n};\n\n/**\n * Mark a gate seen. Suppressed in testing mode (so replay never persists) and a\n * no-op without storage. Best-effort — a throwing adapter never breaks the tour.\n */\nexport const markSeenGate = (\n key: string,\n storageOverride?: CoachmarkStorage | null,\n testingOverride?: boolean,\n): void => {\n if (testingOverride ?? isCoachmarkTesting()) return;\n const storage = storageOverride ?? getCoachmarkStorage();\n if (!storage) return;\n try {\n storage.setItem(key, SEEN_VALUE);\n } catch {\n // Best-effort: a failed write just means the hint may show once more.\n }\n};\n","/**\n * The fail-open defaults + a tolerant parser for the server's flags payload.\n *\n * THE CONTRACT: fail-open, never fail-dark. An absent server object, a missing module key, a\n * malformed value, or an unreachable endpoint must resolve to the MOST PERMISSIVE state — every\n * module ON, onboarding `ai` — so a control-plane outage can only ever be more permissive than\n * the dashboard intends. It can never dark a tenant's activation. Matches the server's own\n * documented fail-open default (FEATURES.md: \"local defaults — every module on, onboarding ai\").\n */\nimport type { OnboardingMode, WireFeatures } from \"./types\";\n\n/**\n * ALL ON, onboarding `ai`. Frozen + module-level so its reference is stable across every call\n * (consumers that list resolved flags in an effect dep array never loop on a fresh identity).\n */\nexport const defaultWireFeatures: WireFeatures = Object.freeze({\n coachmarks: Object.freeze({ enabled: true }),\n showcase: Object.freeze({ enabled: true }),\n onboarding: Object.freeze({ mode: \"ai\" as OnboardingMode }),\n review: Object.freeze({ enabled: true }),\n questionnaire: Object.freeze({ enabled: true }),\n announcements: Object.freeze({ enabled: true }),\n}) as WireFeatures;\n\nconst VALID_MODES: readonly OnboardingMode[] = [\"ai\", \"static\", \"off\"];\n\n/** Read a `{ enabled: boolean }` module, defaulting to ON when absent/malformed (fail-open). */\nconst readEnabled = (raw: unknown): { enabled: boolean } => {\n if (raw && typeof raw === \"object\" && typeof (raw as { enabled?: unknown }).enabled === \"boolean\") {\n return { enabled: (raw as { enabled: boolean }).enabled };\n }\n return { enabled: true };\n};\n\n/**\n * Narrow the server's (untrusted) JSON to a complete `WireFeatures`. Every field is filled with\n * its fail-open default when the server omits it or sends the wrong type, so a partial or\n * malformed payload degrades PERMISSIVELY rather than throwing. A non-object input (null, array,\n * string) → the full defaults. Never throws.\n */\nexport const parseWireFeatures = (raw: unknown): WireFeatures => {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return defaultWireFeatures;\n const src = raw as Record<string, unknown>;\n const modeRaw = (src.onboarding as { mode?: unknown } | undefined)?.mode;\n const mode: OnboardingMode =\n typeof modeRaw === \"string\" && VALID_MODES.includes(modeRaw as OnboardingMode)\n ? (modeRaw as OnboardingMode)\n : \"ai\";\n return {\n coachmarks: readEnabled(src.coachmarks),\n showcase: readEnabled(src.showcase),\n onboarding: { mode },\n review: readEnabled(src.review),\n questionnaire: readEnabled(src.questionnaire),\n announcements: readEnabled(src.announcements),\n };\n};\n\n/** Value-equality of two resolved flags — lets the hook hold a stable reference across churn. */\nexport const featuresEqual = (a: WireFeatures, b: WireFeatures): boolean =>\n a === b ||\n (a.coachmarks.enabled === b.coachmarks.enabled &&\n a.showcase.enabled === b.showcase.enabled &&\n a.onboarding.mode === b.onboarding.mode &&\n a.review.enabled === b.review.enabled &&\n a.questionnaire.enabled === b.questionnaire.enabled &&\n a.announcements.enabled === b.announcements.enabled);\n","/**\n * The last-known-flags cache — persisted through the host's optional `storage`.\n *\n * WHY: the fetch is best-effort and fail-open. When the control plane is unreachable we serve the\n * LAST value we saw rather than snapping to defaults, so a tenant that intentionally disabled a\n * module during an incident keeps it disabled across an outage instead of flapping back on. That\n * is \"stale-while-error\": a fresh cache (< TTL) is trusted, and on a fetch error even a STALE\n * cache is served (any age beats a guess). Every operation is best-effort — a broken/hung storage\n * degrades to defaults, never a throw and never a stuck gate (mirrors persistedSession).\n */\nimport { parseWireFeatures } from \"./defaults\";\nimport type { WireFeatures } from \"./types\";\nimport type { WireOnboardingStorage } from \"../session/persistedSession\";\n\n/**\n * TTL for a cached flags entry, aligned to the server's `Cache-Control: private, max-age=300`.\n * A cache younger than this is \"fresh\"; older is \"stale\" (still served on a fetch error).\n */\nexport const DEFAULT_FEATURES_TTL_MS = 300_000;\n\n/** Ceiling on the storage read so a hung adapter degrades to defaults, never a stuck resolve. */\nconst READ_TIMEOUT_MS = 1_500;\n\n/** Storage key for a tenant's cached flags, e.g. `wireai:features:acme`. */\nexport const featuresCacheKey = (appId?: string): string =>\n `wireai:features:${appId && appId.length > 0 ? appId : \"default\"}`;\n\n/** A cached flags entry: the resolved flags + the epoch-ms they were fetched. */\nexport interface CachedFeatures {\n features: WireFeatures;\n /** Age of the entry in ms at read time. */\n ageMs: number;\n}\n\nconst withTimeout = <T>(p: Promise<T>, ms: number): Promise<T | undefined> => {\n let timer: ReturnType<typeof setTimeout>;\n const timeout = new Promise<undefined>((resolve) => {\n timer = setTimeout(() => resolve(undefined), ms);\n });\n return Promise.race([p, timeout]).finally(() => clearTimeout(timer));\n};\n\n/** Whether a cache entry is still within the TTL (server max-age aligned). */\nexport const isFeaturesFresh = (ageMs: number, ttlMs: number = DEFAULT_FEATURES_TTL_MS): boolean =>\n ageMs >= 0 && ageMs < ttlMs;\n\n/**\n * Read the cached flags for `key`, or `undefined` when absent/corrupt/timed-out. Returns the\n * value AND its age so a caller can apply a TTL (fresh) or ignore it (stale-while-error). Never\n * throws; never hangs past the read timeout. The stored value is re-parsed through\n * `parseWireFeatures`, so a partially-corrupt entry still degrades fail-open.\n */\nexport const readCachedFeatures = async (\n storage: WireOnboardingStorage,\n key: string,\n): Promise<CachedFeatures | undefined> => {\n let raw: string | null | undefined;\n try {\n raw = await withTimeout(storage.getItem(key), READ_TIMEOUT_MS);\n } catch {\n return undefined;\n }\n if (!raw) return undefined;\n try {\n const parsed: unknown = JSON.parse(raw);\n if (parsed && typeof parsed === \"object\" && typeof (parsed as { ts?: unknown }).ts === \"number\") {\n const ts = (parsed as { ts: number }).ts;\n return {\n features: parseWireFeatures((parsed as { features?: unknown }).features),\n ageMs: Date.now() - ts,\n };\n }\n } catch {\n // Corrupt entry → treat as absent; the caller falls back to defaults / a fresh fetch.\n }\n return undefined;\n};\n\n/** Persist `{ features, ts }` under `key` — fire-and-forget, all errors swallowed. */\nexport const writeCachedFeatures = (\n storage: WireOnboardingStorage,\n key: string,\n features: WireFeatures,\n): void => {\n try {\n void storage\n .setItem(key, JSON.stringify({ features, ts: Date.now() }))\n .catch(() => {});\n } catch {\n // Best-effort: a failed write just means the next launch has no cache to fall back to.\n }\n};\n","/**\n * fetchWireFeatures — read the per-module kill switches from `GET {serverUrl}/v1/features`.\n *\n * Contract (server: chohra-med/wireai-onboarding-server, routers/onboarding.py):\n * GET {serverUrl}/v1/features\n * Authorization: Bearer {apiKey}\n * 200 → { coachmarks:{enabled}, showcase:{enabled}, onboarding:{mode}, review:{enabled},\n * announcements:{enabled} } (Cache-Control: private, max-age=300)\n *\n * THE CONTRACT — fail-open, never fail-dark. Unreachable, timed-out, unauthorized (401),\n * server-error (5xx), or malformed JSON all resolve to the LAST CACHED value, else the\n * all-on defaults. No throw ever reaches the host: this is timeout-capped and swallows every\n * error, exactly like the session's best-effort reads. A control-plane outage can only ever be\n * MORE permissive than the dashboard intends — it can never dark a tenant's activation.\n */\nimport { readCachedFeatures, featuresCacheKey, writeCachedFeatures } from \"./cache\";\nimport { defaultWireFeatures, parseWireFeatures } from \"./defaults\";\nimport type { WireFeatures, WireFeaturesConfig } from \"./types\";\nimport type { WireOnboardingStorage } from \"../session/persistedSession\";\n\nconst DEFAULT_TIMEOUT_MS = 4_000;\n\n/** The fail-open fallback: last cached value (any age — stale-while-error), else defaults. */\nconst failOpen = async (\n storage: WireOnboardingStorage | undefined,\n key: string,\n): Promise<WireFeatures> => {\n if (!storage) return defaultWireFeatures;\n const cached = await readCachedFeatures(storage, key);\n return cached?.features ?? defaultWireFeatures;\n};\n\n/** GET with a hard timeout via AbortController (falls back to no-abort where unsupported). */\nconst fetchWithTimeout = async (\n url: string,\n apiKey: string,\n timeoutMs: number,\n): Promise<Response> => {\n const controller =\n typeof AbortController !== \"undefined\" ? new AbortController() : undefined;\n const timer = setTimeout(() => controller?.abort(), timeoutMs);\n try {\n return await fetch(url, {\n method: \"GET\",\n headers: { Accept: \"application/json\", Authorization: `Bearer ${apiKey}` },\n signal: controller?.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n};\n\n/**\n * Resolve the tenant's feature flags. Attempts the network once; on ANY failure returns the\n * cached value (any age), else the all-on defaults. On success, persists the fresh value to the\n * cache (fire-and-forget) and returns it. Never throws.\n *\n * const flags = await fetchWireFeatures({ serverUrl, apiKey, appId, storage });\n * if (!flags.coachmarks.enabled) { /* skip the tour * / }\n */\nexport const fetchWireFeatures = async (\n config?: WireFeaturesConfig,\n): Promise<WireFeatures> => {\n const key = featuresCacheKey(config?.appId);\n const storage = config?.storage;\n\n // Nothing to fetch (no creds) → cache, else defaults. Lets a host mount the provider before\n // its config is ready without ever darking.\n if (!config?.serverUrl || !config?.apiKey) return failOpen(storage, key);\n\n const url = `${config.serverUrl.replace(/\\/$/, \"\")}/v1/features`;\n const timeoutMs = config.timeoutMs && config.timeoutMs > 0 ? config.timeoutMs : DEFAULT_TIMEOUT_MS;\n\n try {\n const res = await fetchWithTimeout(url, config.apiKey, timeoutMs);\n // 401 (bad/rotated key), 5xx, any non-2xx → last-known, never dark.\n if (!res || !res.ok) return failOpen(storage, key);\n const json: unknown = await res.json(); // throws on malformed body → caught below\n const features = parseWireFeatures(json);\n if (storage) writeCachedFeatures(storage, key, features);\n return features;\n } catch {\n // Timeout/abort, network error, missing fetch, or malformed JSON — all fail open.\n return failOpen(storage, key);\n }\n};\n","/**\n * useWireFeatures — resolve the tenant's feature flags for a component tree, once per mount.\n *\n * Returns the all-on defaults immediately (fail-open), then swaps to the fetched value when the\n * network resolves. When no `serverUrl`+`apiKey` is supplied it never fetches and just returns\n * defaults, so a host that never adopts flags sees ZERO behavior change and ZERO network calls.\n *\n * frequent_rules #11 (unstable inline props): the config is idiomatically passed inline\n * (`config={{ serverUrl, apiKey }}`), a fresh identity every render. We stabilize the fetch key\n * by VALUE so the effect fires once per real credential change, and we only swap the returned\n * flags object when its VALUES change — so a host that lists the returned flags in an effect dep\n * array settles in a bounded number of commits instead of looping (enforced by the canary suite).\n */\nimport { useEffect, useRef, useState } from \"react\";\n\nimport { defaultWireFeatures, featuresEqual } from \"./defaults\";\nimport { fetchWireFeatures } from \"./fetchWireFeatures\";\nimport type { WireFeatures, WireFeaturesConfig } from \"./types\";\n\n/** Hold a stable reference for a value while its CONTENT is unchanged (see reviews/equality.ts). */\nconst useStableValue = <T,>(value: T, isEqual: (a: T, b: T) => boolean): T => {\n const ref = useRef(value);\n if (!isEqual(ref.current, value)) ref.current = value;\n return ref.current;\n};\n\n/** The credential slice that actually drives a refetch — stabilized by value. */\nconst sameFetchKey = (a: WireFeaturesConfig | undefined, b: WireFeaturesConfig | undefined): boolean =>\n a?.serverUrl === b?.serverUrl && a?.apiKey === b?.apiKey && a?.appId === b?.appId;\n\nexport const useWireFeatures = (config?: WireFeaturesConfig): WireFeatures => {\n const stableConfig = useStableValue(config, sameFetchKey);\n const [flags, setFlags] = useState<WireFeatures>(defaultWireFeatures);\n\n const canFetch = !!stableConfig?.serverUrl && !!stableConfig?.apiKey;\n\n useEffect(() => {\n if (!canFetch) {\n // No creds → stay on the all-on defaults (and reset if creds were removed).\n setFlags((prev) => (featuresEqual(prev, defaultWireFeatures) ? prev : defaultWireFeatures));\n return;\n }\n let alive = true;\n // fetchWireFeatures never throws — the .then always runs with a fail-open value.\n void fetchWireFeatures(stableConfig).then((next) => {\n if (!alive) return;\n // Only adopt a NEW reference on a real value change, so a host effect keyed on the\n // returned flags doesn't loop (frequent_rules #11).\n setFlags((prev) => (featuresEqual(prev, next) ? prev : next));\n });\n return () => {\n alive = false;\n };\n }, [canFetch, stableConfig]);\n\n return flags;\n};\n","/**\n * WireFeaturesProvider + useResolvedFeatures — the OPTIONAL one-fetch-serves-all-modules layer.\n *\n * Mount `WireFeaturesProvider` once near your app root with the tenant `config` and it fetches the\n * flags ONCE; every gated surface below it (coachmarks / showcase / reviews) reads the same\n * resolved value from context instead of each fetching its own. It is entirely optional: with no\n * provider, every surface falls back to its own lazy fetch (when given a config) or to the all-on\n * defaults, so modules work with zero setup and a host that never adopts flags is unchanged.\n *\n * NOTE on the built npm subpaths: React context is shared across the kit's subpath entries in the\n * SOURCE-consumption path (the monorepo apps) and in this test suite — one module instance, one\n * context. If you consume the built `@wireai/activation/*` subpaths as separate bundles, pass the\n * resolved flags (or a `featuresConfig`) to each surface explicitly instead of relying on context.\n */\nimport React, { createContext, useContext, useMemo } from \"react\";\n\nimport { defaultWireFeatures } from \"./defaults\";\nimport { useWireFeatures } from \"./useWireFeatures\";\nimport type { WireFeatures, WireFeaturesConfig } from \"./types\";\n\nconst CONTEXT_SYMBOL = Symbol.for(\"wireai.features.context\");\nconst globalObj = global as any;\n\nif (!globalObj[CONTEXT_SYMBOL]) {\n globalObj[CONTEXT_SYMBOL] = createContext<WireFeatures | null>(null);\n}\nconst WireFeaturesContext = globalObj[CONTEXT_SYMBOL];\n\nexport interface WireFeaturesProviderProps {\n /** Tenant creds (+ optional storage) to fetch the flags once for the whole tree. */\n config?: WireFeaturesConfig;\n /**\n * Pre-resolved flags to provide directly instead of fetching (e.g. you already have them, or\n * you drive them from your own remote-config). Takes precedence over `config`.\n */\n flags?: WireFeatures;\n children: React.ReactNode;\n}\n\n/**\n * Fetch (or accept) the flags once and broadcast them to descendants. The provider VALUE is\n * memoized (performance rule #6) so a fresh object each render doesn't re-render every consumer.\n */\nexport const WireFeaturesProvider: React.FC<WireFeaturesProviderProps> = ({\n config,\n flags,\n children,\n}) => {\n // Always call the hook (rules of hooks); when `flags` is supplied we pass no config so it\n // never fetches and the explicit flags win below.\n const fetched = useWireFeatures(flags ? undefined : config);\n const value = flags ?? fetched;\n const stable = useMemo(() => value, [value]);\n return <WireFeaturesContext.Provider value={stable}>{children}</WireFeaturesContext.Provider>;\n};\n\nWireFeaturesProvider.displayName = \"WireFeaturesProvider\";\n\n/** Raw context read — `null` when no `WireFeaturesProvider` is mounted above. */\nexport const useWireFeaturesContext = (): WireFeatures | null =>\n useContext(WireFeaturesContext);\n\n/** Options for `useResolvedFeatures`: an explicit override and/or a lazy-fetch config. */\nexport interface ResolveFeaturesOptions {\n /** Explicit flags — win over everything (a surface's own `features` prop). */\n flags?: WireFeatures;\n /** Lazy-fetch config, used ONLY when there is no explicit `flags` and no provider above. */\n config?: WireFeaturesConfig;\n}\n\n/**\n * The resolution every gated surface uses. Precedence:\n * 1. explicit `options.flags` (a surface's own prop) →\n * 2. the nearest `WireFeaturesProvider` (context) →\n * 3. a lazy fetch from `options.config` (serverUrl + apiKey) →\n * 4. the all-on defaults.\n * Always fail-open, always a stable reference for a given value (so it's safe in effect deps).\n */\nexport const useResolvedFeatures = (options?: ResolveFeaturesOptions): WireFeatures => {\n const ctx = useWireFeaturesContext();\n // Only lazily fetch when nothing else provides the flags — otherwise pass no config so the\n // hook stays inert (defaults, no network). The hook is always called (rules of hooks).\n const shouldFetch = !options?.flags && ctx == null;\n const fetched = useWireFeatures(shouldFetch ? options?.config : undefined);\n return options?.flags ?? ctx ?? fetched ?? defaultWireFeatures;\n};\n","/**\n * equality.ts — tiny, dependency-free value-equality helpers used to STABILIZE\n * references across renders.\n *\n * WHY this exists: real integrators pass inline objects — `config={{ id: 'x' }}`,\n * `decision={{ fire: true }}` — whose IDENTITY changes every render even though\n * their VALUES do not. If the review hook let those unstable references flow into\n * a `useMemo`/`useEffect` dependency list (or handed a fresh verdict object back\n * every render), a host that depends on the returned value would re-run its own\n * effect forever → \"Maximum update depth exceeded\". A library must tolerate inline\n * props without looping; these helpers are how the hook holds a stable reference\n * whenever the underlying values are unchanged.\n *\n * Pure (no React import) so it runs under the kit's node:test runner.\n */\nimport type { ReviewDecision } from \"./types\";\n\n/**\n * One-level value-equality of two plain objects (or primitives). Same-reference is\n * equal; two objects are equal when they have the same keys and each value is\n * `Object.is`-equal. Good enough for the flat `config` / `decision` shapes the gate\n * accepts (all leaf values are primitives).\n */\nexport const shallowEqual = (a: unknown, b: unknown): boolean => {\n if (Object.is(a, b)) return true;\n if (\n a == null ||\n b == null ||\n typeof a !== \"object\" ||\n typeof b !== \"object\"\n ) {\n return false;\n }\n const ak = Object.keys(a as Record<string, unknown>);\n const bk = Object.keys(b as Record<string, unknown>);\n if (ak.length !== bk.length) return false;\n for (const k of ak) {\n if (!Object.is((a as Record<string, unknown>)[k], (b as Record<string, unknown>)[k])) {\n return false;\n }\n }\n return true;\n};\n\n/**\n * Structural equality of two review verdicts by their contract fields (`fire`,\n * `reason`). A `null`/`undefined` verdict equals only another absent one. Used to\n * decide whether the hook can keep returning the SAME verdict object (so a host\n * that lists `controller.decision` in an effect dep array never loops).\n */\nexport const sameDecision = (a?: ReviewDecision | null, b?: ReviewDecision | null): boolean => {\n if (a == null || b == null) return a == b;\n return a.fire === b.fire && a.reason === b.reason;\n};\n","/**\n * Runtime helpers for the review gate: the `wire_review_*` gate keys and small\n * typed storage accessors. The gate reuses the SAME injected sync storage + global\n * testing flag as the coachmark engine (see coachmarks/runtime), so a host that\n * mounted CoachmarkProvider gets review gating for free — no second storage to wire.\n */\nimport type { CoachmarkStorage } from \"../coachmarks/types\";\nimport { getCoachmarkStorage } from \"../coachmarks/runtime\";\nimport { getCurrentSessionId } from \"../analytics/currentSession\";\nimport { makeSessionId } from \"../analytics/reportClientEvent\";\n\n/** Once-gate key. Keyed by app version when `oncePerVersion` is on, so a new release re-enables. */\nexport const reviewSeenKey = (id: string, version?: string): string =>\n version ? `wire_review_${id}_${version}_seen` : `wire_review_${id}_seen`;\n\n/** Last-shown timestamp key (epoch ms), for the cooldown rule. */\nexport const reviewLastShownKey = (id: string): string => `wire_review_${id}_last`;\n\n/** Session-count key, incremented once per APP-OPEN (not per mount), for the min-sessions rule. */\nexport const reviewSessionsKey = (id: string): string => `wire_review_${id}_sessions`;\n\n/** Companion key holding the open id the counter was LAST incremented for (see {@link bumpSessionCount}). */\nexport const reviewSessionOpenKey = (id: string): string => `wire_review_${id}_open`;\n\n/** Resolve the storage to use: an explicit override, else the provider-injected singleton. */\nexport const resolveStorage = (\n override?: CoachmarkStorage | null,\n): CoachmarkStorage | null => override ?? getCoachmarkStorage();\n\n/** RN sets this global; absent under node/SSR. Read defensively via {@link warnMissingGateStorage}. */\ndeclare const __DEV__: boolean | undefined;\n\n/** Once-per-process latch on its own `Symbol.for` slot: a plain `let` would warn once per inlined\n * bundle copy, and the gates live in three of them. */\nconst NO_STORAGE_WARNED_SLOT: unique symbol = Symbol.for(\n \"@wireai/activation:gateStorageWarned\",\n);\n\ntype GlobalWithGateWarn = typeof globalThis & { [NO_STORAGE_WARNED_SLOT]?: boolean };\n\nconst gateWarnGlobal = globalThis as GlobalWithGateWarn;\n\n/**\n * Warn once, in dev builds only, when a gate has NO sync storage to count with.\n *\n * With no storage `readInt` returns 0, so `bumpSessionCount` answers `1` on every call forever and\n * the fail-closed `minSessions: 2` default can never be met: the gate is pinned shut for the life of\n * the app and nothing anywhere says so. The fix is one line at the app root (mount `CoachmarkProvider`\n * with a sync storage, or pass `storage` to the gate), so name it.\n */\nexport const warnMissingGateStorage = (\n storage: CoachmarkStorage | null,\n gate: \"review\" | \"questionnaire\",\n): void => {\n if (storage) return;\n if (gateWarnGlobal[NO_STORAGE_WARNED_SLOT]) return;\n if (typeof __DEV__ === \"undefined\" || !__DEV__) return;\n if (typeof console === \"undefined\" || !console.warn) return;\n gateWarnGlobal[NO_STORAGE_WARNED_SLOT] = true;\n console.warn(\n `[wireai] the ${gate} gate has no sync storage, so its app-open counter is stuck at 1 and the ` +\n \"fail-closed minSessions rule can never be satisfied — the gate will never fire. Mount \" +\n \"CoachmarkProvider with a sync storage adapter at your app root, or pass `storage` to the gate.\",\n );\n};\n\n/** Test-only: forget the once-per-process no-storage warn latch. */\nexport const resetGateStorageWarning = (): void => {\n gateWarnGlobal[NO_STORAGE_WARNED_SLOT] = undefined;\n};\n\n/** Read an integer from sync storage (0 on a missing/unparseable/throwing read). */\nexport const readInt = (storage: CoachmarkStorage | null, key: string): number => {\n if (!storage) return 0;\n try {\n const raw = storage.getItem(key);\n const n = raw == null ? 0 : parseInt(raw, 10);\n return Number.isFinite(n) ? n : 0;\n } catch {\n return 0;\n }\n};\n\n/** Write an integer to sync storage. Best-effort — a throwing adapter never breaks the gate. */\nexport const writeInt = (storage: CoachmarkStorage | null, key: string, value: number): void => {\n if (!storage) return;\n try {\n storage.setItem(key, String(value));\n } catch {\n // Best-effort.\n }\n};\n\n/** Read a string from sync storage (undefined on a missing/blank/throwing read). */\nconst readStr = (storage: CoachmarkStorage | null, key: string): string | undefined => {\n if (!storage) return undefined;\n try {\n const raw = storage.getItem(key);\n return typeof raw === \"string\" && raw.length > 0 ? raw : undefined;\n } catch {\n return undefined;\n }\n};\n\n// ── The \"which app-open is this\" id ──────────────────────────────────────────────────────────\n//\n// The gates' `minSessions` rule needs a SESSION, and the counter behind it used to increment once\n// per gate MOUNT. A mount is not a session: navigating away from the home feed and back, a tab that\n// unmounts its screen, or React StrictMode's dev double-invoke of the `useState` initializer all\n// bumped it. So `minSessions: 2` — the fail-closed default added after the 2026-07-16 one-star\n// incident — was satisfiable inside the user's FIRST app open, which is the exact thing it exists\n// to prevent. `sessions` was a count of mounts wearing the name of a session.\n//\n// The correct unit already exists: `getCurrentSessionId()`, the per-open id the server sees on\n// `app.session_started`. When a host wires the lifecycle events, the counter keys off THAT and is\n// exactly \"distinct app-opens\", the same unit the server's own `min_sessions` uses.\n//\n// When no host wired lifecycle events there is no registered open, so we fall back to a PROCESS-\n// scoped id: one JS process is one app launch, which is still a genuine app-open and is strictly\n// closer to a session than a mount is. It lives in a `globalThis` slot keyed by `Symbol.for(...)`\n// for the same reason `currentSession` does: tsup inlines this module into several bundles and a\n// plain module-local `let` would give each bundle its own \"process\".\n//\n// ── WHY THE UNIT IS PINNED FOR THE WHOLE LAUNCH ──────────────────────────────────────────────\n// The first shape of this function read the two tiers LIVE on every call: the registered session id\n// when there was one, else the process id. That let the UNIT change mid-launch, and React's own\n// ordering guarantees it does. The gates call `bumpSessionCount` from a `useState` INITIALIZER,\n// which runs during render; `useLifecycleEvents` registers the session id from a root EFFECT; and\n// React runs every render before any effect. So a cold start went:\n//\n// render: gate initializer → no session registered yet → PROCESS id → count = 1, open = <process>\n// effect: root lifecycle → setCurrentSessionId(<session id>)\n// remount: gate initializer → SESSION id ≠ the stored open → count = 2\n//\n// Two \"sessions\" inside one app open, which makes the fail-closed `minSessions: 2` default (added\n// after the 2026-07-16 one-star incident) satisfiable in the very launch it exists to guard. So the\n// FIRST read pins whatever it resolved into the process slot and every later read returns that,\n// regardless of what the session registry does afterwards. The client counter only has to be\n// monotone and per-launch; the server's own `min_sessions` still counts real `app.session_started`\n// events, so nothing downstream needs the two ids to be identical.\nconst PROCESS_OPEN_ID_SLOT: unique symbol = Symbol.for(\"@wireai/activation:processOpenId\");\n\ntype GlobalWithOpenId = typeof globalThis & { [PROCESS_OPEN_ID_SLOT]?: string };\n\nconst openIdGlobal = globalThis as GlobalWithOpenId;\n\n/**\n * The id identifying THIS app-open for gate counting, PINNED on first read for the whole launch:\n * the live per-open `session_id` if one was already registered when the first gate asked, else a\n * minted per-process id. Stable from the first render to the last. Never empty.\n */\nexport const currentOpenId = (): string => {\n const existing = openIdGlobal[PROCESS_OPEN_ID_SLOT];\n if (existing) return existing;\n // Adopt the registered session id when the host wired lifecycle BEFORE any gate rendered; that is\n // the same unit the server counts. Otherwise mint one. Either way it is pinned from here on.\n const resolved = getCurrentSessionId() ?? makeSessionId();\n openIdGlobal[PROCESS_OPEN_ID_SLOT] = resolved;\n return resolved;\n};\n\n/** Test-only: forget the process open id so a unit test starts from a clean launch. */\nexport const resetProcessOpenId = (): void => {\n openIdGlobal[PROCESS_OPEN_ID_SLOT] = undefined;\n};\n\n/**\n * Return the app-open count for the gate, incrementing it AT MOST ONCE per app-open.\n *\n * IDEMPOTENT by construction: the open id that last incremented the counter is stored alongside it,\n * so a second call within the same open (a remount, a StrictMode double-invoke, a second gate render)\n * reads the stored count back instead of bumping it. A new open id bumps exactly once.\n *\n * Storage-less hosts get `1` for the first call and `1` for every later call within the open —\n * degraded but never inflating, which is the safe direction for a fail-closed gate.\n */\nexport const bumpSessionCount = (\n storage: CoachmarkStorage | null,\n sessionsKey: string,\n openKey: string,\n openId: string = currentOpenId(),\n): number => {\n const lastOpen = readStr(storage, openKey);\n const stored = readInt(storage, sessionsKey);\n // Already counted this open → return what we counted, do not bump again.\n if (lastOpen === openId) return stored > 0 ? stored : 1;\n const next = stored + 1;\n writeInt(storage, sessionsKey, next);\n if (storage) {\n try {\n storage.setItem(openKey, openId);\n } catch {\n // Best-effort: a failed write only means this open may be counted twice.\n }\n }\n return next;\n};\n","/**\n * useReviewGate — owns WHEN the review gate shows.\n *\n * v1 decision = local rules from `config` (enabled / minSessions / minEvents / cooldownDays\n * / oncePerVersion), evaluated against the same injected sync storage as the coachmark\n * engine (`wire_review_*` keys). The AI seam: pass a server-provided `decision` and it\n * OVERRIDES the local rules (see decideReview). The whole cost of being AI-ready is passing\n * that object straight through.\n *\n * Fallback chain (matches the server design): (1) a server `decision` wins immediately;\n * (2) no decision → after `timeoutFallbackMs` the local rules take over; (3) the once-gate\n * (`wire_review_<id>_seen`) always applies locally, so a server bug can never spam the prompt.\n * The global `isTestingCoachmark` flag force-shows the gate for QA replay.\n */\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport { hasSeenGate, isCoachmarkTesting, markSeenGate } from \"../coachmarks/runtime\";\nimport { useResolvedFeatures } from \"../features/WireFeaturesProvider\";\nimport { decideReview, evaluateGate, resolveRules } from \"./decision\";\nimport { sameDecision, shallowEqual } from \"./equality\";\nimport {\n bumpSessionCount,\n readInt,\n resolveStorage,\n reviewLastShownKey,\n reviewSeenKey,\n reviewSessionOpenKey,\n reviewSessionsKey,\n warnMissingGateStorage,\n writeInt,\n} from \"./runtime\";\nimport type {\n ReviewDecision,\n ReviewGateController,\n UseReviewGateOptions,\n} from \"./types\";\n\n/**\n * Hold a stable reference for a value while its CONTENT is unchanged. Integrators\n * pass inline `config` / `decision` objects (idiomatic React) whose identity flips\n * every render; feeding those straight into the deps below would recompute the\n * verdict — and mint a fresh verdict object — on every render, which loops any host\n * that lists the returned verdict in its own effect. Comparing by value here keeps\n * the reference (and everything derived from it) stable across identity-only churn.\n */\nconst useStableValue = <T,>(value: T, isEqual: (a: T, b: T) => boolean): T => {\n const ref = useRef(value);\n if (!isEqual(ref.current, value)) ref.current = value;\n return ref.current;\n};\n\nexport const useReviewGate = ({\n config: configProp,\n decision: decisionProp,\n events = 0,\n storage,\n isTesting,\n features,\n featuresConfig,\n}: UseReviewGateOptions): ReviewGateController => {\n // Tolerate inline-identity props: only adopt a new reference on a real value change.\n const config = useStableValue(configProp, shallowEqual);\n const decision = useStableValue(\n decisionProp,\n (a, b) => sameDecision(a, b),\n );\n\n // The review MASTER switch. Disabled → the gate never fires, composing as an extra AND over\n // the local rules + the server decision seam below (fail-open: defaults to enabled).\n const featureEnabled = useResolvedFeatures({ flags: features, config: featuresConfig }).review.enabled;\n\n const testing = isTesting ?? isCoachmarkTesting();\n const seenKey = reviewSeenKey(config.id, config.oncePerVersion === false ? undefined : config.appVersion);\n const lastKey = reviewLastShownKey(config.id);\n const sessionsKey = reviewSessionsKey(config.id);\n const sessionOpenKey = reviewSessionOpenKey(config.id);\n\n // Read (and bump) the app-open counter. IDEMPOTENT per app-open, NOT per mount: `bumpSessionCount`\n // keys off the live per-open session id (or a per-process id when no host wired the lifecycle\n // events), so a remount, a navigation return, or React StrictMode's dev double-invoke of this\n // initializer all read the same number back instead of inflating it. Before this, `sessions`\n // counted mounts, so the fail-closed `minSessions: 2` default could be satisfied inside the user's\n // very first app open — the exact scenario it was added to prevent.\n const sessions = useState(() => {\n const store = resolveStorage(storage);\n // No storage pins the counter at 1 forever, so the fail-closed minSessions rule can never be\n // met and the gate silently never fires. Dev-only, once per process.\n warnMissingGateStorage(store, \"review\");\n return bumpSessionCount(store, sessionsKey, sessionOpenKey);\n })[0];\n\n // Gate the local rules behind an optional client-side timeout, so a reachable server\n // gets a window to answer first. A present `decision` bypasses the wait entirely.\n const hasServerDecision = decision != null;\n const timeoutMs = config.timeoutFallbackMs;\n const waits = !hasServerDecision && timeoutMs != null && timeoutMs > 0;\n const [elapsed, setElapsed] = useState(!waits);\n useEffect(() => {\n if (!waits) {\n setElapsed(true);\n return;\n }\n const timer = setTimeout(() => setElapsed(true), timeoutMs as number);\n return () => clearTimeout(timer);\n }, [waits, timeoutMs]);\n\n const { visible, verdict } = useMemo(() => {\n // Master kill switch wins over everything, including QA replay: a tenant that turned reviews\n // OFF must never see the gate, whatever the rules, the server decision, or testing say.\n if (!featureEnabled) return { visible: false, verdict: { fire: false, reason: \"feature_disabled\" } as ReviewDecision };\n\n // QA replay: force-show, bypassing rules + gates (safe: only via __DEV__ && bool).\n if (testing) return { visible: true, verdict: { fire: true, reason: \"testing\" } as ReviewDecision };\n\n const store = resolveStorage(storage);\n const local = evaluateGate(resolveRules(config), {\n sessions,\n events,\n lastShownAt: readInt(store, lastKey) || null,\n seen: hasSeenGate(seenKey, storage, isTesting),\n now: Date.now(),\n });\n const resolved = decideReview(local, decision);\n // (3) the once-gate always wins locally, even over a server \"fire\".\n const alreadySeen = hasSeenGate(seenKey, storage, isTesting);\n const ready = hasServerDecision || elapsed;\n return { visible: ready && resolved.fire && !alreadySeen, verdict: resolved };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [featureEnabled, testing, config, decision, events, sessions, elapsed, hasServerDecision, seenKey, lastKey, storage, isTesting]);\n\n // Stable callbacks + a stable controller object, so a host can safely list any of\n // them in its own effect deps without the effect re-firing every render.\n const shownRef = useRef(false);\n const markShown = useCallback(() => {\n if (shownRef.current) return;\n shownRef.current = true;\n writeInt(resolveStorage(storage), lastKey, Date.now());\n }, [storage, lastKey]);\n const markResolved = useCallback(() => {\n markSeenGate(seenKey, storage, isTesting);\n }, [seenKey, storage, isTesting]);\n\n return useMemo<ReviewGateController>(\n () => ({ visible, decision: verdict, markShown, markResolved }),\n [visible, verdict, markShown, markResolved],\n );\n};\n"]}
1
+ {"version":3,"sources":["../../src/theme/defaultTheme.ts","../../src/theme/ThemeContext.tsx","../../src/theme/typography.ts","../../src/motion/motionSpec.ts","../../src/motion/useReducedMotion.ts","../../src/components/Button.tsx","../../src/components/DoneCheck.tsx","../../src/components/CardHandoff.tsx","../../src/components/ModalPaneHost.tsx","../../src/haptics/haptics.ts","../../src/theme/mergeThemeOver.ts","../../src/reviews/decision.ts","../../src/reviews/feedbackForm.ts","../../src/motion/centeredModalMotion.ts","../../src/reviews/presentation.ts","../../src/components/CenteredModal.tsx","../../src/reviews/storeReview.ts","../../src/analytics/reportClientEvent.ts","../../src/utils/warnInDev.ts","../../src/analytics/currentSession.ts","../../src/reviews/transport.ts","../../src/reviews/ReviewGate.tsx","../../src/coachmarks/runtime.ts","../../src/features/defaults.ts","../../src/features/cache.ts","../../src/features/fetchWireFeatures.ts","../../src/features/useWireFeatures.ts","../../src/features/WireFeaturesProvider.tsx","../../src/reviews/equality.ts","../../src/reviews/runtime.ts","../../src/reviews/useReviewGate.ts"],"names":["createContext","useContext","useState","useEffect","AccessibilityInfo","useRef","Animated","useCallback","jsx","Pressable","Text","React","StyleSheet","styles","Easing","jsxs","Fragment","easeWire","View","forwardRef","useImperativeHandle","BackHandler","Modal","SafeAreaView","Platform","Linking","useMemo","TextInput","useStableValue"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * defaultOnboardingTheme — a neutral, light, brand-agnostic baseline. Consumers\n * pass a `Partial<OnboardingTheme>` that is deep-merged over this (see\n * `mergeTheme`), so they only override what differs from neutral.\n */\nimport type { OnboardingTheme } from \"./types\";\n\nexport const defaultOnboardingTheme: OnboardingTheme = {\n colors: {\n background: \"#FFFFFF\",\n surface: \"#F7F7F8\",\n text: \"#101114\",\n textMuted: \"#6B7280\",\n primary: \"#111111\",\n onPrimary: \"#FFFFFF\",\n primarySoft: \"rgba(17,17,17,0.06)\",\n border: \"#E4E4E7\",\n borderStrong: \"#C7C7CC\",\n disabled: \"#EBEBEB\",\n success: \"#16A34A\",\n warning: \"#D97706\",\n error: \"#DC2626\",\n info: \"#2563EB\",\n },\n fonts: {\n regular: undefined,\n medium: undefined,\n bold: undefined,\n baseSize: 16,\n scale: 1,\n },\n radius: {\n sm: 8,\n md: 12,\n lg: 16,\n full: 9999,\n },\n spacing: {\n xs: 4,\n sm: 8,\n md: 16,\n lg: 24,\n xl: 32,\n },\n // Circled (pill) CTA by default; an app can pass `button: { radius: 12 }` for\n // a rounded rectangle, or override height/paddingX to match its design system.\n button: {\n radius: 9999,\n height: 52,\n paddingX: 24,\n },\n};\n\n/**\n * Deep-merge a partial theme over the default. Two levels deep is enough — the\n * theme is intentionally shallow (colors/fonts/radius/spacing), so we merge each\n * known sub-object and leave scalars to overwrite.\n */\nexport const mergeTheme = (\n override?: Partial<OnboardingTheme>,\n): OnboardingTheme => {\n if (!override) return defaultOnboardingTheme;\n return {\n colors: { ...defaultOnboardingTheme.colors, ...override.colors },\n fonts: { ...defaultOnboardingTheme.fonts, ...override.fonts },\n radius: { ...defaultOnboardingTheme.radius, ...override.radius },\n spacing: { ...defaultOnboardingTheme.spacing, ...override.spacing },\n button: { ...defaultOnboardingTheme.button, ...override.button },\n };\n};\n","/**\n * ThemeContext — provides the resolved OnboardingTheme to every shell element and\n * themed card via `useOnboardingTheme()`. WHY a context rather than prop-drilling:\n * the cards are mounted by the SDK's ComponentRenderer (not by us), so we can't\n * pass them props directly — they must read the theme from context.\n *\n * ── WHY THE CONTEXT LIVES ON A `Symbol.for` SLOT ───────────────────────────────────────────────\n *\n * It was a plain `createContext(...)`, and tsup inlines this module into FIVE dist bundles\n * (`.`, `./reviews`, `./questionnaire`, `./coachmarks`, `./showcase`) — so each one carried its own\n * context instance. `OnboardingThemeProvider` is exported ONLY from the root barrel, so on any\n * `dist` resolution path (plain RN CLI, `unstable_enablePackageExports: false`, node, jest, SSR) a\n * host that wrapped its tree in `<OnboardingThemeProvider>` and rendered `<ReviewGate>` from\n * `@wireai/activation/reviews` was writing one context and reading another. Nothing crashed and\n * nothing warned: `useOnboardingTheme`'s `ctx ?? mergeTheme()` fallback quietly served the NEUTRAL\n * default theme, so the tenant's brand simply did not apply to the gate. On-device this was masked\n * only because Metro's `react-native` export condition collapses every subpath back to this one\n * `src/` file — the same \"bundler accident, not a guarantee\" the `currentSession` header names, and\n * the same root cause as the optional-peers bug 0.13.3 fixed.\n *\n * The bundler-agnostic fix, exactly as `coachmarks/runtime`, `analytics/currentSession`,\n * `context/deviceId` and `features/WireFeaturesProvider` already do it: park the ONE context on a\n * well-known `globalThis` slot keyed by `Symbol.for(...)`. `Symbol.for` uses the runtime-global\n * symbol registry, so every inlined copy of this module resolves the SAME symbol, finds the slot\n * already filled, and reuses that instance — one context identity however many copies exist.\n * `globalThis` is present and identical in Hermes/React Native, Node and SSR (we never touch\n * `window`), so this is safe on every host. `test/canary/themeContextIdentity.test.tsx` measures it\n * in the BUILT artifact, because the defect is invisible in source.\n */\nimport React, { createContext, useContext, useMemo } from \"react\";\nimport { mergeTheme } from \"./defaultTheme\";\nimport type { OnboardingTheme } from \"./types\";\n\n/**\n * Well-known key into the runtime-global symbol registry. `Symbol.for` (NOT a plain `Symbol()`) is\n * what makes this cross-bundle: it returns the SAME symbol for the same string across every copy of\n * this module, so duplicated inlined copies all address one slot.\n *\n * @globalSlot LATCH — the React context object is minted by the first copy of this module to load\n * and every later copy ADOPTS it (the `if (!…)` guard below says so in one line). A second write\n * re-splits the identity: `<OnboardingThemeProvider>` (root barrel only) publishes into one context\n * while `<ReviewGate>` from a subpath reads another, and `useOnboardingTheme`'s `ctx ?? mergeTheme()`\n * fallback quietly serves the NEUTRAL theme. Because it is a latch, and only because of that, the\n * module-local `const OnboardingThemeContext` below may cache it.\n */\nconst THEME_CONTEXT_SLOT: unique symbol = Symbol.for(\"@wireai/activation:themeContext\");\n\ntype GlobalWithThemeContext = typeof globalThis & {\n [THEME_CONTEXT_SLOT]?: React.Context<OnboardingTheme | null>;\n};\n\nconst themeGlobal = globalThis as GlobalWithThemeContext;\n\n// First copy to load creates the context; every later copy adopts it. Never overwrite an existing\n// slot — a second `createContext` here would re-split the identity this guard exists to keep whole.\nif (!themeGlobal[THEME_CONTEXT_SLOT]) {\n themeGlobal[THEME_CONTEXT_SLOT] = createContext<OnboardingTheme | null>(null);\n}\nconst OnboardingThemeContext = themeGlobal[THEME_CONTEXT_SLOT] as React.Context<OnboardingTheme | null>;\n\nexport type OnboardingThemeProviderProps = {\n /** Partial overrides merged over the neutral default theme. */\n theme?: Partial<OnboardingTheme>;\n children: React.ReactNode;\n};\n\nexport const OnboardingThemeProvider: React.FC<OnboardingThemeProviderProps> = ({\n theme,\n children,\n}) => {\n const resolved = useMemo(() => mergeTheme(theme), [theme]);\n return (\n <OnboardingThemeContext.Provider value={resolved}>\n {children}\n </OnboardingThemeContext.Provider>\n );\n};\n\n/**\n * Read the active theme. Falls back to the default theme when used outside a\n * provider so a stray card never crashes — it just renders neutral.\n */\nexport const useOnboardingTheme = (): OnboardingTheme => {\n const ctx = useContext(OnboardingThemeContext);\n return ctx ?? mergeTheme();\n};\n","/**\n * typography — derive concrete RN text styles from the theme's font config, so\n * every label/title/caption in the kit scales from one `baseSize` + `scale` and\n * picks the right family. Cards call these instead of hardcoding fontSize/weight.\n */\nimport type { TextStyle } from \"react-native\";\nimport type { OnboardingFonts } from \"./types\";\n\nconst round = (n: number) => Math.round(n);\n\n/** Body text — the reference size. */\nexport const bodyStyle = (f: OnboardingFonts): TextStyle => ({\n fontFamily: f.regular,\n fontSize: round(f.baseSize * f.scale),\n lineHeight: round(f.baseSize * f.scale * 1.5),\n});\n\n/** Slightly smaller secondary/hint text. */\nexport const captionStyle = (f: OnboardingFonts): TextStyle => ({\n fontFamily: f.regular,\n fontSize: round(f.baseSize * 0.8125 * f.scale),\n lineHeight: round(f.baseSize * 0.8125 * f.scale * 1.5),\n});\n\n/** Emphasised label (e.g. a card question). */\nexport const labelStyle = (f: OnboardingFonts): TextStyle => ({\n fontFamily: f.medium ?? f.bold,\n fontSize: round(f.baseSize * f.scale),\n lineHeight: round(f.baseSize * f.scale * 1.4),\n fontWeight: f.medium ? undefined : (\"600\" as const),\n});\n\n/** Heading (status title, done block). */\nexport const headingStyle = (f: OnboardingFonts): TextStyle => ({\n fontFamily: f.bold,\n fontSize: round(f.baseSize * 1.25 * f.scale),\n lineHeight: round(f.baseSize * 1.25 * f.scale * 1.3),\n fontWeight: f.bold ? undefined : (\"700\" as const),\n});\n\n/** Button label. */\nexport const buttonLabelStyle = (f: OnboardingFonts): TextStyle => ({\n fontFamily: f.bold ?? f.medium,\n fontSize: round(f.baseSize * f.scale),\n fontWeight: f.bold ? undefined : (\"600\" as const),\n});\n","/**\n * motionSpec — the single source of truth for every animation value in the kit,\n * extracted 1:1 from the Wire Activation card-interactions design canvas\n * (see design-refs/EXTRACTED-SPEC.md for the row-by-row mapping).\n *\n * Pure constants + pure helpers only — NO react-native import — so the spec is\n * unit-testable under node:test and components can't drift from the design\n * without a red test.\n */\n\n/** The design system's \"ease-wire\" curve: cubic-bezier(0.16, 1, 0.3, 1). */\nexport const WIRE_BEZIER = [0.16, 1, 0.3, 1] as const;\n\n/** Design-system duration tokens (colors_and_type.css --duration-*). */\nexport const DURATION_FAST_MS = 150;\nexport const DURATION_BASE_MS = 300;\nexport const DURATION_SLOW_MS = 500;\n\n/** Reduce-motion fallback: a short fade to the final frame, never a frozen mid-state. */\nexport const REDUCED_FADE_MS = 120;\n\n// ── Press feedback (spec rows 1, 4, 20) ─────────────────────────────────────\nexport const ROW_PRESS_SCALE = 0.975;\nexport const CHIP_PRESS_SCALE = 0.94;\nexport const BUTTON_PRESS_SCALE = 0.975;\n/** Reduce-motion press feedback: opacity dip instead of scale. */\nexport const REDUCED_PRESS_OPACITY = 0.85;\nexport const PRESS_SPRING = { friction: 8, tension: 220 } as const;\nexport const BUTTON_PRESS_SPRING = { friction: 8, tension: 240 } as const;\n\n// ── Selection state changes (spec rows 2, 3, 5, 6) ──────────────────────────\nexport const SELECT_FILL_MS = 180;\nexport const CHIP_FILL_MS = 160;\nexport const CHECK_SPRING = { friction: 6, tension: 180 } as const;\nexport const CHECK_SCALE_FROM = 0.6;\n\n// ── Card-to-card handoff (spec rows 7-10) ───────────────────────────────────\nexport const CARD_ENTER_MS = 220;\nexport const CARD_EXIT_MS = 180;\n/** The exit starts this long BEFORE the enter (enter is delayed by this). */\nexport const CARD_EXIT_LEAD_MS = 80;\nexport const CARD_SLIDE_PX = 26;\nexport const CARD_ENTER_SCALE_FROM = 0.96;\n/** The interstitial keeps the shell's springier value-beat (AnimatedCard idiom). */\nexport const CARD_SPRING = { damping: 14, stiffness: 180, mass: 1 } as const;\n/** Loader-to-card handoff: the loader just fades out. */\nexport const LOADER_HANDOFF_FADE_MS = 120;\n\n// ── InterstitialCard reveal (spec rows 11-13) ───────────────────────────────\nexport const INTERSTITIAL_HEAD_MS = 500;\nexport const INTERSTITIAL_HEAD_STAGGER_MS = 100;\nexport const INTERSTITIAL_ITEM_MS = 300;\nexport const INTERSTITIAL_ITEM_STAGGER_MS = 90;\nexport const INTERSTITIAL_ITEM_BASE_DELAY_MS = 200;\nexport const INTERSTITIAL_CHECK_DELAY_MS = 200;\nexport const INTERSTITIAL_CHECK_SCALE_FROM = 0.4;\n\n/** Delay before work item `index` begins its reveal (row lands after the head). */\nexport const interstitialItemDelay = (index: number): number =>\n INTERSTITIAL_ITEM_BASE_DELAY_MS + index * INTERSTITIAL_ITEM_STAGGER_MS;\n\n/** Delay before work item `index`'s check pops (200ms after its row starts). */\nexport const interstitialCheckDelay = (index: number): number =>\n interstitialItemDelay(index) + INTERSTITIAL_CHECK_DELAY_MS;\n\n// ── Status / completion payoff (spec rows 14-16) ────────────────────────────\nexport const STATUS_POP_SPRING = { friction: 6, tension: 200 } as const;\nexport const STATUS_POP_SCALE_FROM = 0.4;\nexport const STATUS_BURST_MS = 500;\nexport const STATUS_BURST_SCALE_FROM = 0.4;\nexport const STATUS_BURST_SCALE_TO = 1.5;\nexport const STATUS_BURST_OPACITY_FROM = 0.5;\nexport const STATUS_COPY_MS = 300;\nexport const STATUS_COPY_STAGGER_MS = 80;\n/** Copy starts settling once the badge pop is visibly underway. */\nexport const STATUS_COPY_BASE_DELAY_MS = 200;\n\n/** Delay before payoff copy line `index` fades up. */\nexport const statusCopyDelay = (index: number): number =>\n STATUS_COPY_BASE_DELAY_MS + index * STATUS_COPY_STAGGER_MS;\n\n// ── Loading treatment (spec rows 17-19) ─────────────────────────────────────\nexport const LOADER_PULSE_MS = 1600;\nexport const LOADER_PULSE_MIN_OPACITY = 0.35;\nexport const LOADER_ORBIT_MS = 2400;\n/** Skeleton lines appear only when the fetch runs past this. */\nexport const SKELETON_DELAY_MS = 300;\nexport const SKELETON_PULSE_MS = 1500;\nexport const SKELETON_MIN_OPACITY = 0.45;\n\n// ── Progress bar (spec row 21) ──────────────────────────────────────────────\nexport const PROGRESS_MS = 400;\n\n// ── Dev/QA duration scale (slow-mo for judging motion) ──────────────────────\n// A module-level multiplier applied to every timing DURATION (and stagger\n// delay) at animation start. 1 = design speed; 2 = half-speed slow-mo. Springs\n// are physics-based (friction/tension) and do NOT scale. This is a dev/QA tool\n// (the playground's tweak panel drives it) — production code never calls the\n// setter, so the default 1 keeps shipped motion exactly on spec.\n\nlet durationScale = 1;\n\n/** Set the global duration multiplier (clamped to 0.1-10; invalid resets to 1). */\nexport const setMotionDurationScale = (scale: number): void => {\n durationScale = Number.isFinite(scale) && scale > 0 ? Math.min(10, Math.max(0.1, scale)) : 1;\n};\n\nexport const getMotionDurationScale = (): number => durationScale;\n\n/** Apply the current duration scale to a spec duration/delay (rounded ms). */\nexport const scaledMs = (ms: number): number => Math.round(ms * durationScale);\n\n// ── Reduce-motion helpers (pure, shared by every animated component) ────────\n\nexport type EntranceSpec = {\n /** Duration to animate with (falls back to the short fade under reduce motion). */\n duration: number;\n /** Whether transforms (translate/scale) may move; false = opacity-only fade. */\n useTransform: boolean;\n};\n\n/** Entrances: full motion normally; a 120ms opacity-only fade under reduce motion. */\nexport const entranceSpec = (reduced: boolean, durationMs: number): EntranceSpec =>\n reduced\n ? { duration: REDUCED_FADE_MS, useTransform: false }\n : { duration: durationMs, useTransform: true };\n\n/** State changes (select fills etc.): snap instantly under reduce motion. */\nexport const stateChangeDuration = (reduced: boolean, durationMs: number): number =>\n reduced ? 0 : durationMs;\n\n/** Looping/ambient animations run only when motion is allowed. */\nexport const loopEnabled = (reduced: boolean): boolean => !reduced;\n","/**\n * useReducedMotion — the kit's ONE reduce-motion gate. Reads the OS setting via\n * AccessibilityInfo and stays subscribed to `reduceMotionChanged`, so every\n * animated component lands on its final frame (or a ~120ms fade) the moment the\n * user flips the setting — no restart needed.\n *\n * This closes the gap documented in ai_rules/rules/performance.md §3: every\n * animation in the kit must consume this hook (usually through the pure helpers\n * in motionSpec.ts: `entranceSpec` / `stateChangeDuration` / `loopEnabled`).\n */\nimport { useEffect, useState } from \"react\";\nimport { AccessibilityInfo } from \"react-native\";\n\n// Last-known value, module-level: cards remount every turn (keyed on message id),\n// and a `useState(false)` default would flash one animated frame per turn for a\n// reduce-motion user before the async read resolves.\nlet lastKnown = false;\n\nexport const useReducedMotion = (): boolean => {\n const [reduced, setReduced] = useState(lastKnown);\n\n useEffect(() => {\n let mounted = true;\n AccessibilityInfo.isReduceMotionEnabled()\n .then((value) => {\n lastKnown = value;\n if (mounted) setReduced(value);\n })\n .catch(() => {\n // Unavailable (e.g. some test envs): keep the last-known value.\n });\n const sub = AccessibilityInfo.addEventListener(\"reduceMotionChanged\", (value) => {\n lastKnown = value;\n setReduced(value);\n });\n return () => {\n mounted = false;\n sub?.remove();\n };\n }, []);\n\n return reduced;\n};\n","/**\n * Button — the kit's themed primary/outline button, replacing the SDK's hardcoded\n * `Btn`. Every color/radius/font comes from the active theme.\n *\n * Motion (design-refs/EXTRACTED-SPEC.md row 20): \"a footer button that answers\n * the tap\" — scale 1→0.975 spring on press-in, back on release, native driver.\n * Reduce motion: an opacity dip to 0.85 while pressed, no scale. Disabled keeps\n * the `disabled` token fill with no press handler (unchanged).\n */\nimport React, { useCallback, useRef } from \"react\";\nimport { Animated, Pressable, StyleSheet, Text } from \"react-native\";\nimport { useOnboardingTheme } from \"../theme/ThemeContext\";\nimport { buttonLabelStyle } from \"../theme/typography\";\nimport {\n BUTTON_PRESS_SCALE,\n BUTTON_PRESS_SPRING,\n REDUCED_FADE_MS,\n REDUCED_PRESS_OPACITY,\n scaledMs,\n} from \"../motion/motionSpec\";\nimport { useReducedMotion } from \"../motion/useReducedMotion\";\n\ntype Variant = \"primary\" | \"outline\";\n\ntype ButtonProps = {\n title: string;\n onPress?: () => void;\n variant?: Variant;\n disabled?: boolean;\n /** Stretch to the container width — for form-style CTAs (e.g. the review form's\n * \"Send feedback\"). Default false keeps the content-width pill used by cards. */\n fullWidth?: boolean;\n};\n\nconst _Button: React.FC<ButtonProps> = ({\n title,\n onPress,\n variant = \"primary\",\n disabled = false,\n fullWidth = false,\n}) => {\n const t = useOnboardingTheme();\n const reduced = useReducedMotion();\n const isPrimary = variant === \"primary\";\n\n // 0 = at rest, 1 = pressed. Scale normally; opacity dip under reduce motion.\n const pressT = useRef(new Animated.Value(0)).current;\n const pressTo = useCallback(\n (to: number) => {\n if (reduced) {\n Animated.timing(pressT, {\n toValue: to,\n duration: scaledMs(REDUCED_FADE_MS),\n useNativeDriver: true,\n }).start();\n return;\n }\n Animated.spring(pressT, {\n toValue: to,\n friction: BUTTON_PRESS_SPRING.friction,\n tension: BUTTON_PRESS_SPRING.tension,\n useNativeDriver: true,\n }).start();\n },\n [pressT, reduced],\n );\n const lastPress = useRef<number>(0);\n const handlePress = useCallback(() => {\n if (!onPress) return;\n const now = Date.now();\n if (now - lastPress.current < 500) return;\n lastPress.current = now;\n onPress();\n }, [onPress]);\n\n const handlePressIn = useCallback(() => pressTo(1), [pressTo]);\n const handlePressOut = useCallback(() => pressTo(0), [pressTo]);\n\n const scale = pressT.interpolate({\n inputRange: [0, 1],\n outputRange: [1, reduced ? 1 : BUTTON_PRESS_SCALE],\n });\n const opacity = pressT.interpolate({\n inputRange: [0, 1],\n outputRange: [1, reduced ? REDUCED_PRESS_OPACITY : 1],\n });\n\n return (\n <Pressable\n onPress={disabled ? undefined : handlePress}\n onPressIn={disabled ? undefined : handlePressIn}\n onPressOut={disabled ? undefined : handlePressOut}\n disabled={disabled}\n accessibilityRole=\"button\"\n accessibilityState={{ disabled }}\n style={fullWidth ? styles.fill : undefined}\n >\n <Animated.View\n style={[\n styles.base,\n fullWidth && styles.fill,\n {\n borderRadius: t.button.radius,\n minHeight: t.button.height,\n paddingHorizontal: t.button.paddingX,\n opacity,\n transform: [{ scale }],\n },\n isPrimary\n ? { backgroundColor: disabled ? t.colors.disabled : t.colors.primary }\n : { borderWidth: 1.5, borderColor: disabled ? t.colors.border : t.colors.primary },\n ]}\n >\n <Text\n style={[\n buttonLabelStyle(t.fonts),\n {\n color: disabled\n ? t.colors.textMuted\n : isPrimary\n ? t.colors.onPrimary\n : t.colors.primary,\n },\n ]}\n >\n {title}\n </Text>\n </Animated.View>\n </Pressable>\n );\n};\n\nexport const Button = React.memo(_Button);\n\nconst styles = StyleSheet.create({\n base: { alignItems: \"center\", justifyContent: \"center\" },\n fill: { width: \"100%\" },\n});\n","/**\n * DoneCheck — the animated \"done\" payoff glyph shared by the review gate's and the\n * questionnaire's thank-you panes. One component, two call sites, one feel.\n *\n * The beat is the house success beat, lifted from StatusCard (design-refs/\n * EXTRACTED-SPEC.md rows 14/16): the glyph springs in with a slight overshoot\n * (STATUS_POP_SPRING, scale STATUS_POP_SCALE_FROM→1) while the ring settles behind it.\n * No new motion values are invented here — every number comes from motionSpec.\n *\n * It is a Text glyph in a themed circle, NOT an SVG path: `react-native-svg` is not a\n * peer of this kit, and adding a NATIVE dependency to draw a checkmark would force every\n * host into a rebuild. The five review stars are already glyphs, so this matches the\n * house idiom.\n *\n * Reduce motion: the final frame, instantly — never a frozen mid-pop.\n *\n * Accessibility: purely decorative. The thank-you copy next to it carries the meaning,\n * so the glyph is hidden from screen readers rather than announced as \"heavy check mark\".\n */\nimport React, { useEffect, useRef } from \"react\";\nimport { Animated, StyleSheet } from \"react-native\";\n\nimport { STATUS_POP_SCALE_FROM, STATUS_POP_SPRING } from \"../motion/motionSpec\";\nimport { useReducedMotion } from \"../motion/useReducedMotion\";\nimport type { OnboardingTheme } from \"../theme/types\";\n\nexport type DoneCheckProps = {\n /** The (already merged) active theme — the ring reads `colors.success`. */\n theme: OnboardingTheme;\n /** Diameter of the ring. Defaults to a size that balances the modal heading. */\n size?: number;\n};\n\nconst _DoneCheck: React.FC<DoneCheckProps> = ({ theme, size = 48 }) => {\n const reduced = useReducedMotion();\n const pop = useRef(new Animated.Value(reduced ? 1 : 0)).current;\n\n useEffect(() => {\n if (reduced) {\n pop.setValue(1);\n return;\n }\n const anim = Animated.spring(pop, {\n toValue: 1,\n friction: STATUS_POP_SPRING.friction,\n tension: STATUS_POP_SPRING.tension,\n useNativeDriver: true,\n });\n anim.start();\n return () => anim.stop();\n }, [pop, reduced]);\n\n const scale = pop.interpolate({\n inputRange: [0, 1],\n outputRange: [STATUS_POP_SCALE_FROM, 1],\n });\n\n return (\n <Animated.View\n accessibilityElementsHidden\n importantForAccessibility=\"no-hide-descendants\"\n style={[\n styles.ring,\n {\n width: size,\n height: size,\n borderRadius: theme.radius.full,\n backgroundColor: theme.colors.success,\n opacity: pop,\n transform: [{ scale }],\n },\n ]}\n >\n <Animated.Text\n style={[\n styles.glyph,\n { color: theme.colors.onPrimary, fontSize: Math.round(size * 0.5), fontFamily: theme.fonts.bold },\n ]}\n >\n ✓\n </Animated.Text>\n </Animated.View>\n );\n};\n\nexport const DoneCheck = React.memo(_DoneCheck);\nDoneCheck.displayName = \"DoneCheck\";\n\nconst styles = StyleSheet.create({\n ring: { alignItems: \"center\", justifyContent: \"center\", alignSelf: \"center\" },\n glyph: { includeFontPadding: false, textAlign: \"center\" },\n});\n\nexport default DoneCheck;\n","/**\n * CardHandoff — the card-to-card transition from the Wire Activation design:\n * \"cards hand off, they don't cut\". When `transitionKey` changes, the outgoing\n * child leaves left (translateX 0→-26, fade, 180ms) in an absolute layer while\n * the incoming child arrives from the right (translateX +26→0, fade, 220ms)\n * delayed by the 80ms exit lead — one moving surface, ~300ms total.\n *\n * Variants (per child):\n * - \"slide\": questions — the directional stepper motion above.\n * - \"spring\": the InterstitialCard value beat — scale 0.96→1 spring pop\n * (the shell's springier variant, kept from AnimatedCard).\n * - \"fade\": loaders — enter/exit as a plain 120ms fade so the \"thinking\"\n * state hands off to the first card without a hard cut.\n *\n * `direction` mirrors the whole slide for BACK navigation, so a step the user\n * returns to comes back from the side it left toward. Spatial consistency: if\n * \"next\" pushes left, \"back\" must pull right, or the stack loses its geography.\n *\n * `layout` picks how the moving surface is sized. \"fill\" (default) is the\n * full-screen flow, where the pane owns the viewport. \"auto\" is for a pane\n * inside an auto-height container (the CenteredModal card): `flex: 1` there\n * resolves to a ZERO-height child under Yoga — flex-basis 0 with no free space\n * to grow into — so the in-flow layer must size to its own content instead.\n *\n * Reduce motion: every handoff becomes a 120ms crossfade, no translate/scale.\n * All values run on the native driver (transform/opacity only); in-flight\n * animations are stopped when superseded and on unmount.\n *\n * This SUPERSEDES the enter-only AnimatedCard: same keyed-remount contract from\n * OnboardingFlow (the child renderer stays keyed on the message id), plus the\n * paired exit layer the design calls for.\n */\nimport React, { useEffect, useRef, useState } from \"react\";\nimport { Animated, Easing, StyleSheet } from \"react-native\";\nimport {\n CARD_ENTER_MS,\n CARD_ENTER_SCALE_FROM,\n CARD_EXIT_LEAD_MS,\n CARD_EXIT_MS,\n CARD_SLIDE_PX,\n CARD_SPRING,\n LOADER_HANDOFF_FADE_MS,\n REDUCED_FADE_MS,\n WIRE_BEZIER,\n scaledMs,\n} from \"../motion/motionSpec\";\nimport { useReducedMotion } from \"../motion/useReducedMotion\";\n\nconst easeWire = Easing.bezier(...WIRE_BEZIER);\n\nexport type CardHandoffVariant = \"slide\" | \"spring\" | \"fade\";\n\n/** Which way the stack is moving. \"backward\" mirrors the slide (see the header note). */\nexport type CardHandoffDirection = \"forward\" | \"backward\";\n\n/** How the moving surface is sized. \"auto\" = a pane in an auto-height container. */\nexport type CardHandoffLayout = \"fill\" | \"auto\";\n\nexport type CardHandoffProps = {\n /** Identity of the current child (e.g. the message id). A change runs the handoff. */\n transitionKey: string;\n /** How the CURRENT child enters (and, snapshotted, how it will later exit). */\n variant?: CardHandoffVariant;\n /** Travel direction of THIS transition. Default \"forward\". */\n direction?: CardHandoffDirection;\n /** Sizing mode. Default \"fill\" (the full-screen flow). */\n layout?: CardHandoffLayout;\n children: React.ReactNode;\n};\n\ntype Snapshot = { key: string; node: React.ReactNode; variant: CardHandoffVariant };\n\nconst _CardHandoff: React.FC<CardHandoffProps> = ({\n transitionKey,\n variant = \"slide\",\n direction = \"forward\",\n layout = \"fill\",\n children,\n}) => {\n const reduced = useReducedMotion();\n const [leaving, setLeaving] = useState<Snapshot | null>(null);\n\n const enterT = useRef(new Animated.Value(0)).current;\n const exitT = useRef(new Animated.Value(0)).current;\n // The previous render's child, snapshotted so it can play the exit layer.\n const prev = useRef<Snapshot>({ key: transitionKey, node: children, variant });\n const running = useRef<{ enter?: Animated.CompositeAnimation; exit?: Animated.CompositeAnimation }>({});\n const mounted = useRef(false);\n\n // One orchestration effect, run every render: refresh the snapshot while the key\n // is stable; on a key change, snapshot the old child into the exit layer and run\n // the paired exit+enter. (No deps array on purpose — `children` is a fresh node\n // every parent render and must keep the snapshot current.)\n useEffect(() => {\n const last = prev.current;\n prev.current = { key: transitionKey, node: children, variant };\n\n // First mount: enter only (the design's initial reveal), nothing exits.\n if (!mounted.current) {\n mounted.current = true;\n running.current.enter = startEnter(enterT, variant, reduced, 0);\n return;\n }\n if (last.key === transitionKey) return;\n\n running.current.enter?.stop();\n running.current.exit?.stop();\n setLeaving(last);\n enterT.setValue(0);\n exitT.setValue(0);\n\n const exitDuration = reduced\n ? REDUCED_FADE_MS\n : last.variant === \"fade\"\n ? LOADER_HANDOFF_FADE_MS\n : CARD_EXIT_MS;\n const exit = Animated.timing(exitT, {\n toValue: 1,\n duration: scaledMs(exitDuration),\n easing: easeWire,\n useNativeDriver: true,\n });\n running.current.exit = exit;\n exit.start();\n\n const enter = startEnter(enterT, variant, reduced, reduced ? 0 : CARD_EXIT_LEAD_MS, () =>\n setLeaving(null),\n );\n running.current.enter = enter;\n });\n\n // Stop anything in flight on unmount (leaked animations drain the JS thread).\n useEffect(\n () => () => {\n running.current.enter?.stop();\n running.current.exit?.stop();\n },\n [],\n );\n\n const enterStyle = {\n opacity: enterT,\n transform: transformFor(variant, enterT, reduced, \"enter\", direction),\n };\n\n return (\n <>\n {leaving ? (\n <Animated.View\n pointerEvents=\"none\"\n style={[\n StyleSheet.absoluteFill,\n {\n opacity: exitT.interpolate({ inputRange: [0, 1], outputRange: [1, 0] }),\n // The CURRENT direction drives BOTH layers: one transition, one geography.\n transform: transformFor(leaving.variant, exitT, reduced, \"exit\", direction),\n },\n ]}\n >\n {leaving.node}\n </Animated.View>\n ) : null}\n <Animated.View key={variant} style={[layout === \"fill\" ? styles.fill : null, enterStyle]}>\n {children}\n </Animated.View>\n </>\n );\n};\n\n/** Kick off the enter animation for a variant (spring for the value beat, timing otherwise). */\nconst startEnter = (\n value: Animated.Value,\n variant: CardHandoffVariant,\n reduced: boolean,\n delay: number,\n onDone?: () => void,\n): Animated.CompositeAnimation => {\n value.setValue(0);\n const anim =\n !reduced && variant === \"spring\"\n ? Animated.spring(value, {\n toValue: 1,\n delay: scaledMs(delay),\n damping: CARD_SPRING.damping,\n stiffness: CARD_SPRING.stiffness,\n mass: CARD_SPRING.mass,\n useNativeDriver: true,\n })\n : Animated.timing(value, {\n toValue: 1,\n delay: scaledMs(delay),\n duration: scaledMs(\n reduced\n ? REDUCED_FADE_MS\n : variant === \"fade\"\n ? LOADER_HANDOFF_FADE_MS\n : CARD_ENTER_MS,\n ),\n easing: easeWire,\n useNativeDriver: true,\n });\n anim.start(({ finished }) => {\n if (finished) onDone?.();\n });\n return anim;\n};\n\n/**\n * Transform track per variant/layer; reduce motion = opacity-only crossfade.\n *\n * `travel` mirrors the X axis: forward pushes the stack left (new arrives from the\n * right), backward pulls it right (new arrives from the left).\n */\nconst transformFor = (\n variant: CardHandoffVariant,\n t: Animated.Value,\n reduced: boolean,\n layer: \"enter\" | \"exit\",\n travel: CardHandoffDirection,\n) => {\n if (reduced || variant === \"fade\") return [];\n if (variant === \"spring\" && layer === \"enter\") {\n return [{ scale: t.interpolate({ inputRange: [0, 1], outputRange: [CARD_ENTER_SCALE_FROM, 1] }) }];\n }\n const slide = travel === \"forward\" ? CARD_SLIDE_PX : -CARD_SLIDE_PX;\n return [\n {\n translateX: t.interpolate({\n inputRange: [0, 1],\n outputRange: layer === \"enter\" ? [slide, 0] : [0, -slide],\n }),\n },\n ];\n};\n\nexport const CardHandoff = React.memo(_CardHandoff);\n\nconst styles = StyleSheet.create({\n fill: { flex: 1 },\n});\n","/**\n * ModalPaneHost — \"slide between panes inside a CenteredModal\", the shared primitive\n * behind BOTH the review gate's phase changes and the questionnaire's step changes.\n * One primitive, two call sites: the two gates must never drift apart on feel.\n *\n * It does NOT reimplement the slide. `CardHandoff` already owns the design's\n * \"cards hand off, they don't cut\" motion (exit left 180ms / enter right 220ms, 80ms\n * lead, reduce-motion crossfade), so this delegates to it in `layout=\"auto\"` mode and\n * adds the ONE thing a modal needs that a full-screen flow does not: a card that\n * resizes instead of snapping.\n *\n * WHY the height animation exists: the CenteredModal card is auto-height, so the moment\n * a taller/shorter pane mounts, the card would jump to its new height in a single frame\n * while the old pane is still sliding out — the exact \"jarring change\" the slide was\n * added to remove. Measuring the pane and easing the card to it keeps one continuous\n * surface.\n *\n * WHY it is the only non-native-driven value in the kit: height is a layout prop, so it\n * cannot ride the native driver. That is a deliberate, bounded exception — ONE property\n * on ONE small card, for ~220ms, while nothing is scrolling. The panes' own opacity and\n * translate (inside CardHandoff) stay on the native driver, and the two values live on\n * different views, so they never contend. Everything else in the kit stays transform/\n * opacity-only.\n *\n * Height is eased ONLY on a pane change. A multiline box growing as the user types also\n * fires onLayout, and animating that would make typing feel laggy — those resize\n * instantly. Reduce motion: the height snaps too (movement removed, crossfade kept).\n *\n * `header` is the STATIC slot: chrome that belongs to the card rather than to any one pane\n * (the questionnaire's back icon + step dots). It sits inside the measured container but\n * OUTSIDE the CardHandoff, which buys two things a caller cannot get by rendering it above\n * the host:\n * 1. It never slides, so it is never DUPLICATED mid-handoff. Anything inside CardHandoff\n * exists twice for ~300ms (the outgoing layer and the incoming one) — fine for a\n * question that is genuinely changing, wrong for chrome that is not. Two dot rows\n * cross-fading past each other is a bug, not a transition.\n * 2. Its size is part of the measured height, so dropping the header (form → thanks)\n * rides the same eased resize as the pane swap instead of snapping the card.\n */\nimport React, { useCallback, useEffect, useRef, useState } from \"react\";\nimport { Animated, Easing, StyleSheet, View, type LayoutChangeEvent } from \"react-native\";\n\nimport { CARD_ENTER_MS, WIRE_BEZIER, scaledMs } from \"../motion/motionSpec\";\nimport { useReducedMotion } from \"../motion/useReducedMotion\";\nimport { CardHandoff, type CardHandoffDirection } from \"./CardHandoff\";\n\nconst easeWire = Easing.bezier(...WIRE_BEZIER);\n\nexport type ModalPaneHostProps = {\n /** Identity of the pane on screen (phase name / step key). A change runs the handoff. */\n paneKey: string;\n /** Travel direction — \"backward\" mirrors the slide when the user steps back. */\n direction?: CardHandoffDirection;\n /**\n * Card-level chrome pinned above the sliding pane: measured with it, but never moved\n * and never duplicated by the handoff. Omit it (or pass null) and nothing renders —\n * dropping it between pane changes animates the card's height rather than snapping it.\n * The node owns its own bottom spacing; the host adds none (it has no theme).\n */\n header?: React.ReactNode;\n children: React.ReactNode;\n};\n\nconst _ModalPaneHost: React.FC<ModalPaneHostProps> = ({\n paneKey,\n direction = \"forward\",\n header,\n children,\n}) => {\n const reduced = useReducedMotion();\n const height = useRef(new Animated.Value(0)).current;\n /** Last height we settled on. null until the first real measurement lands. */\n const measured = useRef<number | null>(null);\n /** Drives the height style on only AFTER a real measurement, so nothing renders at 0. */\n const [sized, setSized] = useState(false);\n /** Set when the pane changed, cleared by the next measurement it causes. */\n const paneChanged = useRef(false);\n const running = useRef<Animated.CompositeAnimation | null>(null);\n\n // Runs on mount and on every pane change, always BEFORE the resulting onLayout (which\n // arrives from native after the commit) — so the next measurement knows why it fired.\n useEffect(() => {\n paneChanged.current = true;\n }, [paneKey]);\n\n useEffect(() => () => running.current?.stop(), []);\n\n const onLayout = useCallback(\n (event: LayoutChangeEvent) => {\n const next = Math.round(event.nativeEvent.layout.height);\n if (!next) return;\n const previous = measured.current;\n if (previous !== null && Math.abs(previous - next) < 1) return;\n\n const first = previous === null;\n const ease = !first && !reduced && paneChanged.current;\n paneChanged.current = false;\n measured.current = next;\n running.current?.stop();\n\n // First measurement, a reduce-motion user, or a plain content resize (typing):\n // take the new height immediately.\n if (!ease) {\n height.setValue(next);\n if (first) setSized(true);\n return;\n }\n\n const anim = Animated.timing(height, {\n toValue: next,\n duration: scaledMs(CARD_ENTER_MS),\n easing: easeWire,\n // height is layout, not transform — see the header note.\n useNativeDriver: false,\n });\n running.current = anim;\n anim.start();\n },\n [height, reduced],\n );\n\n return (\n <Animated.View style={sized ? { height, overflow: \"hidden\" } : undefined}>\n <View onLayout={onLayout} style={styles.pane}>\n {header}\n <CardHandoff transitionKey={paneKey} direction={direction} layout=\"auto\" variant=\"slide\">\n {children}\n </CardHandoff>\n </View>\n </Animated.View>\n );\n};\n\nexport const ModalPaneHost = React.memo(_ModalPaneHost);\nModalPaneHost.displayName = \"ModalPaneHost\";\n\nconst styles = StyleSheet.create({\n pane: { width: \"100%\" },\n});\n\nexport default ModalPaneHost;\n","/**\n * haptics.ts — the physical half of the kit's feedback layer: a tap you feel.\n *\n * `expo-haptics` is an OPTIONAL peer, loaded lazily via a guarded dynamic import so the\n * core kit never depends on it — the exact contract `reviews/storeReview.ts` uses for\n * `expo-store-review`. A host that has not installed it (or a platform without a taptic\n * engine) simply feels nothing: every call resolves to `false` and NOTHING throws. A\n * missed haptic must never break a gate.\n *\n * The module is deliberately fire-and-forget. Callers `void play(...)` from a press\n * handler and never await it, so a slow/absent native module can't delay a tap.\n *\n * Tones map to intent, not to a device API:\n * • \"selection\" — a value changed (picking a star).\n * • \"light\" — a light impact (moving to the next question).\n * • \"success\" — a task completed (the submit payoff, under the done check).\n *\n * NOT gated on reduce-motion: `prefers-reduced-motion` is a VESTIBULAR accommodation about\n * on-screen movement. Haptics are a non-visual channel and are often the accessibility\n * affordance a reduce-motion user relies on, so silencing them here would be the wrong\n * trade. Hosts that want no haptics leave `expo-haptics` uninstalled.\n */\n\n/** What a haptic is for, in product terms. Mapped to the native API inside `play`. */\nexport type HapticTone = \"selection\" | \"light\" | \"success\";\n\n/**\n * Play a haptic when the optional peer is available. Resolves `true` when a haptic was\n * actually requested, `false` when the peer is absent or the native call failed. NEVER\n * throws and never rejects — the result is advisory (tests/analytics), not control flow.\n */\nexport const play = async (tone: HapticTone): Promise<boolean> => {\n try {\n const mod = await import(\"expo-haptics\");\n if (!mod) return false;\n if (tone === \"selection\") {\n await mod.selectionAsync();\n return true;\n }\n if (tone === \"light\") {\n await mod.impactAsync(mod.ImpactFeedbackStyle.Light);\n return true;\n }\n await mod.notificationAsync(mod.NotificationFeedbackType.Success);\n return true;\n } catch {\n // Peer not installed, unsupported platform, or the native call failed — stay silent.\n return false;\n }\n};\n\n/**\n * Fire-and-forget `play`. The call site reads as the intent (\"a star was tapped\"), and a\n * rejected/absent native module can never surface as an unhandled rejection.\n */\nexport const playHaptic = (tone: HapticTone): void => {\n void play(tone);\n};\n","/**\n * mergeThemeOver — merge a partial theme over an ARBITRARY base theme.\n *\n * Sibling of `mergeTheme` (defaultTheme.ts), which merges over the DEFAULT theme only.\n * Gates need the other shape: a `theme` prop merged over whatever the host's active\n * ThemeContext currently is. That helper was copy-pasted verbatim into ReviewGate and\n * QuestionnaireGate; it lives here once instead, so a new theme section can never be\n * added to one gate and forgotten in the other.\n *\n * Two levels deep, matching `mergeTheme`: the theme is intentionally shallow, so each\n * known sub-object is merged and scalars overwrite.\n */\nimport type { OnboardingTheme } from \"./types\";\n\nexport const mergeThemeOver = (\n base: OnboardingTheme,\n override?: Partial<OnboardingTheme>,\n): OnboardingTheme => {\n if (!override) return base;\n return {\n colors: { ...base.colors, ...override.colors },\n fonts: { ...base.fonts, ...override.fonts },\n radius: { ...base.radius, ...override.radius },\n spacing: { ...base.spacing, ...override.spacing },\n button: { ...base.button, ...override.button },\n };\n};\n","/**\n * decision.ts — the pure decision logic for the review gate.\n *\n * `decideReview` is the AI seam, mirroring `selectTourSteps` exactly: ONE function that\n * turns an optional injected decision into the live verdict. No injected decision → the\n * local rules stand; a server decision present → it OVERRIDES them. Building the seam now\n * is the whole cost of being AI-ready: today the host passes local rules only; later the\n * Wire server computes `{fire, reason}` from the onboarding learnings and the host passes\n * it straight in — zero app changes.\n *\n * // v1 (local rules only)\n * const verdict = decideReview(evaluateGate(rules, signals));\n *\n * // AI on (server-provided decision wins)\n * const verdict = decideReview(evaluateGate(rules, signals), serverDecision);\n *\n * `evaluateGate`, `routeRating`, and `buildReviewSubmission` are pure so the gate logic\n * is verifiable in isolation (the kit has no test runner; correctness lives in these).\n */\nimport type {\n RatingRoute,\n ReviewDecision,\n ReviewSubmission,\n} from \"./types\";\n\n/**\n * The seam. `decision` absent → the local verdict stands; present → it overrides.\n * Same contract philosophy as `selectTourSteps(catalog, selection?)`.\n */\nexport const decideReview = (\n local: ReviewDecision,\n decision?: ReviewDecision,\n): ReviewDecision => decision ?? local;\n\n/** The signals the local rules evaluate against (read from injected sync storage + host). */\nexport interface GateSignals {\n /** Sessions observed on this device (incl. the current one). */\n sessions: number;\n /** Tracked app events observed (host-owned). */\n events: number;\n /** Epoch ms of the last time the gate was shown, or null if never. */\n lastShownAt: number | null;\n /** Whether the once-per-version gate is already satisfied. */\n seen: boolean;\n /** Current epoch ms (injected so the function stays pure). */\n now: number;\n}\n\n/** The resolved local rules (defaults applied). */\nexport interface GateRules {\n enabled: boolean;\n minSessions: number;\n minEvents: number;\n cooldownDays: number;\n oncePerVersion: boolean;\n}\n\n/** Fill a partial `ReviewConfig` with the rule defaults. */\nexport const resolveRules = (config: {\n enabled?: boolean;\n minSessions?: number;\n minEvents?: number;\n cooldownDays?: number;\n oncePerVersion?: boolean;\n}): GateRules => ({\n enabled: config.enabled ?? true,\n // FAIL-CLOSED on the first session (behavior change, 0.11.0). An unconfigured host with no\n // server decision must NOT prompt on the very first mount: `minSessions` defaults to 2 so the\n // gate needs at least a second session before the LOCAL rules can fire. This is the client-side\n // floor only — a server `decision` (from `fetchReviewDecision`) still OVERRIDES everything, and a\n // host that genuinely wants first-session prompting can set `minSessions: 1` (or `0`) explicitly.\n // Mirrors the 2026-07-16 incident: a first-session user, no server decision, got the prompt and\n // left 1 star. See `evaluateGate` (`min_sessions` reason) and the CHANGELOG.\n minSessions: config.minSessions ?? 2,\n minEvents: config.minEvents ?? 0,\n cooldownDays: config.cooldownDays ?? 0,\n oncePerVersion: config.oncePerVersion ?? true,\n});\n\nconst MS_PER_DAY = 86_400_000;\n\n/**\n * Deterministic v1 local decision. Order: disabled → once-gate → min-sessions →\n * min-events → cooldown → fire. Reasons mirror the server evaluator's vocabulary.\n */\nexport const evaluateGate = (rules: GateRules, s: GateSignals): ReviewDecision => {\n if (!rules.enabled) return { fire: false, reason: \"disabled\" };\n if (rules.oncePerVersion && s.seen) return { fire: false, reason: \"already_shown\" };\n if (s.sessions < rules.minSessions) return { fire: false, reason: \"min_sessions\" };\n if (s.events < rules.minEvents) return { fire: false, reason: \"min_events\" };\n if (rules.cooldownDays > 0 && s.lastShownAt != null) {\n const elapsedDays = (s.now - s.lastShownAt) / MS_PER_DAY;\n if (elapsedDays < rules.cooldownDays) return { fire: false, reason: \"cooldown\" };\n }\n return { fire: true, reason: \"local_rules_met\" };\n};\n\n/**\n * Route a rating. 5 stars → the native store review; 1-4 → the feedback form. The\n * sentiment question itself is neutral — this is the ONLY place 5 is treated specially,\n * never a button labeled \"rate us 5 stars\" (see the store-policy notes in the README).\n */\nexport const routeRating = (stars: number): RatingRoute =>\n stars >= 5 ? \"store\" : \"feedback\";\n\n/** Build the `POST /v1/reviews` body. Feedback text/contact belong ONLY here, never in events. */\nexport const buildReviewSubmission = (input: {\n stars: number;\n feedbackText?: string;\n suggestion?: string;\n contact?: string;\n sessionId?: string;\n source?: string;\n meta?: Record<string, unknown>;\n}): ReviewSubmission => {\n const body: ReviewSubmission = { stars: input.stars };\n const text = input.feedbackText?.trim();\n if (text) body.feedback_text = text;\n const suggestion = input.suggestion?.trim();\n if (suggestion) body.suggestion = suggestion;\n const contact = input.contact?.trim();\n if (contact) body.contact = contact;\n if (input.sessionId) body.session_id = input.sessionId;\n if (input.source) body.source = input.source;\n if (input.meta && Object.keys(input.meta).length > 0) body.meta = input.meta;\n return body;\n};\n","/**\n * feedbackForm — pure sizing helpers for the 1–4★ feedback form, kept out of the\n * React component so the \"≈4 lines tall, capped at 5\" rule is verifiable in\n * isolation (the reviews module's correctness lives in these pure functions, the\n * same convention as `decision.ts` / `presentation.ts`).\n *\n * Malik's ask: the feedback field should be a multiline box \"around 3 to 5 lines\"\n * tall, not a single-line input. We size it from the theme's body line-height so\n * it scales with the app's font config.\n */\nimport type { OnboardingFonts } from \"../theme/types\";\nimport { bodyStyle } from \"../theme/typography\";\n\n/** Lines the box shows at rest, and the cap it can grow to. */\nexport const FEEDBACK_MIN_LINES = 4;\nexport const FEEDBACK_MAX_LINES = 5;\n\nexport interface FeedbackInputHeights {\n /** Resting height — the box opens at ~4 lines so it reads as \"write a few words\". */\n minHeight: number;\n /** Growth cap — it never balloons past ~5 lines; scroll takes over beyond that. */\n maxHeight: number;\n}\n\n/**\n * Derive the multiline feedback box's min/max height from the theme's body\n * line-height. `minHeight = MIN_LINES × lineHeight`, capped at\n * `MAX_LINES × lineHeight`, plus the field's vertical padding (top + bottom) so\n * the requested number of text lines is actually visible inside the padding.\n */\nexport const feedbackInputHeights = (\n fonts: OnboardingFonts,\n verticalPadding = 0,\n): FeedbackInputHeights => {\n const lineHeight = bodyStyle(fonts).lineHeight ?? Math.round(fonts.baseSize * fonts.scale * 1.5);\n const pad = verticalPadding * 2;\n return {\n minHeight: lineHeight * FEEDBACK_MIN_LINES + pad,\n maxHeight: lineHeight * FEEDBACK_MAX_LINES + pad,\n };\n};\n","/**\n * centeredModalMotion - the pure entrance spec for the content-agnostic CenteredModal shell.\n *\n * WHY split out: the shell UI (CenteredModal) is RN and cannot run under the kit's node:test\n * runner, but the entrance math is pure and worth pinning. Keep the animation spec here so both\n * the component and the tests read one source, and so any module (reviews, questionnaire) can\n * reuse the shell without depending on another feature module.\n *\n * The centered popup uses RN's built-in `Animated` (native driver), the same idiom as\n * CompletionView/CardHandoff, so the shell stays dependency-free (no reanimated).\n *\n * NOTE: `reviews/presentation.ts` re-exports these under their original names\n * (`REVIEW_MODAL_ANIM`, `entranceFrame`) for back-compat with the reviews public API.\n */\n\n/**\n * The entrance spec for the centered popup. Values chosen to match the boilerplate's\n * `animated-modal` feel (a springy zoom-in) with the kit's own native-driver idiom:\n * • backdrop fades 0 -> `backdropOpacity`\n * • card springs from `scaleFrom` -> 1 and fades 0 -> 1\n */\nexport const CENTERED_MODAL_ANIM = {\n /** Spring config for the card scale/opacity entrance (mirrors CompletionView's pop). */\n enterSpring: { friction: 7, tension: 80 },\n /** Exit timing in ms (fade + shrink back out before unmount). */\n exitMs: 160,\n /** Card scale at rest (progress 0); grows to 1 at progress 1. */\n scaleFrom: 0.9,\n /** Peak dim of the backdrop at progress 1. */\n backdropOpacity: 0.55,\n} as const;\n\n/** A single frame of the entrance, for a progress value in [0..1] (clamped). Pure, testable. */\nexport const centeredModalFrame = (\n progress: number,\n backdropMax: number = CENTERED_MODAL_ANIM.backdropOpacity,\n): { cardOpacity: number; cardScale: number; backdropOpacity: number } => {\n const p = progress < 0 ? 0 : progress > 1 ? 1 : progress;\n const { scaleFrom } = CENTERED_MODAL_ANIM;\n return {\n cardOpacity: p,\n cardScale: scaleFrom + (1 - scaleFrom) * p,\n backdropOpacity: backdropMax * p,\n };\n};\n","/**\n * presentation.ts — the pure presentation + entrance-animation logic for the review gate.\n *\n * WHY split out: the gate UI (ReviewGate/ReviewModal) is RN and can't run under the kit's\n * node:test runner, but the presentation DEFAULT and the entrance math are pure and worth\n * pinning. Keep the animation spec here so both the component and the tests read one source.\n *\n * The centered popup uses RN's built-in `Animated` (native driver) — the same idiom as\n * CompletionView/CardHandoff — so the reviews module stays dependency-free (no reanimated).\n */\nimport type { ReviewPresentation } from \"./types\";\n\n/** Centered popup by default (Malik's ruling: the bottom-anchored inline card read as wrong). */\nexport const DEFAULT_PRESENTATION: ReviewPresentation = \"modal\";\n\n/** Resolve the presentation prop → the effective mode. Absent → the centered modal. */\nexport const resolvePresentation = (p?: ReviewPresentation): ReviewPresentation =>\n p ?? DEFAULT_PRESENTATION;\n\n/**\n * The centered-popup entrance spec + pure entrance-frame math now live in the neutral\n * `../motion/centeredModalMotion` (the shell was extracted to `components/CenteredModal` so the\n * questionnaire module can reuse it without importing reviews/). Re-exported here under their\n * original names so the reviews public API (reviews/index.ts) and presentation.test.ts stay valid.\n */\nexport {\n CENTERED_MODAL_ANIM as REVIEW_MODAL_ANIM,\n centeredModalFrame as entranceFrame,\n} from \"../motion/centeredModalMotion\";\n","/**\n * CenteredModal - the content-agnostic centered-popup shell.\n *\n * Renders its children as an alert-style card in the MIDDLE of the screen, over a dimmed\n * backdrop, with a spring/fade entrance. It knows nothing about what it presents: the reviews\n * gate uses it (via the `ReviewModal` back-compat re-export) and the questionnaire module reuses\n * the same shell, neither depends on the other.\n *\n * Adapted from the mobile-launcher boilerplate's `animated-modal` (transparent RN Modal +\n * centered container + backdrop-tap dismiss + content stop-propagation + springy zoom-in), but\n * reimplemented on RN's built-in `Animated` (native driver) so the shell stays dependency-free:\n * the boilerplate uses react-native-reanimated, which the kit does not.\n *\n * Dismissal (backdrop tap / Android back / the imperative `close()` handle) animates the card\n * out, then fires `onDismiss`. It never auto-closes on resolve: the host keeps the card showing\n * whatever payoff it wants (a thank-you, a summary) until the user taps the backdrop to close,\n * exactly like CompletionView.\n */\nimport React, {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from \"react\";\nimport { Animated, BackHandler, Easing, Modal, Pressable, StyleSheet } from \"react-native\";\nimport { SafeAreaView } from \"react-native-safe-area-context\";\n\nimport type { OnboardingTheme } from \"../theme/types\";\nimport { REDUCED_FADE_MS } from \"../motion/motionSpec\";\nimport { useReducedMotion } from \"../motion/useReducedMotion\";\nimport { CENTERED_MODAL_ANIM } from \"../motion/centeredModalMotion\";\n\n/** Imperative handle so an in-card \"Not now\"/\"Skip\" link can trigger the exit animation. */\nexport type CenteredModalHandle = { close: () => void };\n\nexport type CenteredModalProps = {\n /** The (already merged) active theme - card surface/radius/spacing come from here. */\n theme: OnboardingTheme;\n /** Whether a backdrop tap / back press dismisses. Default true (never a dead end). */\n dismissible?: boolean;\n /** Fired once, AFTER the exit animation, on any dismiss. Wire to the host's resolve. */\n onDismiss: () => void;\n children: React.ReactNode;\n};\n\nconst _CenteredModal = forwardRef<CenteredModalHandle, CenteredModalProps>(\n ({ theme, dismissible = true, onDismiss, children }, ref) => {\n const progress = useRef(new Animated.Value(0)).current;\n const [open, setOpen] = useState(true);\n const closingRef = useRef(false);\n const reduced = useReducedMotion();\n\n // Entrance: spring the card in (scale + fade); the backdrop fades up in lock-step.\n // Reduce motion: a short fade to the settled card, no spring/scale.\n useEffect(() => {\n const anim = reduced\n ? Animated.timing(progress, {\n toValue: 1,\n duration: REDUCED_FADE_MS,\n useNativeDriver: true,\n })\n : Animated.spring(progress, {\n toValue: 1,\n useNativeDriver: true,\n friction: CENTERED_MODAL_ANIM.enterSpring.friction,\n tension: CENTERED_MODAL_ANIM.enterSpring.tension,\n });\n anim.start();\n return () => anim.stop();\n }, [progress, reduced]);\n\n const close = useCallback(() => {\n if (closingRef.current) return;\n closingRef.current = true;\n Animated.timing(progress, {\n toValue: 0,\n duration: reduced ? REDUCED_FADE_MS : CENTERED_MODAL_ANIM.exitMs,\n easing: Easing.in(Easing.cubic),\n useNativeDriver: true,\n }).start(() => {\n setOpen(false);\n onDismiss();\n });\n }, [progress, onDismiss, reduced]);\n\n useImperativeHandle(ref, () => ({ close }), [close]);\n\n // Android hardware back -> dismiss (when allowed). Always consume so it never pops a route.\n useEffect(() => {\n const sub = BackHandler.addEventListener(\"hardwareBackPress\", () => {\n if (dismissible) close();\n return true;\n });\n return () => sub.remove();\n }, [dismissible, close]);\n\n const onBackdrop = useCallback(() => {\n if (dismissible) close();\n }, [dismissible, close]);\n\n const backdropOpacity = progress.interpolate({\n inputRange: [0, 1],\n outputRange: [0, CENTERED_MODAL_ANIM.backdropOpacity],\n });\n // Reduce motion: the card fades in already settled (no zoom).\n const cardScale = reduced\n ? 1\n : progress.interpolate({\n inputRange: [0, 1],\n outputRange: [CENTERED_MODAL_ANIM.scaleFrom, 1],\n });\n\n return (\n <Modal\n visible={open}\n transparent\n animationType=\"none\"\n statusBarTranslucent\n onRequestClose={dismissible ? close : undefined}\n >\n {/* Dimmed backdrop (fades with the entrance). */}\n <Animated.View\n style={[styles.backdrop, { opacity: backdropOpacity }]}\n pointerEvents=\"none\"\n />\n\n {/* Full-screen tap target: a tap outside the card dismisses. */}\n <Pressable\n style={styles.fill}\n onPress={onBackdrop}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Dismiss\"\n >\n <SafeAreaView style={styles.center} pointerEvents=\"box-none\">\n {/* Inner Pressable swallows taps so the card itself never dismisses. */}\n <Pressable onPress={() => {}} style={styles.cardWrap}>\n <Animated.View\n accessibilityViewIsModal\n accessibilityRole=\"alert\"\n style={[\n styles.card,\n {\n backgroundColor: theme.colors.surface,\n borderRadius: theme.radius.lg,\n padding: theme.spacing.lg,\n gap: theme.spacing.md,\n opacity: progress,\n transform: [{ scale: cardScale }],\n },\n ]}\n >\n {children}\n </Animated.View>\n </Pressable>\n </SafeAreaView>\n </Pressable>\n </Modal>\n );\n },\n);\n\n_CenteredModal.displayName = \"CenteredModal\";\n\nexport const CenteredModal = React.memo(_CenteredModal);\n\nconst styles = StyleSheet.create({\n fill: { flex: 1 },\n backdrop: { position: \"absolute\", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: \"#000\" },\n center: { flex: 1, alignItems: \"center\", justifyContent: \"center\", padding: 24 },\n cardWrap: { width: \"100%\", maxWidth: 420 },\n card: {\n width: \"100%\",\n // Soft elevation so the popup reads as lifted above the app in both themes.\n shadowColor: \"#000\",\n shadowOffset: { width: 0, height: 8 },\n shadowOpacity: 0.28,\n shadowRadius: 24,\n elevation: 12,\n },\n});\n\nexport default CenteredModal;\n","/**\n * storeReview.ts — the 5-star route: ask for a native in-app review, or fall back to\n * opening the store listing.\n *\n * `expo-store-review` is an OPTIONAL peer, loaded lazily via a guarded dynamic import so\n * the core kit never depends on it. When present + available, we call\n * `StoreReview.requestReview()` (the OS decides whether to actually show the dialog — it is\n * quota'd, ~3/year on iOS, and NEVER guaranteed). When absent/unavailable, we open the\n * configured store URL as a graceful fallback. Both paths are best-effort and never throw.\n *\n * Store-policy: call this ONLY on the honest 5-star path, from a NEUTRAL prompt — never a\n * button labeled \"rate us 5 stars\". See the README store-policy notes.\n */\nimport { Linking, Platform } from \"react-native\";\n\nimport type { ReviewStoreConfig } from \"./types\";\n\n/** The store deep-link for the current platform, or null if none is configured. */\nexport const storeUrl = (store: ReviewStoreConfig | undefined): string | null => {\n if (!store) return null;\n if (Platform.OS === \"ios\" && store.iosAppId) {\n return `itms-apps://itunes.apple.com/app/id${store.iosAppId}?action=write-review`;\n }\n if (Platform.OS === \"android\" && store.androidPackage) {\n return `market://details?id=${store.androidPackage}`;\n }\n return store.storeUrl ?? null;\n};\n\n/** Open the configured store listing. Best-effort; resolves false if there was nothing to open. */\nexport const openStoreListing = async (\n store: ReviewStoreConfig | undefined,\n): Promise<boolean> => {\n const url = storeUrl(store);\n if (!url) return false;\n try {\n await Linking.openURL(url);\n return true;\n } catch {\n return false;\n }\n};\n\n/**\n * Request the native in-app review when the optional peer is available; otherwise open the\n * store listing. Returns the path taken so the caller can fire the right analytics.\n */\nexport const requestStoreReview = async (\n store?: ReviewStoreConfig,\n): Promise<\"native\" | \"store_url\" | \"none\"> => {\n try {\n const mod = await import(\"expo-store-review\");\n if (mod && (await mod.isAvailableAsync())) {\n await mod.requestReview();\n return \"native\";\n }\n } catch {\n // Peer not installed or the native call failed — fall through to the URL fallback.\n }\n const opened = await openStoreListing(store);\n return opened ? \"store_url\" : \"none\";\n};\n","/**\n * reportClientEvent — forward DEVICE-ONLY onboarding events to the Wire AI analytics\n * backend (`POST {serverUrl}/v1/events`), completing the funnel for events the server\n * can't observe on its own.\n *\n * The backend already records the server-observable funnel during the A2A flow\n * (`session_started`, `screen_shown`, `answer_submitted`, `completed`, `llm_fallback`,\n * and even `screen_skipped` — it derives that from the kit's skip sentinel). The one\n * event no server request can capture is `dropped`: the user closing the app / unmounting\n * the flow without finishing. That's what this reporter is for.\n *\n * Contract (server: routers/onboarding.py → analytics/events.py):\n * POST {serverUrl}/v1/events\n * Authorization: Bearer {apiKey}\n * { \"events\": [ { event_type, session_id, screen_index?, component?, question_key?,\n * latency_ms?, meta?, device?, user_context? } ] }\n * The server fills `app_id` + `environment` from the resolving key (never send app_id),\n * and silently skips malformed events — one bad payload never fails the batch.\n *\n * ⚠️ Correlation: `session_id` MUST equal the A2A `contextId` the server uses to key the\n * server-side events, or the funnel report (which groups by `session_id`) treats this as a\n * phantom session. See `makeSessionId` + WireOnboarding for how the kit seeds it.\n *\n * Fire-and-forget: this never throws into the UI and never awaits — analytics must never\n * be able to break onboarding.\n */\nimport type { DeviceContext } from \"../device/deviceContext\";\n\n/** Event types a CLIENT may report. The rest of the funnel is server-side; sending those\n * here would double-count. `screen_skipped` is included for completeness, but the kit does\n * NOT emit it — the backend already derives it from the skip sentinel (see OnboardingFlow).\n * `client_fallback` is emitted by the kit when the AI flow degrades to the static fallback,\n * so the dashboard's fallback-rate counts the whole-flow case (distinct from the server's\n * per-turn `llm_fallback`). The server back-fills a `session_started` for it if unseen.\n * This is the SINGLE fallback signal — hosts must NOT also report their own.\n * `identify` binds the host's opaque `user_id` to this `session_id` (late binding — the user\n * registered during/after onboarding). It carries no funnel weight; the server maps the\n * session to the user and back-fills a `session_started` if it never saw the session.\n * `app_event` is the host's own in-app event (the `createAnalytics` façade's `track`/`screen`\n * route through the offline queue as first-class `ClientEvent`s). The server already ingests\n * it — `reportAppEvent` (reviews/transport) has fired `event_type='app_event'` all along; this\n * widening just lets the same shape flow through the durable queue instead of a blind POST. */\nexport type ClientEventType =\n | \"screen_skipped\"\n | \"dropped\"\n | \"client_fallback\"\n | \"identify\"\n | \"app_event\";\n\n/** One client-reported event. Mirrors the server's `OnboardingEvent` (client-settable fields). */\nexport type ClientEvent = {\n event_type: ClientEventType;\n /** Must match the server-side A2A contextId for this onboarding (see makeSessionId). */\n session_id: string;\n /** 0-based index of the screen the event refers to (matches server `screen_shown`). */\n screen_index?: number;\n component?: string;\n question_key?: string;\n latency_ms?: number;\n /** JSON-stringified extras; the server stores it verbatim. */\n meta?: string;\n /**\n * Privacy-label-neutral device snapshot (platform / form factor / locale / host appVersion).\n * Sent as an object; the server sanitizes + persists it and derives a coarse country. Old\n * servers ignore this unknown field — fully backward compatible. See device/deviceContext.ts.\n */\n device?: DeviceContext;\n /**\n * Host-injected, non-PII context (signup method, referral, plan, hashed user id). Old servers\n * ignore it. MUST NOT contain PII like raw emails — see the README `userContext` section.\n */\n user_context?: Record<string, string | number | boolean>;\n /**\n * The host's OPAQUE PSEUDONYMOUS user id (their internal id, NOT an email/name). Required on\n * `identify`, optional (rides along) on other events. Trimmed + capped at 128 chars host-side.\n * Lets the backend reconcile onboarding sessions to real users. Old servers ignore it.\n */\n user_id?: string;\n /**\n * Client-stamped timestamp of when the event was ENQUEUED on the device. Optional and ADDITIVE.\n *\n * TWO REPRESENTATIONS, on purpose:\n * - INTERNAL (epoch-ms `number`): what the offline queue stamps at enqueue time (see\n * `createEventQueue`), so two otherwise byte-identical events fired seconds apart (a genuine\n * repeat, e.g. the user taps \"share\" twice) are NOT collapsed by the queue's identical-JSON\n * de-dup — while two truly simultaneous re-enqueues of the same instant (a redundant\n * re-render) still share a `ts` and collapse. The de-dup signature depends on this number.\n * - WIRE (ISO8601 UTC `string`): what actually leaves the device. {@link buildEventsRequest}\n * converts the number on its way out, because the server declares `ts: str | None` and\n * pydantic v2 does NOT coerce a number into it — a numeric `ts` made the server answer HTTP\n * 200 with `{written: 0, skipped: N, errors: [{field: \"ts\", reason: \"validation_error\"}]}`,\n * silently discarding EVERY `app_event` through 0.13.0.\n *\n * A caller-set ISO string is passed through as-is. Never a wall-clock the server trusts (it\n * derives its own receive time); an old/strict server that does not model it ignores the field.\n */\n ts?: number | string;\n};\n\n/** Where to POST. Derived from `WireOnboardingConfig` (`serverUrl` + `apiKey`). */\nexport type ClientEventTarget = {\n /** Base server URL (same as `WireOnboardingConfig.serverUrl`); `/v1/events` is appended. */\n serverUrl: string;\n /** Tenant API key; sent as `Authorization: Bearer`. */\n apiKey: string;\n};\n\n/**\n * A unique-per-onboarding session id. Used both as the client event `session_id` AND as the\n * seed the kit forwards to the backend so the SERVER adopts it as the A2A `contextId` — making\n * client and server agree (see WireOnboarding + the SDK-correlation note in the kit docs).\n * No crypto dependency: timestamp + random is collision-safe for a single device's onboarding.\n */\nexport const makeSessionId = (): string =>\n `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;\n\n/**\n * Serialize the internal epoch-ms `ts` to the ISO8601 UTC string the WIRE requires.\n *\n * The server's event model declares `ts: str | None` and pydantic v2 does NOT coerce int → str, so\n * a numeric `ts` fails per-event validation: the endpoint still answers HTTP 200, but with\n * `{written: 0, skipped: N, errors: [{index, reason: \"validation_error\", field: \"ts\"}]}` — every\n * `app_event` from `track()`/`screen()` silently discarded behind a green response.\n *\n * WHY HERE and not at the queue's `stamp()`: this is the single choke point all FIVE send paths go\n * through (offline queue, fire-and-forget, awaitable, review transport, session-start). Converting\n * here leaves the queue's numeric `ts` — and therefore its identical-JSON de-dup signature — exactly\n * as it was, and it also converts the persisted 0.13.0 backlogs (which hold a numeric `ts`) on their\n * way out.\n *\n * ⚠️ NOT the same list as the ack-consumer list in {@link warnOnSkippedEvents}, which is FOUR. The\n * review transport builds its request here but fires and forgets without reading the response, so\n * it rides this conversion and is absent from that one. Count the call sites before editing either.\n *\n * NEVER mutates the caller's event: an event that needs a change is copied. A string `ts` (a\n * caller-set ISO stamp) and an absent `ts` pass through untouched. A non-finite (`NaN`/`Infinity`)\n * or out-of-range number — the latter makes `toISOString` throw — drops the `ts` field from the\n * copy rather than killing the whole batch.\n */\nconst toWireEvents = (events: ClientEvent[]): ClientEvent[] =>\n events.map((event) => {\n if (typeof event.ts !== \"number\") return event;\n const { ts, ...rest } = event;\n if (!Number.isFinite(ts)) return rest;\n try {\n return { ...rest, ts: new Date(ts).toISOString() };\n } catch {\n // Out-of-range epoch-ms — send the event WITHOUT a ts rather than lose the batch.\n return rest;\n }\n });\n\n/**\n * The ONE place the `/v1/events` POST is described (url + method + headers + body). Both the\n * fire-and-forget {@link reportClientEvents} and the awaitable {@link reportClientEventsAwait}\n * build their request here so there is a SINGLE definition of the events transport — no second\n * copy of the endpoint path, headers, or envelope shape to drift. Returns `null` when there is\n * nothing to send (no target / no events) or serialization throws, so callers just bail.\n *\n * It is also where the internal epoch-ms `ts` becomes the wire's ISO8601 string — see\n * {@link toWireEvents} for why the conversion belongs at this choke point.\n *\n * `options.dryRun` asks the server to VALIDATE the batch and write nothing, which is what the wire\n * doctor's round-trip check needs: a real POST, through this one builder, that cannot pollute a\n * tenant's funnel. The `dry_run` key is added ONLY on an explicit `true`, so every production\n * caller (which passes no options at all) still serializes the exact same bytes it did before the\n * parameter existed. That byte-identity is asserted in `buildEventsRequest.test.ts`; every shipped\n * send path rides this envelope, so a diagnostic is not allowed to change it for them.\n */\nexport const buildEventsRequest = (\n target: { serverUrl: string; apiKey?: string } | undefined,\n events: ClientEvent[],\n options?: { dryRun?: boolean },\n): { url: string; init: RequestInit } | null => {\n if (!target?.serverUrl || events.length === 0) return null;\n try {\n const url = `${target.serverUrl.replace(/\\/$/, \"\")}/v1/events`;\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;\n // The key is CONDITIONALLY assigned rather than set to a falsy default: an absent property and\n // a `dry_run: false` property do not serialize the same, and only absence keeps the body byte\n // identical for the production call sites.\n const envelope: { events: ClientEvent[]; dry_run?: true } = { events: toWireEvents(events) };\n if (options?.dryRun === true) envelope.dry_run = true;\n const body = JSON.stringify(envelope);\n return { url, init: { method: \"POST\", headers, body } };\n } catch {\n // URL construction or JSON serialization failed — nothing to send.\n return null;\n }\n};\n\n/** What the `/v1/events` endpoint says in its 200 body. The deployed server DOES send `errors[]`,\n * each entry carrying `{index, reason, field}` — `field` names the property that failed validation\n * (it is what identified the `ts` rejection), so it is folded into the reason string here. Still\n * read defensively: an older server sends no `errors` at all. */\nexport type EventsAck = { written?: number; skipped: number; reasons: string[] };\n\n/**\n * Read the `/v1/events` ACK body. Resolves `undefined` when there is nothing readable — no `.json`\n * (an old server, a bare test mock), an already-consumed body, or a hostile response object. NEVER\n * throws, and NEVER dev-gated: the `skipped` count is a RETURN VALUE for the awaitable path, not only\n * a warning, so it has to be read in production too.\n *\n * ⚠️ The body can be read exactly once, so a caller that both decides on `skipped` AND warns must do\n * both from ONE call to this.\n *\n * Exported from this MODULE (not from the `./analytics` barrel) so the wire doctor can read an ack\n * with the same reader the send paths use. A second ack reader would be a second thing to drift,\n * and the drift it exists to catch is exactly the kind that hides behind a 200.\n */\nexport const readEventsAck = async (res: unknown): Promise<EventsAck | undefined> => {\n try {\n const json = (res as { json?: () => Promise<unknown> } | null | undefined)?.json;\n if (typeof json !== \"function\") return undefined;\n const body = (await Promise.resolve(json.call(res))) as\n | { written?: unknown; skipped?: unknown; errors?: unknown }\n | null\n | undefined;\n const skipped = body?.skipped;\n if (typeof skipped !== \"number\" || !Number.isFinite(skipped)) return undefined;\n const reasons = Array.isArray(body?.errors)\n ? body.errors\n .map((e) => {\n const entry = e as { reason?: unknown; field?: unknown } | null;\n const reason = entry?.reason;\n if (typeof reason !== \"string\") return undefined;\n // `field` is the whole point of a validation error — a bare \"validation_error\" sends the\n // reader hunting; \"validation_error (field: ts)\" names the property the server refused.\n const field = entry?.field;\n return typeof field === \"string\" && field.length > 0\n ? `${reason} (field: ${field})`\n : reason;\n })\n .filter((r): r is string => typeof r === \"string\")\n : [];\n return { written: typeof body?.written === \"number\" ? body.written : undefined, skipped, reasons };\n } catch {\n // Unreadable / already-consumed body / hostile object — best-effort, treat as \"no ack\".\n return undefined;\n }\n};\n\n/**\n * The DISCARDED warning text, built from what the server ACTUALLY said.\n *\n * It used to staple a cause onto a bare integer — \"the usual cause is a missing or empty session_id\".\n * Since `ensureCurrentSessionId` shipped, no kit path can emit an event without a `session_id`, so\n * that is now the LEAST likely explanation, and naming it sent every reader looking in the one place\n * the problem is not. The endpoint folds four real failures and two idempotent no-ops into one\n * integer, so unless the server volunteers reasons, the honest thing to report is the count and where\n * the reason lives.\n */\nconst describeDiscarded = (ack: EventsAck): string =>\n `[wireai] the server ACCEPTED the /v1/events POST but DISCARDED ${ack.skipped} event(s) ` +\n \"(skipped in the response body) — they are gone, not retried. The server reported: \" +\n `skipped=${ack.skipped}${ack.written !== undefined ? `, written=${ack.written}` : \"\"}` +\n (ack.reasons.length > 0\n ? `, reasons: ${ack.reasons.join(\", \")}.`\n : \". It gave no reason (the endpoint folds every rejection into one count), so check the \" +\n \"server's ingest log for this request rather than guessing.\");\n\n/** RN sets this global; absent under node/SSR. Read defensively inside {@link warnOnSkippedEvents}. */\ndeclare const __DEV__: boolean | undefined;\n\n/**\n * Read the `/v1/events` ACK body and warn (dev builds only) when the server DISCARDED events.\n *\n * The endpoint answers HTTP **200** with `{ ok, written, skipped }` — an event it refuses is counted\n * in `skipped`, never surfaced in the status code. Every send path here reads `res.ok` alone, so a\n * whole batch can evaporate behind a green response. As of 0.13.0 ALL FOUR send paths consume the ack:\n * the offline queue, session-start, the fire-and-forget POST, and the awaitable one (which also acts\n * on it — see {@link reportClientEventsAwait}).\n *\n * LOG ONLY: returns immediately, never throws, and never influences retry / dequeue / return values.\n * A response with no usable `.json` (an old server, a test mock) is silently ignored.\n *\n * DEV-GATED FIRST: the `__DEV__` check is the FIRST statement, before the body is even looked at.\n * The check used to sit inside the `.then`, so a release build parsed the JSON of every\n * persistent-path POST to build a warning no one would ever read. Nothing here runs in production.\n */\nexport const warnOnSkippedEvents = (res: unknown): void => {\n if (typeof __DEV__ === \"undefined\" || !__DEV__) return;\n if (typeof console === \"undefined\" || !console.warn) return;\n void readEventsAck(res).then((ack) => {\n if (!ack || ack.skipped <= 0) return;\n console.warn(describeDiscarded(ack));\n });\n};\n\n/**\n * POST one or more client events, fire-and-forget. A missing/invalid target, a build error,\n * a missing `fetch`, or a network failure is swallowed — the call returns immediately and the\n * request (if any) runs in the background.\n */\nexport const reportClientEvents = (\n target: ClientEventTarget | undefined,\n events: ClientEvent[],\n): void => {\n try {\n const req = buildEventsRequest(target, events);\n if (!req) return;\n void fetch(req.url, req.init)\n .then((res) => {\n // Still fire-and-forget — nothing is retried, nothing is returned. But a batch that\n // evaporated behind a 200 is exactly what this path used to make invisible, so in dev it\n // now says so, like the queue and session-start paths already did.\n warnOnSkippedEvents(res);\n })\n .catch(() => {\n // Network/transport error — analytics is best-effort, swallow.\n });\n } catch {\n // A missing `fetch` — swallow.\n }\n};\n\n/** Convenience single-event wrapper around {@link reportClientEvents}. */\nexport const reportClientEvent = (\n target: ClientEventTarget | undefined,\n event: ClientEvent,\n): void => reportClientEvents(target, [event]);\n\n/**\n * AWAITABLE sibling of {@link reportClientEvents}: POST one or more client events through the SAME\n * `/v1/events` path, but resolve only once the server has RESPONDED — so a decision re-fetch fired\n * immediately after is guaranteed to see the event in the session stream (this is the guarantee\n * `wire.track` needs before it triggers decision revalidation). Never throws: a missing/invalid\n * target, a missing `fetch`, a network error, or a non-2xx status all resolve to `false`.\n *\n * ⚠️ 0.13.0: a 2xx is NO LONGER SUFFICIENT. The endpoint answers HTTP 200 with `{ ok, written,\n * skipped }` and counts an event it refuses in `skipped`, so this used to resolve `true` for an event\n * the server had thrown away — and `wire.track` then bumped decision revalidation, making every\n * subscribed gate re-fetch against a stream the action never entered. It now reads the ack and\n * resolves `false` when the server reports a positive `skipped`.\n *\n * BACKWARD COMPATIBLE BY CONSTRUCTION: only an explicit positive `skipped` demotes a 200. An old\n * server that sends no such field, a body that cannot be parsed, or a response with no `.json` at all\n * resolves `true` exactly as before — the change can produce no false negatives.\n */\nexport const reportClientEventsAwait = async (\n target: ClientEventTarget | undefined,\n events: ClientEvent[],\n): Promise<boolean> => {\n try {\n const req = buildEventsRequest(target, events);\n if (!req) return false;\n const res = await fetch(req.url, req.init);\n if (!res || !res.ok) return false;\n // ONE body read, used for both the verdict and the dev warning — it can only be read once.\n const ack = await readEventsAck(res);\n if (!ack || ack.skipped <= 0) return true;\n if (typeof __DEV__ !== \"undefined\" && __DEV__ && typeof console !== \"undefined\" && console.warn) {\n console.warn(describeDiscarded(ack));\n }\n return false;\n } catch {\n // Unreachable / missing-fetch / network — best-effort, report failure.\n return false;\n }\n};\n\n/** Convenience single-event wrapper around {@link reportClientEventsAwait}. */\nexport const reportClientEventAwait = (\n target: ClientEventTarget | undefined,\n event: ClientEvent,\n): Promise<boolean> => reportClientEventsAwait(target, [event]);\n","/**\n * warnInDev — the ONE developer-warning primitive.\n *\n * The kit warns a developer in four places (the onboarding host, the env-config helper, the analytics\n * façade, the current-session registry) and until 0.13.0 each carried its own copy of the same five\n * lines. Three were byte-identical; the fourth differed only by returning whether it actually warned.\n * Four copies of a guard is four chances for one of them to drift out of the `__DEV__` gate, which is\n * the failure that matters: a warning that runs in production is a string built for nobody.\n *\n * ON THE TREE-SHAKING NOTE THIS REPLACES: `analytics/currentSession` used to justify its copy as\n * keeping the module import-free for the tree-shaken `./analytics` bundle. That rationale had already\n * lapsed — the module imports `makeSessionId` from `./reportClientEvent` — and this module has no\n * imports of its own, so it adds one leaf to the graph and nothing to the bundle. The `treeShake`\n * canary still holds the real guarantee (the analytics graph reaches no UI module).\n */\n\n/** RN sets this global; absent under node/SSR. Read defensively, never assumed. */\ndeclare const __DEV__: boolean | undefined;\n\n/**\n * Emit a one-line developer warning, but ONLY in a dev build (RN `__DEV__`). No-op in prod/tests.\n *\n * Returns whether it ACTUALLY warned, which a caller holding a once-flag must honour: marking\n * \"already warned\" after a no-op would burn the single warning in production, and the one dev build\n * that needed it would then run silent.\n */\nexport const warnInDev = (message: string): boolean => {\n if (typeof __DEV__ !== \"undefined\" && __DEV__ && typeof console !== \"undefined\" && console.warn) {\n console.warn(message);\n return true;\n }\n return false;\n};\n","/**\n * currentSession — a tiny registry of the CURRENT per-open `session_id`.\n *\n * WHY it exists (kills the phantom-session): the per-open emitters (`reportSessionStart` and the\n * `useSessionStart` / `useLifecycleEvents` hooks) mint a fresh `session_id` for each app-open and\n * post `app.session_started` with it — so the SERVER knows that id. But other client paths\n * (`identify`, host `app_event`s through the analytics façade) used to reference a DIFFERENT id\n * (a frozen per-instance id), which the server had never seen, so it back-filled a synthetic\n * `session_started` — inflating session counts (the \"phantom-session\" bug).\n *\n * This registry is the single seam that lets those paths reuse the LIVE per-open session id the\n * server already ingested. `reportSessionStart` writes the current id here on every open; the façade\n * reads it so `identify`/app-events correlate to the real session instead of minting a phantom.\n *\n * ── WHY A globalThis SLOT, NOT A PLAIN MODULE VARIABLE ────────────────────────────────────────\n * This module is exported from TWO package entry points — the main `.` bundle (`src/index.ts`) and\n * the `./analytics` subpath (`src/analytics/index.ts`). Under `dist` resolution (node `import`/\n * `require`, which is how tests, SSR and some tooling load the kit) tsup inlines a SEPARATE copy of\n * this module into each bundle, so a plain `let` would give the SETTER (reached via `.` →\n * `reportSessionStart`) and the READER (reached via `./analytics` → façade / `userIdentity`) TWO\n * different variables: the reader would see `undefined` even after an open set the id, and gating\n * would fire under a null session id. On-device this was masked only because Metro's `react-native`\n * export condition resolves both subpaths back to this one `src/` file (a single instance) — a\n * bundler accident, not a guarantee.\n *\n * The bundler-agnostic fix: keep the ONE live value in a well-known `globalThis` slot keyed by a\n * `Symbol.for(...)`. `Symbol.for` uses the runtime-global symbol registry, so every inlined copy of\n * this module resolves the SAME symbol and reads/writes the SAME slot — one identity no matter how\n * many times the module is duplicated across bundles. `globalThis` is present and identical in\n * Hermes/React Native, Node and SSR (we never touch `window`), so this is safe on every host.\n *\n * PROCESS-LOCAL, NOT PERSISTED: the slot lives on the runtime global, so it tracks the CURRENT\n * process's open and a fresh open overwrites it. There is no cross-launch state.\n * `resetCurrentSessionId` clears the slot so a unit test starts from a clean registry.\n *\n * ── WHY `ensureCurrentSessionId` EXISTS (the silent-drop contract) ─────────────────────────────\n * The server's event model declares `session_id: str = Field(min_length=1)` — REQUIRED, non-empty.\n * `POST /v1/events` validates each event inside a try/except that increments a `skipped` counter and\n * still returns HTTP 200. So an event posted without a `session_id` is accepted by the wire and\n * DISCARDED by the server, and a fire-and-forget client can never learn it happened. That is the\n * worst of both: no error, no data. Screen tracking in a host that never mounted the lifecycle hook\n * fell into exactly that hole — every screen view posted, 200'd, and dropped.\n *\n * `ensureCurrentSessionId` closes it: it returns the registered id when an open HAS been registered\n * (unchanged behaviour for every host that fires `reportSessionStart` first), and otherwise mints one,\n * REGISTERS it, and returns it — so every later event in the process correlates to that same id\n * instead of each emitting its own orphan. A minted id is a fallback, not a substitute for a real\n * app-open: it warns once in dev, naming the fix.\n */\nimport { makeSessionId } from \"./reportClientEvent\";\n// The shared primitive returns whether it ACTUALLY warned, which the once-flag below depends on:\n// marking \"already warned\" after a prod no-op would burn the single warning and leave the one dev\n// build that needed it silent. (The old local copy justified itself as keeping this module\n// import-free for the tree-shaken analytics bundle; that had already lapsed — it imports\n// `makeSessionId` right here — and `utils/warnInDev` has no imports of its own.)\nimport { warnInDev } from \"../utils/warnInDev\";\n\n/**\n * Well-known key into the runtime-global symbol registry. `Symbol.for` (NOT a plain `Symbol()`) is\n * what makes this cross-bundle: it returns the SAME symbol for the same string across every copy of\n * this module, so duplicated inlined copies all address one slot.\n *\n * @globalSlot LIVE — every app-open overwrites this with that open's id, so a reader that captures\n * it into a module-local (or a `const` taken once at mount) posts the PREVIOUS open's session to a\n * server that has already moved on. Read it at the moment of use, through `getCurrentSessionId()` /\n * `ensureCurrentSessionId()`. `reviews/runtime`'s `currentOpenId()` deliberately pins one sample of\n * it for the launch; that is a documented latch DERIVED from this slot, not a cache of it.\n */\nconst CURRENT_SESSION_ID_SLOT: unique symbol = Symbol.for(\n \"@wireai/activation:currentSessionId\",\n);\n\ntype GlobalWithSlot = typeof globalThis & {\n [CURRENT_SESSION_ID_SLOT]?: string | undefined;\n};\n\nconst globalSlot = globalThis as GlobalWithSlot;\n\n/**\n * Record the current per-open `session_id`. Called by `reportSessionStart` when it emits an\n * app-open. A blank / non-string id is ignored (the previous id stays current). Idempotent.\n */\nexport const setCurrentSessionId = (id: string | undefined): void => {\n if (typeof id === \"string\" && id.length > 0) {\n globalSlot[CURRENT_SESSION_ID_SLOT] = id;\n }\n};\n\n/** The current per-open `session_id`, or `undefined` when no app-open has been registered yet. */\nexport const getCurrentSessionId = (): string | undefined =>\n globalSlot[CURRENT_SESSION_ID_SLOT];\n\n/** Test-only: forget the current session id so a unit test starts from a clean registry. */\nexport const resetCurrentSessionId = (): void => {\n globalSlot[CURRENT_SESSION_ID_SLOT] = undefined;\n};\n\n/** The one-time message. Hoisted so a prod mint does not rebuild a string nobody will read. */\nconst MINT_WARNING =\n \"[wireai] No app-open session was registered, so a session id was minted for this event \" +\n \"(the server drops an event that has no session_id, and still answers 200). Mount \" +\n \"useLifecycleEvents at your app root so events correlate to a real app-open.\";\n\n/**\n * \"Have we already warned about a minted session id?\" — its OWN `Symbol.for` slot, for the same\n * cross-bundle reason as the id itself: a plain module `let` would warn once per inlined copy, i.e.\n * once per bundle, not once per process. NOT cleared by `resetCurrentSessionId`: \"warn once\" is a\n * process-lifetime promise, and a test that resets the id between mints is still one process.\n *\n * @globalSlot LATCH — written `true` on the first mint that actually warned, and never again. A\n * second differing write would re-arm a warning the process has already spent, turning \"once\" into\n * \"once per whoever cleared it\".\n */\nconst MINT_WARNED_SLOT: unique symbol = Symbol.for(\n \"@wireai/activation:currentSessionIdMintWarned\",\n);\n\ntype GlobalWithWarnSlot = typeof globalThis & { [MINT_WARNED_SLOT]?: boolean };\n\nconst warnSlot = globalThis as GlobalWithWarnSlot;\n\n/**\n * The current per-open `session_id`, MINTING and registering one when no app-open has been\n * registered yet. Always returns a non-empty string. Idempotent (a second call returns the same id)\n * and never throws.\n *\n * Use this on every path that puts a `session_id` on the wire. The server REQUIRES a non-empty\n * `session_id` and drops the event otherwise while still answering 200 (see the module header), so\n * \"no id yet\" must never mean \"send it without one\".\n *\n * BACKWARD-COMPATIBLE BY CONSTRUCTION: when `reportSessionStart` / `useLifecycleEvents` has already\n * registered the real per-open id, this is `getCurrentSessionId()` and nothing changes. It only ever\n * mints in the case that used to produce a silently discarded event.\n *\n * A mint means the host never registered an app-open, so the minted id is one the server has not\n * seen a `session_started` for — the events land, but the session is thinner than a real open.\n * Hence the one-time dev warning naming the fix (mount `useLifecycleEvents` at the app root).\n */\nexport const ensureCurrentSessionId = (): string => {\n const existing = globalSlot[CURRENT_SESSION_ID_SLOT];\n if (typeof existing === \"string\" && existing.length > 0) return existing;\n const minted = makeSessionId();\n globalSlot[CURRENT_SESSION_ID_SLOT] = minted;\n if (!warnSlot[MINT_WARNED_SLOT] && warnInDev(MINT_WARNING)) {\n warnSlot[MINT_WARNED_SLOT] = true;\n }\n return minted;\n};\n","/**\n * transport.ts — kit → Wire server requests for the review module. Non-blocking and never\n * throwing (analytics/reviews must never break the app). Mirrors analytics/reportClientEvent: a\n * thin fetch wrapper with a Bearer tenant key. `submitReview` additionally REPORTS the fate of the\n * row to its caller — accepted / rejected / unsent, three outcomes on purpose; the rest swallow\n * every error.\n *\n * • submitReview → POST {serverUrl}/v1/reviews (the review row)\n * • fetchReviewDecision → GET {serverUrl}/v1/reviews/decision (best-effort, the AI seam)\n * • reportAppEvent → POST {serverUrl}/v1/events (generic app.* namespace)\n *\n * `reportAppEvent` is the strategic extension: it lets a host report arbitrary in-app\n * events through the SAME transport (stored server-side as event_type='app_event',\n * question_key=<name>), which is what the backend review-firing rules evaluate on — and\n * it seeds the broader app-analytics stream. Keep payloads minimal + non-PII.\n */\nimport { ensureCurrentSessionId } from \"../analytics/currentSession\";\nimport { buildEventsRequest, type ClientEvent } from \"../analytics/reportClientEvent\";\nimport type { SubmitResult } from \"../utils/submitResult\";\nimport type { ReviewDecisionResponse, ReviewSubmission, ReviewTarget } from \"./types\";\n\n/**\n * What became of a review POST — the SHARED three-outcome verdict, re-exported under the name the\n * reviews surface has always published. The split is load-bearing and the reasoning lives once, in\n * `utils/submitResult.ts`, because `submitQuestionnaireResponse` answers the identical question.\n * See `submitReview` below for what reads it.\n */\nexport type ReviewSubmitResult = SubmitResult;\n\n/**\n * POST a review (the 1-4 feedback path, and the text-less 5-star row).\n *\n * NON-BLOCKING, and ACKED. It never throws and never makes a caller wait — the request is fired\n * synchronously and the UI is free to advance on the next line — but it RESOLVES with what became\n * of the row.\n *\n * WHY THE RETURN VALUE EXISTS: a 1-4 star rating with mandatory free text is the kit's most\n * valuable payload and it has no persisted queue behind it (unlike `analytics/eventQueue`). If the\n * response is dropped on the floor, a caller cannot tell a delivered submission from one that died\n * in the socket, so it must latch the rating as posted and a detractor who rated offline is lost\n * AND never asked again. Reading the ack is what lets `ReviewGate` keep an undelivered row\n * recoverable.\n *\n * ── WHY IT IS NOT A BOOLEAN ──────────────────────────────────────────────────────────────────\n *\n * It was, for exactly one unpublished release, and the boolean was the bug. `false` meant both\n * \"nothing reached the server\" and \"the server answered non-2xx\", and the one caller that reads\n * this (`ReviewGate.postOnce`) treats `false` as \"still owed\" and re-posts. But the server mints\n * its own row id (`create_review` / `_new_id()`) and `CreateReviewRequest` carries no id, so there\n * is NO idempotency key on the wire: a 502 returned AFTER the insert commits means the re-post\n * writes a SECOND row, double-counting `count` and corrupting `avg` — the precise corruption the\n * one-row latch exists to prevent. A response of any status proves the server was reached, and\n * that is a different question from whether it liked the row. So the two are different values.\n *\n * ── THE RESIDUAL, STATED HONESTLY ────────────────────────────────────────────────────────────\n *\n * `unsent` is not proof the server never got the row. A connection dropped after the request was\n * written — or after the row committed — surfaces as a thrown/rejected `fetch` here, exactly like\n * an offline device. Retrying only on `unsent` is therefore SAFER, not SAFE: it removes the\n * double-post the server itself told us about, and leaves the narrow window where the answer never\n * made it back onto the wire. Closing that window needs a CLIENT-MINTED IDEMPOTENCY KEY the server\n * upserts on, which is a server change (`CreateReviewRequest` + `create_review`) and is not\n * something the kit can fake. Until it exists, prefer losing a row over inventing one: a lost\n * detractor is a gap in the data, a duplicated one is a lie in the data.\n */\nexport const submitReview = async (\n target: ReviewTarget | undefined,\n review: ReviewSubmission,\n): Promise<ReviewSubmitResult> => {\n if (!target?.serverUrl) return \"unsent\";\n try {\n const url = `${target.serverUrl.replace(/\\/$/, \"\")}/v1/reviews`;\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;\n const res = await fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(review),\n });\n // No response object at all is not an answer — treat it as nothing having reached the server\n // rather than as a rejection, or a stubbed-out `fetch` would silently latch the row away.\n if (!res) return \"unsent\";\n return (res as { ok?: boolean }).ok ? \"accepted\" : \"rejected\";\n } catch {\n /* URL/JSON/missing-fetch/network — nothing came back, and never surfaced to the UI */\n return \"unsent\";\n }\n};\n\n/**\n * Options for the best-effort review decision fetch.\n *\n * ── WHAT THE DEPLOYED SERVER ACTUALLY READS (verified 2026-07-17) ────────────────────────\n *\n * `GET /v1/reviews/decision` declares exactly two query params — `session_id` and\n * `device_key` — plus the `Authorization` header. That is the whole wire. Verified against\n * the deployed OpenAPI schema, not against intent.\n *\n * This type is CLOSED on purpose. Hosts that hand-rolled this fetch invented `user_id` and\n * `session_count` query params believing \"the server ignores what it doesn't read, so passing\n * it is always safe\". Both are no-ops: the route declares neither. `session_count` is real, but\n * only on the questionnaire POST body — which is precisely how it copy-pasted its way into a\n * reviews call site and sat there doing nothing. They cost a wire lie — a call site that reads as though\n * identity and a session counter reach the firing brain when neither does. So they are not\n * offered here. Pass identity as `deviceKey`; the session count the server reasons about is the\n * one IT derives from the event stream keyed by `deviceKey`, not one the client asserts.\n *\n * The client-side session floor is a LOCAL rule, not a wire param: use `ReviewConfig.minSessions`\n * (evaluated by `useReviewGate` against the kit's own `wire_review_<id>_sessions` counter).\n */\nexport interface FetchReviewDecisionOptions {\n /**\n * The onboarding session id, when there IS one. OPTIONAL on purpose: the review gate lives on\n * the home feed, where a user legitimately has no onboarding session. `deviceKey` is the real\n * identity for this call. (The server relaxed `session_id` to optional on 2026-07-16 and the\n * relaxation IS deployed — the route no longer 422s without it. Permissive wire: do NOT make\n * this required here.)\n */\n sessionId?: string;\n /**\n * A stable, non-PII device id — THE identity for this call. The decision endpoint reads it for\n * cooldown + min-sessions, and it is the key the server groups a device's events under. A host\n * whose own identity is a user id passes that id here rather than reaching for a `user_id`\n * param the route does not declare.\n */\n deviceKey?: string;\n}\n\n/**\n * Best-effort fetch of the SERVER's review firing decision. The mirror of\n * `fetchQuestionnaireDecision`, and the primitive whose absence caused a live incident.\n *\n * ── WHY THIS LIVES IN THE KIT (Malik, 2026-07-16) ────────────────────────────────────────\n *\n * The questionnaire module has always had its decision fetch; reviews never did. So a host\n * hand-rolled one, and got it subtly wrong: its helper collapsed an explicit `{fire:false}`\n * into `undefined`. `decideReview` is `(local, decision) => decision ?? local`, so `undefined`\n * means \"the server has no opinion, use the local rules\" — and the local timer fired. The\n * server could therefore only ever turn review prompts ON, never OFF. A real user was asked to\n * rate the app ~3 minutes into their FIRST session, having seen nothing yet, and left 1 star.\n *\n * The bug was not that the host was careless. It was that the kit made every host invent this.\n * So the primitive moves here and hosts keep a thin call site.\n *\n * ── THE CONTRACT THAT MATTERS ────────────────────────────────────────────────────────────\n *\n * • 2xx → the FULL `{fire, reason, arm}`, INCLUDING `fire:false`. Never collapse a false\n * into null. That collapse IS the bug: a false must reach `decideReview` intact so\n * it can override the local rules and keep the gate shut.\n * • else → null, and ONLY then. Null means \"the server genuinely has no opinion\", which is\n * the one case where falling back to local rules is correct.\n *\n * Never throws: unreachable, non-2xx, bad JSON, or a missing `fetch` all resolve to null. The\n * kit does not call this internally; a host awaits it and passes the result straight to\n * `useReviewGate({ decision })`.\n *\n * ── DON'T RACE THIS AGAINST A LOCAL TIMER ────────────────────────────────────────────────\n *\n * The return type is `ReviewDecisionResponse`, which carries `arm` alongside `{fire, reason}`.\n * Hand the WHOLE object to the gate and echo `arm` into the submission's `meta.firing_arm`;\n * narrowing it to `{fire, reason}` on the way through silently kills per-arm attribution\n * across a reweighting of the experiment.\n *\n * A host that starts its own dwell timer in parallel with this fetch has built a race a slow\n * server loses: the timer fires, the local rules show the prompt, and the `{fire:false}` still\n * in flight arrives too late to stop it. Do not hand-roll that. `useReviewGate` already owns\n * the wait — set `ReviewConfig.timeoutFallbackMs` and the local rules stay parked until either\n * the decision lands or the window expires, whichever comes first.\n *\n * const decision = await fetchReviewDecision(target, { deviceKey });\n * const gate = useReviewGate({\n * config: { id: \"home\", minSessions: 2, timeoutFallbackMs: 3000 },\n * decision: decision ?? undefined, // pass it whole — keep `arm`\n * storage,\n * });\n */\nexport const fetchReviewDecision = async (\n target: ReviewTarget | undefined,\n options: FetchReviewDecisionOptions = {},\n): Promise<ReviewDecisionResponse | null> => {\n if (!target?.serverUrl) return null;\n try {\n const base = target.serverUrl.replace(/\\/$/, \"\");\n const params = new URLSearchParams();\n if (options.sessionId) params.set(\"session_id\", options.sessionId);\n if (options.deviceKey) params.set(\"device_key\", options.deviceKey);\n const qs = params.toString();\n const url = `${base}/v1/reviews/decision${qs ? `?${qs}` : \"\"}`;\n const headers: Record<string, string> = {};\n if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;\n const res = await fetch(url, { headers });\n if (!res || !res.ok) return null;\n const json = (await res.json()) as ReviewDecisionResponse | null;\n // A body without a boolean `fire` is not a decision. Guard it explicitly rather than\n // letting `{}` through as a truthy object that `decideReview` would treat as a verdict\n // (`{}.fire === undefined` is falsy, so it would silently read as \"never fire\").\n if (!json || typeof json.fire !== \"boolean\") return null;\n return json;\n } catch {\n /* unreachable / non-2xx / bad JSON / missing-fetch → the server has no opinion */\n return null;\n }\n};\n\n/** Options for a reported app event. `deviceKey` groups a device's sessions server-side. */\nexport interface ReportAppEventOptions {\n /**\n * The onboarding/session id to correlate with, when known. Optional: when omitted the event\n * still carries the CURRENT per-open session id (`ensureCurrentSessionId()`), because an event\n * with no `session_id` is dropped server-side behind a 200. Pass one only to override.\n */\n sessionId?: string;\n /** A stable, non-PII device id — the review-decision endpoint reads it for min-sessions. */\n deviceKey?: string;\n /** Small non-PII extras. */\n meta?: Record<string, unknown>;\n}\n\n/**\n * Report a generic in-app event through the existing events transport. Stored server-side\n * as `event_type='app_event'`, `question_key=<name>`. Fire-and-forget. Keep `name` a short\n * stable identifier and `meta` small + non-PII.\n *\n * reportAppEvent(target, \"content_share\", { sessionId, deviceKey });\n *\n * ── `session_id` IS NON-NEGOTIABLE ON THE WIRE ───────────────────────────────────────────\n * The server's event model declares `session_id` required + non-empty, and `POST /v1/events`\n * validates per event inside a try/except that counts the failure as `skipped` and STILL returns\n * HTTP 200. An event sent without a `session_id` is therefore accepted and discarded, and a\n * fire-and-forget caller never finds out. This used to be reachable through the ordinary API:\n * `options.sessionId` was optional, so a host calling `reportAppEvent(target, \"screen\", { deviceKey })`\n * posted every screen view into that hole. So the id is no longer conditional — an explicit\n * `sessionId` wins, otherwise the CURRENT per-open id is used (minted + registered if no app-open\n * has been registered yet).\n */\nexport const reportAppEvent = (\n target: ReviewTarget | undefined,\n name: string,\n options: ReportAppEventOptions = {},\n): void => {\n if (!target?.serverUrl || !name) return;\n try {\n // An explicit id wins; a missing OR BLANK one falls back to the current per-open id. A bare\n // `??` would let `sessionId: \"\"` through, and the server rejects an empty string exactly like\n // a missing key (`min_length=1`), so the blank case has to fall back too.\n const supplied = options.sessionId ?? \"\";\n const event: Record<string, unknown> = {\n event_type: \"app_event\",\n question_key: name,\n session_id: supplied.trim().length > 0 ? supplied : ensureCurrentSessionId(),\n };\n // device_key rides in the non-PII user_context bucket the server sanitizes; the\n // review-decision endpoint reads it to group a device's sessions.\n if (options.deviceKey) event.user_context = { device_key: options.deviceKey };\n if (options.meta && Object.keys(options.meta).length > 0) {\n event.meta = JSON.stringify(options.meta);\n }\n // Route through the ONE canonical /v1/events builder (url + headers + body) instead of\n // re-describing the endpoint here; still fire-and-forget.\n const req = buildEventsRequest(target, [event as unknown as ClientEvent]);\n if (!req) return;\n void fetch(req.url, req.init).catch(() => {\n /* best-effort, swallow */\n });\n } catch {\n /* swallow */\n }\n};\n","/**\n * ReviewGate — the in-app review sentiment gate UI.\n *\n * A NEUTRAL prompt (\"Enjoying <app>?\") over five tappable stars. The honest split:\n * • 5 stars → the native store review (expo-store-review when available, else the\n * configured store URL). Fires `store_review_requested`, and POSTs a text-less\n * `stars=5` row carrying `meta.store_route` so promoters reach the server too\n * (before 2026-07-16 this route posted nothing — see `pickRating`).\n * • 1-4 stars → a feedback form: the \"why this rating\" opinion box (~4 lines, MANDATORY\n * as of Malik's 2026-07-16 ruling) and a smaller optional \"what can you suggest\" box,\n * POSTed to the Wire server (`feedback_text` + `suggestion`). Warm thank-you state,\n * never a dead end.\n *\n * EXACTLY ONE row is POSTed per gate, at the last responsible moment — see `postOnce`. A\n * detractor who abandons instead of typing still has their rating posted (`meta.abandoned`),\n * so forcing the text can never cost us the detractor data it exists to collect.\n *\n * The form NEVER asks for identity (no name / email / contact field): the host already\n * identifies the user via `sessionId` + `meta` (e.g. user_id, device_key), which is all\n * the POST body carries alongside the rating and the free text.\n *\n * Motion (2026-07-16): the three phases used to swap IN PLACE with no transition. They now\n * ride `ModalPaneHost`, the same pane-slide primitive the questionnaire steps use, and the\n * thank-you plays the shared `DoneCheck` payoff before the existing 1500ms auto-close.\n * Picking a star fires a haptic through the lazy, optional `expo-haptics` peer — silently\n * absent when the host has not installed it. The popup's own entrance is unchanged: it is\n * the CenteredModal shell's spring, which already animates.\n *\n * Every visual value comes from the OnboardingTheme (useOnboardingTheme). Analytics are\n * callback-based (mirroring the coachmark convention): the host wires `onEvent` to its own\n * analytics. No PII in events — feedback text goes ONLY in the POST body.\n *\n * Store-policy: `requestReview` is quota'd and never guaranteed to show; it is called ONLY\n * on the 5-star path, never from a \"rate us 5 stars\" button. See the README notes.\n */\nimport React, { useCallback, useMemo, useState } from \"react\";\nimport { Pressable, StyleSheet, Text, TextInput, View } from \"react-native\";\n\nimport { Button } from \"../components/Button\";\nimport { DoneCheck } from \"../components/DoneCheck\";\nimport { ModalPaneHost } from \"../components/ModalPaneHost\";\nimport { playHaptic } from \"../haptics/haptics\";\nimport { mergeThemeOver } from \"../theme/mergeThemeOver\";\nimport { useOnboardingTheme } from \"../theme/ThemeContext\";\nimport { bodyStyle, captionStyle, headingStyle } from \"../theme/typography\";\nimport { buildReviewSubmission, routeRating } from \"./decision\";\nimport { feedbackInputHeights } from \"./feedbackForm\";\nimport { resolvePresentation } from \"./presentation\";\nimport { ReviewModal, type ReviewModalHandle } from \"./ReviewModal\";\nimport { requestStoreReview } from \"./storeReview\";\nimport { submitReview } from \"./transport\";\nimport type { ReviewGateProps, ReviewSubmission } from \"./types\";\n\ntype Phase = \"rating\" | \"feedback\" | \"thanks\";\n\nconst _ReviewGate: React.FC<ReviewGateProps> = ({\n appName,\n store,\n target,\n sessionId,\n meta,\n onEvent,\n onResolved,\n onShown,\n theme: themeOverride,\n dismissible = true,\n presentation,\n id = \"default\",\n}) => {\n const active = useOnboardingTheme();\n const t = useMemo(() => mergeThemeOver(active, themeOverride), [active, themeOverride]);\n const mode = resolvePresentation(presentation);\n const modalRef = React.useRef<ReviewModalHandle>(null);\n\n const [phase, setPhase] = useState<Phase>(\"rating\");\n const [stars, setStars] = useState(0);\n const [hovered, setHovered] = useState(0);\n const [feedback, setFeedback] = useState(\"\");\n const [suggestion, setSuggestion] = useState(\"\");\n\n // Fire review_prompt_shown exactly ONCE per mount. The callbacks are read through\n // refs (not effect deps) so an inline `onShown`/`onEvent` — recreated every render,\n // as real hosts idiomatically pass — never re-runs the effect. Empty deps + the\n // shownRef guard make this a true fire-once, immune to prop-identity churn.\n const shownRef = React.useRef(false);\n const onShownRef = React.useRef(onShown);\n const onEventRef = React.useRef(onEvent);\n onShownRef.current = onShown;\n onEventRef.current = onEvent;\n React.useEffect(() => {\n if (shownRef.current) return;\n shownRef.current = true;\n onShownRef.current?.();\n onEventRef.current?.({ name: \"review_prompt_shown\", id });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const resolveTimer = React.useRef<ReturnType<typeof setTimeout> | null>(null);\n\n React.useEffect(() => {\n return () => {\n if (resolveTimer.current) clearTimeout(resolveTimer.current);\n };\n }, []);\n\n const resolve = useCallback(() => {\n onResolved?.();\n }, [onResolved]);\n\n /**\n * EXACTLY ONE review row per gate, ever — the latch that makes \"force feedback\" safe.\n *\n * WHY ONE POST AND NOT TWO: the tempting design is to post the rating at star-tap and then\n * post again with the text, enriching the first row. That needs the server to accept a\n * client-supplied id so the second write UPSERTS. It does not, as deployed today:\n * `create_review` mints its own id unconditionally (`app/reviews.py` `_new_id()`) and\n * `CreateReviewRequest` (`app/schemas.py`) has no id field — so two posts = two rows, which\n * double-counts `count` and corrupts `avg`. Reading the ack does not rescue that guess either:\n * a second post against a server with no upsert comes back 2xx HAVING created the second row, so\n * the failure is invisible to the caller and permanent. Post once, at the last responsible\n * moment, and never depend on an unmerged/undeployed server capability.\n *\n * ── THE LATCH IS A CLAIM, NOT A RECEIPT ────────────────────────────────────────────────────\n *\n * The latch is taken OPTIMISTICALLY, before the row has landed, because its first job is to stop\n * two callers in the same tick from posting twice (the double-row corruption above). It is\n * RELEASED again when `submitReview` reports that NOTHING REACHED THE SERVER, and the undelivered\n * body is kept in `unackedRef` so the abandonment net below can re-post it when the gate goes\n * away. Without that release, a rating that died in the socket — the offline detractor, whose 1-4\n * stars plus mandatory text is the most valuable payload this kit carries — is latched as posted\n * and silently dropped, and the once-gate means they are never asked again.\n *\n * ── A NON-2xx IS NOT A RETRY TICKET ────────────────────────────────────────────────────────\n *\n * \"Did it land\" and \"did the server answer\" are DIFFERENT questions, which is why `submitReview`\n * resolves `accepted` / `rejected` / `unsent` instead of a boolean. Only `unsent` un-latches.\n * A non-2xx means the server was REACHED, and with no idempotency key on the wire (see above:\n * the id is server-minted) we cannot know whether it stored the row before failing to say so — a\n * gateway 502 after the insert commits looks identical to a refusal. Re-posting on that verdict\n * writes the second row this whole latch exists to prevent, and does it on the exact code path\n * meant to protect the data. So on any answer the latch stays closed and `unackedRef` is\n * cleared: the row is the server's problem now.\n *\n * HONEST RESIDUAL: `unsent` is not proof the server never got it either. A connection dropped\n * after the row committed throws here exactly like an offline device, so the one re-post below\n * can still duplicate. This is SAFER, not safe. The real fix is a client-minted idempotency key\n * the server upserts on — a server change, out of scope for the kit.\n *\n * NOTHING HERE IS AWAITED. `postOnce` returns synchronously and every caller advances the phase\n * on the next line, so the user is never waiting on the network to dismiss the prompt.\n *\n * HONEST CEILING: recovery is IN-PROCESS — one re-post when the gate unmounts. A rating taken\n * offline and never recovered before the app is killed is still lost. Durable cross-launch retry\n * needs either a storage prop on this component (new public API) or a generalized\n * `createEventQueue` (that queue is hardwired to POST /v1/events, and a review row is not an\n * event); both are deliberately out of scope here.\n */\n const postedRef = React.useRef(false);\n /** The last submission fired with no answer yet. Non-null after an UNSENT post = still owed. */\n const unackedRef = React.useRef<ReviewSubmission | null>(null);\n const postOnce = useCallback(\n (body: ReviewSubmission) => {\n if (postedRef.current) return;\n postedRef.current = true;\n unackedRef.current = body;\n void submitReview(target, body).then((result) => {\n if (result === \"unsent\") {\n // Nothing reached the server → un-latch, so this row can still go out.\n postedRef.current = false;\n return;\n }\n // `accepted` OR `rejected`: the server answered, so it owns this row. Never re-post it —\n // a non-2xx can follow a committed insert, and the wire has no idempotency key.\n unackedRef.current = null;\n });\n },\n [target],\n );\n\n /**\n * The abandonment safety net, and the reason forcing feedback does not COST us detractor data.\n *\n * Malik's ruling: a 1-4 rater must leave text. Implemented naively (block Send, remove Skip),\n * that would LOSE the ratings of everyone who bails rather than types — the detractors we most\n * need — because today the 1-4 branch posts only from `sendFeedback`. So: if the gate goes away\n * while a rating is pending and unposted, we post the rating anyway, with whatever text was\n * typed, flagged `meta.abandoned`. The user is pushed to explain; the rating is never the price.\n *\n * Read through a ref and fired from an unmount-only effect, so it always sees the latest\n * state without re-subscribing (the same ref convention `review_prompt_shown` above uses).\n *\n * It is ALSO the recovery point for a submission the server never ANSWERED — never for one it\n * answered non-2xx, which `postOnce` clears rather than re-queues. That body already carries the\n * user's real text, so it is re-posted verbatim rather than re-flagged `abandoned`.\n */\n const abandonRef = React.useRef<() => void>(() => {});\n abandonRef.current = () => {\n const undelivered = unackedRef.current;\n if (undelivered && !postedRef.current) {\n postOnce(undelivered);\n return;\n }\n if (postedRef.current || stars < 1) return;\n postOnce(\n buildReviewSubmission({\n stars,\n feedbackText: feedback,\n suggestion,\n sessionId,\n meta: { ...meta, abandoned: true },\n }),\n );\n };\n React.useEffect(() => () => abandonRef.current(), []);\n\n const resolveWithDelay = useCallback(() => {\n if (resolveTimer.current) clearTimeout(resolveTimer.current);\n resolveTimer.current = setTimeout(() => {\n if (mode === \"modal\") {\n modalRef.current?.close();\n } else {\n onResolved?.();\n }\n }, 1500);\n }, [mode, onResolved]);\n\n const pickRating = useCallback(\n async (value: number) => {\n setStars(value);\n // The tap you feel. Fire-and-forget: an absent peer must never delay the route below.\n playHaptic(\"selection\");\n onEvent?.({ name: \"review_rating_selected\", id, stars: value });\n if (routeRating(value) === \"store\") {\n onEvent?.({ name: \"store_review_requested\", id, stars: value });\n const route = await requestStoreReview(store);\n // The 5-star row. Until 2026-07-16 this branch POSTed NOTHING: submitReview was\n // reachable only from the feedback phase, which only the 1-4 route enters. Every\n // promoter was silently dropped, so server-side `by_stars[\"5\"]` and `store_routed`\n // were permanently 0 and `avg` averaged detractors only. The four onEvent moments\n // above go to the HOST's analytics, never to Wire, so nothing else covered this.\n //\n // The body is `stars` + `meta` with NO feedback text — exactly the shape the server\n // already documents as the store route (\"A 5-star tap posts stars=5 with no text\")\n // and the shape its `store_routed` aggregate counts (stars == 5 and not\n // feedback_text). So this is valid against prod as deployed, with no server change.\n //\n // `store_route` records WHICH route we took (native dialog / store URL / neither).\n // Honest ceiling: requestStoreReview is OS-quota'd and reports nothing about whether\n // the user actually left a review — this only ever means we ASKED. Never read it as\n // a review count. We await first so the row carries the route it actually took.\n postOnce(\n buildReviewSubmission({\n stars: value,\n sessionId,\n meta: { ...meta, store_route: route },\n }),\n );\n setPhase(\"thanks\");\n resolveWithDelay();\n } else {\n // 1-4: no post yet. The row lands either from `sendFeedback` (with the forced text) or\n // from the abandonment net above — one row, whichever happens first.\n setPhase(\"feedback\");\n }\n },\n [onEvent, id, store, postOnce, sessionId, meta, resolveWithDelay],\n );\n\n /**\n * Malik's ruling (2026-07-16): a 1-4 rater is FORCED to leave feedback. Enforced HERE, on the\n * client, and deliberately NOT on the wire: `feedback_text` stays optional in\n * `CreateReviewRequest`, because the transport is fire-and-forget and every already-shipped\n * client still posts text-less rows — tightening the server would silently 422 them into\n * oblivion. Permissive wire, strict client.\n *\n * Mirrors the questionnaire contract exactly: the opinion box is the mandatory one, the\n * suggestion box stays optional. Trimmed — whitespace is not feedback.\n */\n const feedbackReady = feedback.trim().length > 0;\n\n const sendFeedback = useCallback(() => {\n // The forced-text gate. Also guarded here, not just on the button's `disabled`, so the\n // rule holds no matter who calls this.\n if (!feedbackReady) return;\n // Identity is NEVER collected here — the host already sends user_id / device /\n // session via `sessionId` + `meta`. We POST only the rating + the free text.\n const body = buildReviewSubmission({\n stars,\n feedbackText: feedback,\n suggestion,\n sessionId,\n meta,\n });\n postOnce(body);\n onEvent?.({ name: \"review_feedback_submitted\", id, stars });\n playHaptic(\"success\");\n setPhase(\"thanks\");\n resolveWithDelay();\n }, [feedbackReady, stars, feedback, suggestion, sessionId, meta, postOnce, onEvent, id, resolveWithDelay]);\n\n const dismiss = useCallback(() => {\n setPhase(\"thanks\");\n resolveWithDelay();\n }, [resolveWithDelay]);\n\n // The in-card \"Not now\"/\"Skip\" link. In modal mode it plays the popup's exit animation\n // (which resolves on completion, the same path a backdrop tap takes); inline keeps the\n // legacy behavior of swapping to the thank-you in place.\n const handleDismissTap = useCallback(() => {\n if (mode === \"modal\") modalRef.current?.close();\n else dismiss();\n }, [mode, dismiss]);\n\n const surface = {\n backgroundColor: t.colors.surface,\n borderRadius: t.radius.lg,\n padding: t.spacing.lg,\n gap: t.spacing.md,\n } as const;\n\n // A multiline box ~4 lines tall (capped at ~5), derived from the theme's body\n // line-height so it scales with the app's font config. The field's own vertical\n // padding is folded in so the requested lines are visible inside the padding.\n const feedbackBox = feedbackInputHeights(t.fonts, t.spacing.md);\n\n const linkStyle = [captionStyle(t.fonts), styles.link, { color: t.colors.textMuted }];\n\n // Each phase is a self-contained PANE, title included, so the heading travels with its\n // content instead of swapping under a static header — one moving surface, not two.\n const ratingPane = (\n <View style={{ gap: t.spacing.md }}>\n <Text style={[headingStyle(t.fonts), { color: t.colors.text }]}>\n {appName ? `Enjoying ${appName}?` : \"How's your experience?\"}\n </Text>\n <Text style={[captionStyle(t.fonts), { color: t.colors.textMuted }]}>\n Tap a star to let us know.\n </Text>\n <View style={styles.stars}>\n {[1, 2, 3, 4, 5].map((n) => {\n const filled = n <= (hovered || stars);\n return (\n <Pressable\n key={n}\n onPressIn={() => setHovered(n)}\n onPressOut={() => setHovered(0)}\n onPress={() => void pickRating(n)}\n accessibilityRole=\"button\"\n accessibilityLabel={`${n} star${n > 1 ? \"s\" : \"\"}`}\n hitSlop={8}\n >\n <Text style={[styles.star, { color: filled ? t.colors.primary : t.colors.border }]}>\n {filled ? \"★\" : \"☆\"}\n </Text>\n </Pressable>\n );\n })}\n </View>\n {dismissible && (\n <Pressable\n onPress={handleDismissTap}\n hitSlop={8}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Dismiss the review prompt\"\n >\n <Text style={linkStyle}>Not now</Text>\n </Pressable>\n )}\n </View>\n );\n\n const feedbackPane = (\n <View style={{ gap: t.spacing.md }}>\n <Text style={[headingStyle(t.fonts), { color: t.colors.text }]}>What should we improve?</Text>\n <Text style={[captionStyle(t.fonts), { color: t.colors.textMuted }]}>\n A few words is plenty — tell us what went wrong and we'll fix it.\n </Text>\n {/* No name / email / contact field: the host already identifies the user via\n sessionId + meta, so we never ask for identity here. As of Malik's 2026-07-16\n ruling the FIRST box is mandatory (mirroring the questionnaire's `opinion`);\n the suggestion box stays optional. */}\n <Text style={[captionStyle(t.fonts), { color: t.colors.textMuted }]}>\n Why this rating?\n </Text>\n <TextInput\n value={feedback}\n onChangeText={setFeedback}\n placeholder=\"e.g. too many steps\"\n placeholderTextColor={t.colors.textMuted}\n accessibilityLabel=\"Why this rating?\"\n multiline\n textAlignVertical=\"top\"\n style={[\n bodyStyle(t.fonts),\n styles.input,\n {\n color: t.colors.text,\n backgroundColor: t.colors.background,\n borderRadius: t.radius.md,\n borderColor: t.colors.border,\n padding: t.spacing.md,\n minHeight: feedbackBox.minHeight,\n maxHeight: feedbackBox.maxHeight,\n },\n ]}\n />\n <Text style={[captionStyle(t.fonts), { color: t.colors.textMuted }]}>\n What can we do better? (optional)\n </Text>\n <TextInput\n value={suggestion}\n onChangeText={setSuggestion}\n placeholder=\"e.g. save my place between steps\"\n placeholderTextColor={t.colors.textMuted}\n accessibilityLabel=\"What can we do better? (optional)\"\n style={[\n bodyStyle(t.fonts),\n styles.input,\n {\n color: t.colors.text,\n backgroundColor: t.colors.background,\n borderRadius: t.radius.md,\n borderColor: t.colors.border,\n paddingHorizontal: t.spacing.md,\n paddingVertical: t.spacing.sm,\n minHeight: t.button.height,\n },\n ]}\n />\n <Button\n title=\"Send feedback\"\n onPress={sendFeedback}\n variant=\"primary\"\n fullWidth\n disabled={!feedbackReady}\n />\n {/* No Skip here — this step is mandatory (Malik, 2026-07-16). The rating itself is\n still never held hostage: bailing out of the gate entirely (backdrop / Android\n back / navigation) posts it through the abandonment net above. We do NOT trap the\n user in the modal; forcing the TEXT is the rule, imprisoning them is not. */}\n </View>\n );\n\n const thanksPane = (\n <View style={{ gap: t.spacing.md }}>\n <DoneCheck theme={t} />\n <Text style={[headingStyle(t.fonts), styles.centerText, { color: t.colors.text }]}>\n Thank you\n </Text>\n <Text style={[bodyStyle(t.fonts), styles.centerText, { color: t.colors.textMuted }]}>\n We appreciate you. This helps us make it better.\n </Text>\n </View>\n );\n\n // The phase content, shared by both presentations. In \"modal\" mode ReviewModal supplies\n // the centered, dimmed, animated card surface; in \"inline\" mode the bare card below does.\n const content = (\n <ModalPaneHost paneKey={phase}>\n {phase === \"rating\" ? ratingPane : phase === \"feedback\" ? feedbackPane : thanksPane}\n </ModalPaneHost>\n );\n\n // Default: a centered popup over a dimmed backdrop with a spring/fade entrance. A backdrop\n // tap (or Android back) resolves via the same path onResolved already tracks.\n if (mode === \"modal\") {\n return (\n <ReviewModal ref={modalRef} theme={t} dismissible={dismissible} onDismiss={resolve}>\n {content}\n </ReviewModal>\n );\n }\n\n // Legacy: the bare card, positioned by wherever the host mounts it.\n return <View style={[styles.card, surface]}>{content}</View>;\n};\n\nexport const ReviewGate = React.memo(_ReviewGate);\nReviewGate.displayName = \"ReviewGate\";\n\nconst styles = StyleSheet.create({\n card: { width: \"100%\" },\n stars: { flexDirection: \"row\", justifyContent: \"center\", gap: 8 },\n star: { fontSize: 40, lineHeight: 46 },\n input: { borderWidth: 1 },\n link: { textAlign: \"center\", textDecorationLine: \"underline\" },\n centerText: { textAlign: \"center\" },\n});\n\nexport default ReviewGate;\n","/**\n * Module-level runtime for the coachmark engine: the injected sync storage, the\n * global `isTestingCoachmark` flag, and the seen-gate helpers built on top.\n *\n * WHY module singletons (not just context): the tour hooks and the anchor\n * registry are driven imperatively from anywhere in the tree (a screen deep\n * under the navigator), while the storage + testing flag are configured once at\n * the root by `CoachmarkProvider`. Singletons let a hook read the gate without\n * every call site threading the storage through props. The provider is still the\n * single writer — it calls `setCoachmarkStorage` / `setCoachmarkTesting` on mount.\n *\n * ── WHY A globalThis SLOT, NOT PLAIN MODULE VARIABLES ─────────────────────────────────────────\n * These three values used to be plain module `let`s, and this module is inlined by tsup into FOUR\n * dist bundles (`./coachmarks`, `./reviews`, `./questionnaire`, `./showcase`). Under `dist`\n * resolution — node `import`/`require`, SSR, jest, RN-web, anything that is not Metro — a host that\n * mounts `CoachmarkProvider` from `@wireai/activation/coachmarks` wrote the COACHMARKS copy, while\n * `useReviewGate` from `@wireai/activation/reviews` read the REVIEWS copy, which was still `null`.\n * Everything downstream then failed silently: `readInt(null, …)` returns 0, so `bumpSessionCount`\n * returns 1 forever and the fail-closed `minSessions: 2` default is unsatisfiable (the gate NEVER\n * fires); the `seen` once-gate never persists; and a tenant's coachmarks kill switch never reaches\n * the reviews bundle. Metro masked it by collapsing every subpath back to one `src/` file through\n * the `react-native` export condition, which is a bundler accident, not a guarantee.\n *\n * Same remedy this repo already applies to `currentSession`, the auto device-key registry and the\n * activation revalidation counter: ONE record in a `globalThis` slot keyed by `Symbol.for(...)`, so\n * every inlined copy of this module addresses the SAME state.\n */\nimport type { CoachmarkStorage } from \"./types\";\n\n/**\n * Well-known key into the runtime-global symbol registry — one coachmark runtime across bundles.\n *\n * @globalSlot LATCH — the RUNTIME RECORD is created once and its identity is then stable. A second\n * write replaces the record `subscribeCoachmarksEnabled` registered its listeners on, so a\n * kill-switch flip notifies an orphaned set while a tour that believes it is running paints nothing\n * — the exact limbo `coachmarkKillSwitch` canaries. Its FIELDS (`storage`, `testing`,\n * `coachmarksEnabled`, `listeners`) are live, which is why every accessor calls `coachmarkRuntime()`\n * instead of holding the record, and why `coachmarksEnabled` is subscribable rather than sampled.\n */\nconst COACHMARK_RUNTIME_SLOT: unique symbol = Symbol.for(\"@wireai/activation:coachmarkRuntime\");\n\n/** The shared runtime: the provider-injected storage, the QA replay flag, and the kill switch. */\ntype CoachmarkRuntime = {\n storage: CoachmarkStorage | null;\n testing: boolean;\n // The coachmarks feature kill switch (from GET /v1/features). Default true = fail-open: with no\n // flags fetched, coachmarks behave exactly as before. CoachmarkProvider writes it from the\n // resolved flags. Disabled → the tour never ARMS and the overlay `show()` is a no-op, so nothing\n // paints and — critically — no once-gate is written, so re-enabling replays the tour correctly.\n coachmarksEnabled: boolean;\n // Subscribers to the kill switch. OPTIONAL on the type (never on the behaviour) so a record left\n // in the registry by an inlined copy from an OLDER kit version — which had no listener set — is\n // upgraded in place rather than read as corrupt.\n listeners?: Set<() => void>;\n};\n\ntype GlobalWithCoachmarkRuntime = typeof globalThis & {\n [COACHMARK_RUNTIME_SLOT]?: CoachmarkRuntime;\n};\n\nconst runtimeGlobal = globalThis as GlobalWithCoachmarkRuntime;\n\nconst coachmarkRuntime = (): CoachmarkRuntime => {\n const existing = runtimeGlobal[COACHMARK_RUNTIME_SLOT];\n if (existing) return existing;\n const created: CoachmarkRuntime = { storage: null, testing: false, coachmarksEnabled: true };\n runtimeGlobal[COACHMARK_RUNTIME_SLOT] = created;\n return created;\n};\n\nconst enabledListeners = (): Set<() => void> => {\n const runtime = coachmarkRuntime();\n if (!runtime.listeners) runtime.listeners = new Set();\n return runtime.listeners;\n};\n\n/**\n * Wake the subscribers on a MICROTASK, never synchronously.\n *\n * `CoachmarkProvider` writes this flag during its RENDER phase on purpose (so a child's mount\n * effect already sees the resolved value). Notifying from there synchronously would schedule a\n * React update while a *different* component is rendering. Every subscriber reads the value\n * through `areCoachmarksEnabled()` at render time anyway, so the notification only has to wake\n * the components that are not already re-rendering — one microtask later is soon enough, and it\n * keeps the write side legal from anywhere.\n */\nconst notifyCoachmarksEnabled = (): void => {\n if (enabledListeners().size === 0) return;\n void Promise.resolve().then(() => {\n for (const listener of [...enabledListeners()]) {\n try {\n listener();\n } catch {\n // A broken subscriber must never break the kill switch for the others.\n }\n }\n });\n};\n\n/**\n * Set the coachmarks master switch (from the resolved feature flags). Default true (fail-open).\n * Written by CoachmarkProvider; read imperatively by the tour arm + the overlay store, and\n * SUBSCRIBED to by `useCoachmarkTour` (see `subscribeCoachmarksEnabled`).\n *\n * No-ops when the value is unchanged, which matters because the provider writes it on every\n * render — without that guard every render would fan out a notification.\n */\nexport const setCoachmarksEnabled = (value: boolean): void => {\n const runtime = coachmarkRuntime();\n if (runtime.coachmarksEnabled === value) return;\n runtime.coachmarksEnabled = value;\n notifyCoachmarksEnabled();\n};\n\n/** Whether the coachmarks module is enabled. False → tours/overlays are silently skipped. */\nexport const areCoachmarksEnabled = (): boolean => coachmarkRuntime().coachmarksEnabled;\n\n/**\n * Subscribe to kill-switch flips. Returns the unsubscribe function — the `useSyncExternalStore`\n * contract, and deliberately the SAME shape as `coachmarkOverlay.subscribe` next door rather than\n * a second pattern to keep correct.\n *\n * WHY IT EXISTS: the flag is a mutable `globalThis` value, so on its own it can only be SAMPLED.\n * A tour that samples it cannot notice a tenant flipping it off mid-flight — the overlay's\n * `show()` quietly becomes a no-op while the tour still believes it is running, so nothing paints,\n * nothing can be tapped to advance, and no terminal path is reachable. `useCoachmarkTour`\n * subscribes instead. The listener set lives in the SAME `globalThis` record as the flag, so every\n * inlined copy of this module shares one subscriber list.\n */\nexport const subscribeCoachmarksEnabled = (listener: () => void): (() => void) => {\n const listeners = enabledListeners();\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n};\n\n/** Set (or clear) the injected sync gate storage. Called by CoachmarkProvider. */\nexport const setCoachmarkStorage = (storage: CoachmarkStorage | null): void => {\n coachmarkRuntime().storage = storage;\n};\n\n/** The currently-injected gate storage, or null if no provider is mounted. */\nexport const getCoachmarkStorage = (): CoachmarkStorage | null => coachmarkRuntime().storage;\n\n/**\n * Toggle the global QA replay flag. When true, EVERY seen-gate reads as unseen\n * and every seen-write is suppressed, so every tour + showcase replays on each\n * mount. The app flips ONE boolean to QA the whole coachmark surface.\n */\nexport const setCoachmarkTesting = (value: boolean): void => {\n coachmarkRuntime().testing = value;\n};\n\n/** Whether replay-everything QA mode is on. */\nexport const isCoachmarkTesting = (): boolean => coachmarkRuntime().testing;\n\n/** Gate key for a tour: `wire_coachmark_<tourId>_seen`. */\nexport const coachmarkGateKey = (tourId: string): string =>\n `wire_coachmark_${tourId}_seen`;\n\n/** Gate key for a feature showcase: `wire_showcase_<id>_seen`. */\nexport const showcaseGateKey = (showcaseId: string): string =>\n `wire_showcase_${showcaseId}_seen`;\n\n/** Marker value written for a seen gate. */\nconst SEEN_VALUE = \"1\";\n\n/**\n * Read a seen gate. Testing mode ALWAYS reports unseen (so everything replays).\n * A missing storage (no provider) also reports unseen — the safe default is to\n * show the hint rather than silently swallow it. `storageOverride` /\n * `testingOverride` let a self-contained component (e.g. FeatureShowcase) pass\n * its own storage without relying on the provider singleton.\n */\nexport const hasSeenGate = (\n key: string,\n storageOverride?: CoachmarkStorage | null,\n testingOverride?: boolean,\n): boolean => {\n if (testingOverride ?? isCoachmarkTesting()) return false;\n const storage = storageOverride ?? getCoachmarkStorage();\n if (!storage) return false;\n try {\n return storage.getItem(key) === SEEN_VALUE;\n } catch {\n return false;\n }\n};\n\n/**\n * Mark a gate seen. Suppressed in testing mode (so replay never persists) and a\n * no-op without storage. Best-effort — a throwing adapter never breaks the tour.\n */\nexport const markSeenGate = (\n key: string,\n storageOverride?: CoachmarkStorage | null,\n testingOverride?: boolean,\n): void => {\n if (testingOverride ?? isCoachmarkTesting()) return;\n const storage = storageOverride ?? getCoachmarkStorage();\n if (!storage) return;\n try {\n storage.setItem(key, SEEN_VALUE);\n } catch {\n // Best-effort: a failed write just means the hint may show once more.\n }\n};\n","/**\n * The fail-open defaults + a tolerant parser for the server's flags payload.\n *\n * THE CONTRACT: fail-open, never fail-dark. An absent server object, a missing module key, a\n * malformed value, or an unreachable endpoint must resolve to the MOST PERMISSIVE state — every\n * module ON, onboarding `ai` — so a control-plane outage can only ever be more permissive than\n * the dashboard intends. It can never dark a tenant's activation. Matches the server's own\n * documented fail-open default (FEATURES.md: \"local defaults — every module on, onboarding ai\").\n */\nimport type { OnboardingMode, WireFeatures } from \"./types\";\n\n/**\n * ALL ON, onboarding `ai`. Frozen + module-level so its reference is stable across every call\n * (consumers that list resolved flags in an effect dep array never loop on a fresh identity).\n */\nexport const defaultWireFeatures: WireFeatures = Object.freeze({\n coachmarks: Object.freeze({ enabled: true }),\n showcase: Object.freeze({ enabled: true }),\n onboarding: Object.freeze({ mode: \"ai\" as OnboardingMode }),\n review: Object.freeze({ enabled: true }),\n questionnaire: Object.freeze({ enabled: true }),\n announcements: Object.freeze({ enabled: true }),\n}) as WireFeatures;\n\nconst VALID_MODES: readonly OnboardingMode[] = [\"ai\", \"static\", \"off\"];\n\n/** Read a `{ enabled: boolean }` module, defaulting to ON when absent/malformed (fail-open). */\nconst readEnabled = (raw: unknown): { enabled: boolean } => {\n if (raw && typeof raw === \"object\" && typeof (raw as { enabled?: unknown }).enabled === \"boolean\") {\n return { enabled: (raw as { enabled: boolean }).enabled };\n }\n return { enabled: true };\n};\n\n/**\n * Narrow the server's (untrusted) JSON to a complete `WireFeatures`. Every field is filled with\n * its fail-open default when the server omits it or sends the wrong type, so a partial or\n * malformed payload degrades PERMISSIVELY rather than throwing. A non-object input (null, array,\n * string) → the full defaults. Never throws.\n */\nexport const parseWireFeatures = (raw: unknown): WireFeatures => {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return defaultWireFeatures;\n const src = raw as Record<string, unknown>;\n const modeRaw = (src.onboarding as { mode?: unknown } | undefined)?.mode;\n const mode: OnboardingMode =\n typeof modeRaw === \"string\" && VALID_MODES.includes(modeRaw as OnboardingMode)\n ? (modeRaw as OnboardingMode)\n : \"ai\";\n return {\n coachmarks: readEnabled(src.coachmarks),\n showcase: readEnabled(src.showcase),\n onboarding: { mode },\n review: readEnabled(src.review),\n questionnaire: readEnabled(src.questionnaire),\n announcements: readEnabled(src.announcements),\n };\n};\n\n/** Value-equality of two resolved flags — lets the hook hold a stable reference across churn. */\nexport const featuresEqual = (a: WireFeatures, b: WireFeatures): boolean =>\n a === b ||\n (a.coachmarks.enabled === b.coachmarks.enabled &&\n a.showcase.enabled === b.showcase.enabled &&\n a.onboarding.mode === b.onboarding.mode &&\n a.review.enabled === b.review.enabled &&\n a.questionnaire.enabled === b.questionnaire.enabled &&\n a.announcements.enabled === b.announcements.enabled);\n","/**\n * The last-known-flags cache — persisted through the host's optional `storage`.\n *\n * WHY: the fetch is best-effort and fail-open. When the control plane is unreachable we serve the\n * LAST value we saw rather than snapping to defaults, so a tenant that intentionally disabled a\n * module during an incident keeps it disabled across an outage instead of flapping back on. That\n * is \"stale-while-error\": a fresh cache (< TTL) is trusted, and on a fetch error even a STALE\n * cache is served (any age beats a guess). Every operation is best-effort — a broken/hung storage\n * degrades to defaults, never a throw and never a stuck gate (mirrors persistedSession).\n */\nimport { parseWireFeatures } from \"./defaults\";\nimport type { WireFeatures } from \"./types\";\nimport type { WireOnboardingStorage } from \"../session/persistedSession\";\n\n/**\n * TTL for a cached flags entry, aligned to the server's `Cache-Control: private, max-age=300`.\n * A cache younger than this is \"fresh\"; older is \"stale\" (still served on a fetch error).\n */\nexport const DEFAULT_FEATURES_TTL_MS = 300_000;\n\n/** Ceiling on the storage read so a hung adapter degrades to defaults, never a stuck resolve. */\nconst READ_TIMEOUT_MS = 1_500;\n\n/** Storage key for a tenant's cached flags, e.g. `wireai:features:acme`. */\nexport const featuresCacheKey = (appId?: string): string =>\n `wireai:features:${appId && appId.length > 0 ? appId : \"default\"}`;\n\n/** A cached flags entry: the resolved flags + the epoch-ms they were fetched. */\nexport interface CachedFeatures {\n features: WireFeatures;\n /** Age of the entry in ms at read time. */\n ageMs: number;\n}\n\nconst withTimeout = <T>(p: Promise<T>, ms: number): Promise<T | undefined> => {\n let timer: ReturnType<typeof setTimeout>;\n const timeout = new Promise<undefined>((resolve) => {\n timer = setTimeout(() => resolve(undefined), ms);\n });\n return Promise.race([p, timeout]).finally(() => clearTimeout(timer));\n};\n\n/** Whether a cache entry is still within the TTL (server max-age aligned). */\nexport const isFeaturesFresh = (ageMs: number, ttlMs: number = DEFAULT_FEATURES_TTL_MS): boolean =>\n ageMs >= 0 && ageMs < ttlMs;\n\n/**\n * Read the cached flags for `key`, or `undefined` when absent/corrupt/timed-out. Returns the\n * value AND its age so a caller can apply a TTL (fresh) or ignore it (stale-while-error). Never\n * throws; never hangs past the read timeout. The stored value is re-parsed through\n * `parseWireFeatures`, so a partially-corrupt entry still degrades fail-open.\n */\nexport const readCachedFeatures = async (\n storage: WireOnboardingStorage,\n key: string,\n): Promise<CachedFeatures | undefined> => {\n let raw: string | null | undefined;\n try {\n raw = await withTimeout(storage.getItem(key), READ_TIMEOUT_MS);\n } catch {\n return undefined;\n }\n if (!raw) return undefined;\n try {\n const parsed: unknown = JSON.parse(raw);\n if (parsed && typeof parsed === \"object\" && typeof (parsed as { ts?: unknown }).ts === \"number\") {\n const ts = (parsed as { ts: number }).ts;\n return {\n features: parseWireFeatures((parsed as { features?: unknown }).features),\n ageMs: Date.now() - ts,\n };\n }\n } catch {\n // Corrupt entry → treat as absent; the caller falls back to defaults / a fresh fetch.\n }\n return undefined;\n};\n\n/** Persist `{ features, ts }` under `key` — fire-and-forget, all errors swallowed. */\nexport const writeCachedFeatures = (\n storage: WireOnboardingStorage,\n key: string,\n features: WireFeatures,\n): void => {\n try {\n void storage\n .setItem(key, JSON.stringify({ features, ts: Date.now() }))\n .catch(() => {});\n } catch {\n // Best-effort: a failed write just means the next launch has no cache to fall back to.\n }\n};\n","/**\n * fetchWireFeatures — read the per-module kill switches from `GET {serverUrl}/v1/features`.\n *\n * Contract (server: chohra-med/wireai-onboarding-server, routers/onboarding.py):\n * GET {serverUrl}/v1/features\n * Authorization: Bearer {apiKey}\n * 200 → { coachmarks:{enabled}, showcase:{enabled}, onboarding:{mode}, review:{enabled},\n * announcements:{enabled} } (Cache-Control: private, max-age=300)\n *\n * THE CONTRACT — fail-open, never fail-dark. Unreachable, timed-out, unauthorized (401),\n * server-error (5xx), or malformed JSON all resolve to the LAST CACHED value, else the\n * all-on defaults. No throw ever reaches the host: this is timeout-capped and swallows every\n * error, exactly like the session's best-effort reads. A control-plane outage can only ever be\n * MORE permissive than the dashboard intends — it can never dark a tenant's activation.\n */\nimport { readCachedFeatures, featuresCacheKey, writeCachedFeatures } from \"./cache\";\nimport { defaultWireFeatures, parseWireFeatures } from \"./defaults\";\nimport type { WireFeatures, WireFeaturesConfig } from \"./types\";\nimport type { WireOnboardingStorage } from \"../session/persistedSession\";\n\nconst DEFAULT_TIMEOUT_MS = 4_000;\n\n/** The fail-open fallback: last cached value (any age — stale-while-error), else defaults. */\nconst failOpen = async (\n storage: WireOnboardingStorage | undefined,\n key: string,\n): Promise<WireFeatures> => {\n if (!storage) return defaultWireFeatures;\n const cached = await readCachedFeatures(storage, key);\n return cached?.features ?? defaultWireFeatures;\n};\n\n/** GET with a hard timeout via AbortController (falls back to no-abort where unsupported). */\nconst fetchWithTimeout = async (\n url: string,\n apiKey: string,\n timeoutMs: number,\n): Promise<Response> => {\n const controller =\n typeof AbortController !== \"undefined\" ? new AbortController() : undefined;\n const timer = setTimeout(() => controller?.abort(), timeoutMs);\n try {\n return await fetch(url, {\n method: \"GET\",\n headers: { Accept: \"application/json\", Authorization: `Bearer ${apiKey}` },\n signal: controller?.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n};\n\n/**\n * Resolve the tenant's feature flags. Attempts the network once; on ANY failure returns the\n * cached value (any age), else the all-on defaults. On success, persists the fresh value to the\n * cache (fire-and-forget) and returns it. Never throws.\n *\n * const flags = await fetchWireFeatures({ serverUrl, apiKey, appId, storage });\n * if (!flags.coachmarks.enabled) { /* skip the tour * / }\n */\nexport const fetchWireFeatures = async (\n config?: WireFeaturesConfig,\n): Promise<WireFeatures> => {\n const key = featuresCacheKey(config?.appId);\n const storage = config?.storage;\n\n // Nothing to fetch (no creds) → cache, else defaults. Lets a host mount the provider before\n // its config is ready without ever darking.\n if (!config?.serverUrl || !config?.apiKey) return failOpen(storage, key);\n\n const url = `${config.serverUrl.replace(/\\/$/, \"\")}/v1/features`;\n const timeoutMs = config.timeoutMs && config.timeoutMs > 0 ? config.timeoutMs : DEFAULT_TIMEOUT_MS;\n\n try {\n const res = await fetchWithTimeout(url, config.apiKey, timeoutMs);\n // 401 (bad/rotated key), 5xx, any non-2xx → last-known, never dark.\n if (!res || !res.ok) return failOpen(storage, key);\n const json: unknown = await res.json(); // throws on malformed body → caught below\n const features = parseWireFeatures(json);\n if (storage) writeCachedFeatures(storage, key, features);\n return features;\n } catch {\n // Timeout/abort, network error, missing fetch, or malformed JSON — all fail open.\n return failOpen(storage, key);\n }\n};\n","/**\n * useWireFeatures — resolve the tenant's feature flags for a component tree, once per mount.\n *\n * Returns the all-on defaults immediately (fail-open), then swaps to the fetched value when the\n * network resolves. When no `serverUrl`+`apiKey` is supplied it never fetches and just returns\n * defaults, so a host that never adopts flags sees ZERO behavior change and ZERO network calls.\n *\n * frequent_rules #11 (unstable inline props): the config is idiomatically passed inline\n * (`config={{ serverUrl, apiKey }}`), a fresh identity every render. We stabilize the fetch key\n * by VALUE so the effect fires once per real credential change, and we only swap the returned\n * flags object when its VALUES change — so a host that lists the returned flags in an effect dep\n * array settles in a bounded number of commits instead of looping (enforced by the canary suite).\n */\nimport { useEffect, useRef, useState } from \"react\";\n\nimport { defaultWireFeatures, featuresEqual } from \"./defaults\";\nimport { fetchWireFeatures } from \"./fetchWireFeatures\";\nimport type { WireFeatures, WireFeaturesConfig } from \"./types\";\n\n/** Hold a stable reference for a value while its CONTENT is unchanged (see reviews/equality.ts). */\nconst useStableValue = <T,>(value: T, isEqual: (a: T, b: T) => boolean): T => {\n const ref = useRef(value);\n if (!isEqual(ref.current, value)) ref.current = value;\n return ref.current;\n};\n\n/** The credential slice that actually drives a refetch — stabilized by value. */\nconst sameFetchKey = (a: WireFeaturesConfig | undefined, b: WireFeaturesConfig | undefined): boolean =>\n a?.serverUrl === b?.serverUrl && a?.apiKey === b?.apiKey && a?.appId === b?.appId;\n\nexport const useWireFeatures = (config?: WireFeaturesConfig): WireFeatures => {\n const stableConfig = useStableValue(config, sameFetchKey);\n const [flags, setFlags] = useState<WireFeatures>(defaultWireFeatures);\n\n const canFetch = !!stableConfig?.serverUrl && !!stableConfig?.apiKey;\n\n useEffect(() => {\n if (!canFetch) {\n // No creds → stay on the all-on defaults (and reset if creds were removed).\n setFlags((prev) => (featuresEqual(prev, defaultWireFeatures) ? prev : defaultWireFeatures));\n return;\n }\n let alive = true;\n // fetchWireFeatures never throws — the .then always runs with a fail-open value.\n void fetchWireFeatures(stableConfig).then((next) => {\n if (!alive) return;\n // Only adopt a NEW reference on a real value change, so a host effect keyed on the\n // returned flags doesn't loop (frequent_rules #11).\n setFlags((prev) => (featuresEqual(prev, next) ? prev : next));\n });\n return () => {\n alive = false;\n };\n }, [canFetch, stableConfig]);\n\n return flags;\n};\n","/**\n * WireFeaturesProvider + useResolvedFeatures — the OPTIONAL one-fetch-serves-all-modules layer.\n *\n * Mount `WireFeaturesProvider` once near your app root with the tenant `config` and it fetches the\n * flags ONCE; every gated surface below it (coachmarks / showcase / reviews) reads the same\n * resolved value from context instead of each fetching its own. It is entirely optional: with no\n * provider, every surface falls back to its own lazy fetch (when given a config) or to the all-on\n * defaults, so modules work with zero setup and a host that never adopts flags is unchanged.\n *\n * NOTE on the built npm subpaths: React context is shared across the kit's subpath entries in the\n * SOURCE-consumption path (the monorepo apps) and in this test suite — one module instance, one\n * context. If you consume the built `@wireai/activation/*` subpaths as separate bundles, pass the\n * resolved flags (or a `featuresConfig`) to each surface explicitly instead of relying on context.\n */\nimport React, { createContext, useContext, useMemo } from \"react\";\n\nimport { defaultWireFeatures } from \"./defaults\";\nimport { useWireFeatures } from \"./useWireFeatures\";\nimport type { WireFeatures, WireFeaturesConfig } from \"./types\";\n\n/**\n * @globalSlot LATCH — the React context object is minted by the first copy of this module to load\n * and every later copy ADOPTS it (the `if (!…)` guard below). A second write would re-split the\n * identity the guard exists to keep whole: the provider would publish into one context while a\n * subpath bundle's `useContext` read another, and `useResolvedFeatures`' null fallback would serve\n * the all-on defaults with no crash and no warning. Because it is a latch, and only because of\n * that, the module-local `const WireFeaturesContext` below may cache it.\n */\nconst CONTEXT_SYMBOL = Symbol.for(\"wireai.features.context\");\n// `globalThis`, not `global`: the other nine `Symbol.for` slots in this kit all use it, and `global`\n// simply does not exist in a plain browser (RN-web / SSR-in-the-browser), where this line would\n// throw a ReferenceError before the provider could render anything.\nconst globalObj = globalThis as any;\n\nif (!globalObj[CONTEXT_SYMBOL]) {\n globalObj[CONTEXT_SYMBOL] = createContext<WireFeatures | null>(null);\n}\nconst WireFeaturesContext = globalObj[CONTEXT_SYMBOL];\n\nexport interface WireFeaturesProviderProps {\n /** Tenant creds (+ optional storage) to fetch the flags once for the whole tree. */\n config?: WireFeaturesConfig;\n /**\n * Pre-resolved flags to provide directly instead of fetching (e.g. you already have them, or\n * you drive them from your own remote-config). Takes precedence over `config`.\n */\n flags?: WireFeatures;\n children: React.ReactNode;\n}\n\n/**\n * Fetch (or accept) the flags once and broadcast them to descendants. The provider VALUE is\n * memoized (performance rule #6) so a fresh object each render doesn't re-render every consumer.\n */\nexport const WireFeaturesProvider: React.FC<WireFeaturesProviderProps> = ({\n config,\n flags,\n children,\n}) => {\n // Always call the hook (rules of hooks); when `flags` is supplied we pass no config so it\n // never fetches and the explicit flags win below.\n const fetched = useWireFeatures(flags ? undefined : config);\n const value = flags ?? fetched;\n const stable = useMemo(() => value, [value]);\n return <WireFeaturesContext.Provider value={stable}>{children}</WireFeaturesContext.Provider>;\n};\n\nWireFeaturesProvider.displayName = \"WireFeaturesProvider\";\n\n/** Raw context read — `null` when no `WireFeaturesProvider` is mounted above. */\nexport const useWireFeaturesContext = (): WireFeatures | null =>\n useContext(WireFeaturesContext);\n\n/** Options for `useResolvedFeatures`: an explicit override and/or a lazy-fetch config. */\nexport interface ResolveFeaturesOptions {\n /** Explicit flags — win over everything (a surface's own `features` prop). */\n flags?: WireFeatures;\n /** Lazy-fetch config, used ONLY when there is no explicit `flags` and no provider above. */\n config?: WireFeaturesConfig;\n}\n\n/**\n * The resolution every gated surface uses. Precedence:\n * 1. explicit `options.flags` (a surface's own prop) →\n * 2. the nearest `WireFeaturesProvider` (context) →\n * 3. a lazy fetch from `options.config` (serverUrl + apiKey) →\n * 4. the all-on defaults.\n * Always fail-open, always a stable reference for a given value (so it's safe in effect deps).\n */\nexport const useResolvedFeatures = (options?: ResolveFeaturesOptions): WireFeatures => {\n const ctx = useWireFeaturesContext();\n // Only lazily fetch when nothing else provides the flags — otherwise pass no config so the\n // hook stays inert (defaults, no network). The hook is always called (rules of hooks).\n const shouldFetch = !options?.flags && ctx == null;\n const fetched = useWireFeatures(shouldFetch ? options?.config : undefined);\n return options?.flags ?? ctx ?? fetched ?? defaultWireFeatures;\n};\n","/**\n * equality.ts — tiny, dependency-free value-equality helpers used to STABILIZE\n * references across renders.\n *\n * WHY this exists: real integrators pass inline objects — `config={{ id: 'x' }}`,\n * `decision={{ fire: true }}` — whose IDENTITY changes every render even though\n * their VALUES do not. If the review hook let those unstable references flow into\n * a `useMemo`/`useEffect` dependency list (or handed a fresh verdict object back\n * every render), a host that depends on the returned value would re-run its own\n * effect forever → \"Maximum update depth exceeded\". A library must tolerate inline\n * props without looping; these helpers are how the hook holds a stable reference\n * whenever the underlying values are unchanged.\n *\n * Pure (no React import) so it runs under the kit's node:test runner.\n */\nimport type { ReviewDecision } from \"./types\";\n\n/**\n * One-level value-equality of two plain objects (or primitives). Same-reference is\n * equal; two objects are equal when they have the same keys and each value is\n * `Object.is`-equal. Good enough for the flat `config` / `decision` shapes the gate\n * accepts (all leaf values are primitives).\n */\nexport const shallowEqual = (a: unknown, b: unknown): boolean => {\n if (Object.is(a, b)) return true;\n if (\n a == null ||\n b == null ||\n typeof a !== \"object\" ||\n typeof b !== \"object\"\n ) {\n return false;\n }\n const ak = Object.keys(a as Record<string, unknown>);\n const bk = Object.keys(b as Record<string, unknown>);\n if (ak.length !== bk.length) return false;\n for (const k of ak) {\n if (!Object.is((a as Record<string, unknown>)[k], (b as Record<string, unknown>)[k])) {\n return false;\n }\n }\n return true;\n};\n\n/**\n * Structural equality of two review verdicts by their contract fields (`fire`,\n * `reason`). A `null`/`undefined` verdict equals only another absent one. Used to\n * decide whether the hook can keep returning the SAME verdict object (so a host\n * that lists `controller.decision` in an effect dep array never loops).\n */\nexport const sameDecision = (a?: ReviewDecision | null, b?: ReviewDecision | null): boolean => {\n if (a == null || b == null) return a == b;\n return a.fire === b.fire && a.reason === b.reason;\n};\n","/**\n * Runtime helpers for the review gate: the `wire_review_*` gate keys and small\n * typed storage accessors. The gate reuses the SAME injected sync storage + global\n * testing flag as the coachmark engine (see coachmarks/runtime), so a host that\n * mounted CoachmarkProvider gets review gating for free — no second storage to wire.\n */\nimport type { CoachmarkStorage } from \"../coachmarks/types\";\nimport { getCoachmarkStorage } from \"../coachmarks/runtime\";\nimport { getCurrentSessionId } from \"../analytics/currentSession\";\nimport { makeSessionId } from \"../analytics/reportClientEvent\";\n\n/** Once-gate key. Keyed by app version when `oncePerVersion` is on, so a new release re-enables. */\nexport const reviewSeenKey = (id: string, version?: string): string =>\n version ? `wire_review_${id}_${version}_seen` : `wire_review_${id}_seen`;\n\n/** Last-shown timestamp key (epoch ms), for the cooldown rule. */\nexport const reviewLastShownKey = (id: string): string => `wire_review_${id}_last`;\n\n/** Session-count key, incremented once per APP-OPEN (not per mount), for the min-sessions rule. */\nexport const reviewSessionsKey = (id: string): string => `wire_review_${id}_sessions`;\n\n/** Companion key holding the open id the counter was LAST incremented for (see {@link bumpSessionCount}). */\nexport const reviewSessionOpenKey = (id: string): string => `wire_review_${id}_open`;\n\n/** Resolve the storage to use: an explicit override, else the provider-injected singleton. */\nexport const resolveStorage = (\n override?: CoachmarkStorage | null,\n): CoachmarkStorage | null => override ?? getCoachmarkStorage();\n\n/** RN sets this global; absent under node/SSR. Read defensively via {@link warnMissingGateStorage}. */\ndeclare const __DEV__: boolean | undefined;\n\n/** Once-per-process latch on its own `Symbol.for` slot: a plain `let` would warn once per inlined\n * bundle copy, and the gates live in three of them.\n *\n * @globalSlot LATCH — written `true` by the first gate that found no sync storage, and never again.\n * A second differing write re-arms a warning the process already spent, so a host that mounts three\n * gates gets the same paragraph three times and stops reading it. */\nconst NO_STORAGE_WARNED_SLOT: unique symbol = Symbol.for(\n \"@wireai/activation:gateStorageWarned\",\n);\n\ntype GlobalWithGateWarn = typeof globalThis & { [NO_STORAGE_WARNED_SLOT]?: boolean };\n\nconst gateWarnGlobal = globalThis as GlobalWithGateWarn;\n\n/**\n * Warn once, in dev builds only, when a gate has NO sync storage to count with.\n *\n * With no storage `readInt` returns 0, so `bumpSessionCount` answers `1` on every call forever and\n * the fail-closed `minSessions: 2` default can never be met: the gate is pinned shut for the life of\n * the app and nothing anywhere says so. The fix is one line at the app root (mount `CoachmarkProvider`\n * with a sync storage, or pass `storage` to the gate), so name it.\n */\nexport const warnMissingGateStorage = (\n storage: CoachmarkStorage | null,\n gate: \"review\" | \"questionnaire\",\n): void => {\n if (storage) return;\n if (gateWarnGlobal[NO_STORAGE_WARNED_SLOT]) return;\n if (typeof __DEV__ === \"undefined\" || !__DEV__) return;\n if (typeof console === \"undefined\" || !console.warn) return;\n gateWarnGlobal[NO_STORAGE_WARNED_SLOT] = true;\n console.warn(\n `[wireai] the ${gate} gate has no sync storage, so its app-open counter is stuck at 1 and the ` +\n \"fail-closed minSessions rule can never be satisfied — the gate will never fire. Mount \" +\n \"CoachmarkProvider with a sync storage adapter at your app root, or pass `storage` to the gate.\",\n );\n};\n\n/** Test-only: forget the once-per-process no-storage warn latch. */\nexport const resetGateStorageWarning = (): void => {\n gateWarnGlobal[NO_STORAGE_WARNED_SLOT] = undefined;\n};\n\n/** Read an integer from sync storage (0 on a missing/unparseable/throwing read). */\nexport const readInt = (storage: CoachmarkStorage | null, key: string): number => {\n if (!storage) return 0;\n try {\n const raw = storage.getItem(key);\n const n = raw == null ? 0 : parseInt(raw, 10);\n return Number.isFinite(n) ? n : 0;\n } catch {\n return 0;\n }\n};\n\n/** Write an integer to sync storage. Best-effort — a throwing adapter never breaks the gate. */\nexport const writeInt = (storage: CoachmarkStorage | null, key: string, value: number): void => {\n if (!storage) return;\n try {\n storage.setItem(key, String(value));\n } catch {\n // Best-effort.\n }\n};\n\n/** Read a string from sync storage (undefined on a missing/blank/throwing read). */\nconst readStr = (storage: CoachmarkStorage | null, key: string): string | undefined => {\n if (!storage) return undefined;\n try {\n const raw = storage.getItem(key);\n return typeof raw === \"string\" && raw.length > 0 ? raw : undefined;\n } catch {\n return undefined;\n }\n};\n\n// ── The \"which app-open is this\" id ──────────────────────────────────────────────────────────\n//\n// The gates' `minSessions` rule needs a SESSION, and the counter behind it used to increment once\n// per gate MOUNT. A mount is not a session: navigating away from the home feed and back, a tab that\n// unmounts its screen, or React StrictMode's dev double-invoke of the `useState` initializer all\n// bumped it. So `minSessions: 2` — the fail-closed default added after the 2026-07-16 one-star\n// incident — was satisfiable inside the user's FIRST app open, which is the exact thing it exists\n// to prevent. `sessions` was a count of mounts wearing the name of a session.\n//\n// The correct unit already exists: `getCurrentSessionId()`, the per-open id the server sees on\n// `app.session_started`. When a host wires the lifecycle events, the counter keys off THAT and is\n// exactly \"distinct app-opens\", the same unit the server's own `min_sessions` uses.\n//\n// When no host wired lifecycle events there is no registered open, so we fall back to a PROCESS-\n// scoped id: one JS process is one app launch, which is still a genuine app-open and is strictly\n// closer to a session than a mount is. It lives in a `globalThis` slot keyed by `Symbol.for(...)`\n// for the same reason `currentSession` does: tsup inlines this module into several bundles and a\n// plain module-local `let` would give each bundle its own \"process\".\n//\n// ── WHY THE UNIT IS PINNED FOR THE WHOLE LAUNCH ──────────────────────────────────────────────\n// The first shape of this function read the two tiers LIVE on every call: the registered session id\n// when there was one, else the process id. That let the UNIT change mid-launch, and React's own\n// ordering guarantees it does. The gates call `bumpSessionCount` from a `useState` INITIALIZER,\n// which runs during render; `useLifecycleEvents` registers the session id from a root EFFECT; and\n// React runs every render before any effect. So a cold start went:\n//\n// render: gate initializer → no session registered yet → PROCESS id → count = 1, open = <process>\n// effect: root lifecycle → setCurrentSessionId(<session id>)\n// remount: gate initializer → SESSION id ≠ the stored open → count = 2\n//\n// Two \"sessions\" inside one app open, which makes the fail-closed `minSessions: 2` default (added\n// after the 2026-07-16 one-star incident) satisfiable in the very launch it exists to guard. So the\n// FIRST read pins whatever it resolved into the process slot and every later read returns that,\n// regardless of what the session registry does afterwards. The client counter only has to be\n// monotone and per-launch; the server's own `min_sessions` still counts real `app.session_started`\n// events, so nothing downstream needs the two ids to be identical.\n//\n// @globalSlot LATCH — pinned by the FIRST read and stable for the whole launch. This is the one\n// slot whose latch is the fix: it samples the LIVE `currentSessionId` once, on purpose, so that\n// registry changing underneath it (render before effect, on every cold start) cannot change the\n// unit mid-launch and hand `minSessions: 2` a second \"session\" inside one app open. A second\n// differing write is therefore not a stale-cache bug, it IS the defect — see the block above.\nconst PROCESS_OPEN_ID_SLOT: unique symbol = Symbol.for(\"@wireai/activation:processOpenId\");\n\ntype GlobalWithOpenId = typeof globalThis & { [PROCESS_OPEN_ID_SLOT]?: string };\n\nconst openIdGlobal = globalThis as GlobalWithOpenId;\n\n/**\n * The id identifying THIS app-open for gate counting, PINNED on first read for the whole launch:\n * the live per-open `session_id` if one was already registered when the first gate asked, else a\n * minted per-process id. Stable from the first render to the last. Never empty.\n */\nexport const currentOpenId = (): string => {\n const existing = openIdGlobal[PROCESS_OPEN_ID_SLOT];\n if (existing) return existing;\n // Adopt the registered session id when the host wired lifecycle BEFORE any gate rendered; that is\n // the same unit the server counts. Otherwise mint one. Either way it is pinned from here on.\n const resolved = getCurrentSessionId() ?? makeSessionId();\n openIdGlobal[PROCESS_OPEN_ID_SLOT] = resolved;\n return resolved;\n};\n\n/** Test-only: forget the process open id so a unit test starts from a clean launch. */\nexport const resetProcessOpenId = (): void => {\n openIdGlobal[PROCESS_OPEN_ID_SLOT] = undefined;\n};\n\n/**\n * Return the app-open count for the gate, incrementing it AT MOST ONCE per app-open.\n *\n * IDEMPOTENT by construction: the open id that last incremented the counter is stored alongside it,\n * so a second call within the same open (a remount, a StrictMode double-invoke, a second gate render)\n * reads the stored count back instead of bumping it. A new open id bumps exactly once.\n *\n * Storage-less hosts get `1` for the first call and `1` for every later call within the open —\n * degraded but never inflating, which is the safe direction for a fail-closed gate.\n */\nexport const bumpSessionCount = (\n storage: CoachmarkStorage | null,\n sessionsKey: string,\n openKey: string,\n openId: string = currentOpenId(),\n): number => {\n const lastOpen = readStr(storage, openKey);\n const stored = readInt(storage, sessionsKey);\n // Already counted this open → return what we counted, do not bump again.\n if (lastOpen === openId) return stored > 0 ? stored : 1;\n const next = stored + 1;\n writeInt(storage, sessionsKey, next);\n if (storage) {\n try {\n storage.setItem(openKey, openId);\n } catch {\n // Best-effort: a failed write only means this open may be counted twice.\n }\n }\n return next;\n};\n","/**\n * useReviewGate — owns WHEN the review gate shows.\n *\n * v1 decision = local rules from `config` (enabled / minSessions / minEvents / cooldownDays\n * / oncePerVersion), evaluated against the same injected sync storage as the coachmark\n * engine (`wire_review_*` keys). The AI seam: pass a server-provided `decision` and it\n * OVERRIDES the local rules (see decideReview). The whole cost of being AI-ready is passing\n * that object straight through.\n *\n * Fallback chain (matches the server design): (1) a server `decision` wins immediately;\n * (2) no decision → after `timeoutFallbackMs` the local rules take over; (3) the once-gate\n * (`wire_review_<id>_seen`) always applies locally, so a server bug can never spam the prompt.\n * The global `isTestingCoachmark` flag force-shows the gate for QA replay.\n */\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport { hasSeenGate, isCoachmarkTesting, markSeenGate } from \"../coachmarks/runtime\";\nimport { useResolvedFeatures } from \"../features/WireFeaturesProvider\";\nimport { decideReview, evaluateGate, resolveRules } from \"./decision\";\nimport { sameDecision, shallowEqual } from \"./equality\";\nimport {\n bumpSessionCount,\n readInt,\n resolveStorage,\n reviewLastShownKey,\n reviewSeenKey,\n reviewSessionOpenKey,\n reviewSessionsKey,\n warnMissingGateStorage,\n writeInt,\n} from \"./runtime\";\nimport type {\n ReviewDecision,\n ReviewGateController,\n UseReviewGateOptions,\n} from \"./types\";\n\n/**\n * Hold a stable reference for a value while its CONTENT is unchanged. Integrators\n * pass inline `config` / `decision` objects (idiomatic React) whose identity flips\n * every render; feeding those straight into the deps below would recompute the\n * verdict — and mint a fresh verdict object — on every render, which loops any host\n * that lists the returned verdict in its own effect. Comparing by value here keeps\n * the reference (and everything derived from it) stable across identity-only churn.\n */\nconst useStableValue = <T,>(value: T, isEqual: (a: T, b: T) => boolean): T => {\n const ref = useRef(value);\n if (!isEqual(ref.current, value)) ref.current = value;\n return ref.current;\n};\n\nexport const useReviewGate = ({\n config: configProp,\n decision: decisionProp,\n events = 0,\n storage,\n isTesting,\n features,\n featuresConfig,\n}: UseReviewGateOptions): ReviewGateController => {\n // Tolerate inline-identity props: only adopt a new reference on a real value change.\n const config = useStableValue(configProp, shallowEqual);\n const decision = useStableValue(\n decisionProp,\n (a, b) => sameDecision(a, b),\n );\n\n // The review MASTER switch. Disabled → the gate never fires, composing as an extra AND over\n // the local rules + the server decision seam below (fail-open: defaults to enabled).\n const featureEnabled = useResolvedFeatures({ flags: features, config: featuresConfig }).review.enabled;\n\n const testing = isTesting ?? isCoachmarkTesting();\n const seenKey = reviewSeenKey(config.id, config.oncePerVersion === false ? undefined : config.appVersion);\n const lastKey = reviewLastShownKey(config.id);\n const sessionsKey = reviewSessionsKey(config.id);\n const sessionOpenKey = reviewSessionOpenKey(config.id);\n\n // Read (and bump) the app-open counter. IDEMPOTENT per app-open, NOT per mount: `bumpSessionCount`\n // keys off the live per-open session id (or a per-process id when no host wired the lifecycle\n // events), so a remount, a navigation return, or React StrictMode's dev double-invoke of this\n // initializer all read the same number back instead of inflating it. Before this, `sessions`\n // counted mounts, so the fail-closed `minSessions: 2` default could be satisfied inside the user's\n // very first app open — the exact scenario it was added to prevent.\n const sessions = useState(() => {\n const store = resolveStorage(storage);\n // No storage pins the counter at 1 forever, so the fail-closed minSessions rule can never be\n // met and the gate silently never fires. Dev-only, once per process.\n warnMissingGateStorage(store, \"review\");\n return bumpSessionCount(store, sessionsKey, sessionOpenKey);\n })[0];\n\n // Gate the local rules behind an optional client-side timeout, so a reachable server\n // gets a window to answer first. A present `decision` bypasses the wait entirely.\n const hasServerDecision = decision != null;\n const timeoutMs = config.timeoutFallbackMs;\n const waits = !hasServerDecision && timeoutMs != null && timeoutMs > 0;\n const [elapsed, setElapsed] = useState(!waits);\n useEffect(() => {\n if (!waits) {\n setElapsed(true);\n return;\n }\n const timer = setTimeout(() => setElapsed(true), timeoutMs as number);\n return () => clearTimeout(timer);\n }, [waits, timeoutMs]);\n\n const { visible, verdict } = useMemo(() => {\n // Master kill switch wins over everything, including QA replay: a tenant that turned reviews\n // OFF must never see the gate, whatever the rules, the server decision, or testing say.\n if (!featureEnabled) return { visible: false, verdict: { fire: false, reason: \"feature_disabled\" } as ReviewDecision };\n\n // QA replay: force-show, bypassing rules + gates (safe: only via __DEV__ && bool).\n if (testing) return { visible: true, verdict: { fire: true, reason: \"testing\" } as ReviewDecision };\n\n const store = resolveStorage(storage);\n const local = evaluateGate(resolveRules(config), {\n sessions,\n events,\n lastShownAt: readInt(store, lastKey) || null,\n seen: hasSeenGate(seenKey, storage, isTesting),\n now: Date.now(),\n });\n const resolved = decideReview(local, decision);\n // (3) the once-gate always wins locally, even over a server \"fire\".\n const alreadySeen = hasSeenGate(seenKey, storage, isTesting);\n const ready = hasServerDecision || elapsed;\n return { visible: ready && resolved.fire && !alreadySeen, verdict: resolved };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [featureEnabled, testing, config, decision, events, sessions, elapsed, hasServerDecision, seenKey, lastKey, storage, isTesting]);\n\n // Stable callbacks + a stable controller object, so a host can safely list any of\n // them in its own effect deps without the effect re-firing every render.\n const shownRef = useRef(false);\n const markShown = useCallback(() => {\n if (shownRef.current) return;\n shownRef.current = true;\n writeInt(resolveStorage(storage), lastKey, Date.now());\n }, [storage, lastKey]);\n const markResolved = useCallback(() => {\n markSeenGate(seenKey, storage, isTesting);\n }, [seenKey, storage, isTesting]);\n\n return useMemo<ReviewGateController>(\n () => ({ visible, decision: verdict, markShown, markResolved }),\n [visible, verdict, markShown, markResolved],\n );\n};\n"]}