@wireai/activation 0.12.0 → 0.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +13 -10
- package/CHANGELOG.md +130 -0
- package/INTEGRATION_PROMPT.md +14 -3
- package/README.md +40 -2
- package/dist/analytics/index.d.mts +5 -4
- package/dist/analytics/index.d.ts +5 -4
- package/dist/analytics/index.js +39 -24
- package/dist/analytics/index.js.map +1 -1
- package/dist/analytics/index.mjs +39 -24
- package/dist/analytics/index.mjs.map +1 -1
- package/dist/coachmarks/index.d.mts +16 -0
- package/dist/coachmarks/index.d.ts +16 -0
- package/dist/coachmarks/index.js +19 -13
- package/dist/coachmarks/index.js.map +1 -1
- package/dist/coachmarks/index.mjs +19 -13
- package/dist/coachmarks/index.mjs.map +1 -1
- package/dist/{currentSession-DsSDHqor.d.mts → currentSession-BlCeDP0f.d.mts} +59 -5
- package/dist/{currentSession-D6RiVtc8.d.ts → currentSession-BxEB37xt.d.ts} +59 -5
- package/dist/index.d.mts +10 -20
- package/dist/index.d.ts +10 -20
- package/dist/index.js +301 -187
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +300 -188
- package/dist/index.mjs.map +1 -1
- package/dist/questionnaire/index.js +41 -19
- package/dist/questionnaire/index.js.map +1 -1
- package/dist/questionnaire/index.mjs +41 -19
- package/dist/questionnaire/index.mjs.map +1 -1
- package/dist/reviews/index.js +41 -19
- package/dist/reviews/index.js.map +1 -1
- package/dist/reviews/index.mjs +41 -19
- package/dist/reviews/index.mjs.map +1 -1
- package/dist/showcase/index.js +15 -6
- package/dist/showcase/index.js.map +1 -1
- package/dist/showcase/index.mjs +15 -6
- package/dist/showcase/index.mjs.map +1 -1
- package/llms.txt +2 -0
- package/package.json +1 -1
- package/src/WireOnboarding.tsx +140 -5
- package/src/activation/wireActivation.ts +8 -1
- package/src/analytics/analyticsFacade.ts +15 -11
- package/src/analytics/reportClientEvent.ts +11 -7
- package/src/coachmarks/runtime.ts +53 -17
- package/src/config/wireConfigFromEnv.ts +46 -2
- package/src/context/deviceId.ts +82 -18
- package/src/index.ts +9 -1
- package/src/questionnaire/runtime.ts +1 -0
- package/src/questionnaire/useQuestionnaireGate.ts +8 -3
- package/src/reviews/runtime.ts +68 -7
- package/src/reviews/useReviewGate.ts +8 -3
- package/src/session-analytics/useLifecycleEvents.ts +57 -27
- package/src/session-analytics/useSessionStart.ts +37 -1
- package/src/types.ts +41 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/coachmarks/GestureHint.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/theme/defaultTheme.ts","../../src/theme/ThemeContext.tsx","../../src/showcase/showcaseColors.ts","../../src/showcase/FeatureShowcase.tsx","../../src/showcase/selectShowcaseSlides.ts"],"names":["useEffect","createContext","useContext","useMemo","useRef","useState","_a","jsx","View","styles","StyleSheet","jsxs","React"],"mappings":"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","file":"index.mjs","sourcesContent":["import React, { useEffect, useMemo, type ReactNode } from \"react\";\nimport { StyleSheet, View } from \"react-native\";\nimport Animated, {\n useAnimatedStyle,\n useReducedMotion,\n useSharedValue,\n withDelay,\n withRepeat,\n withSequence,\n withTiming,\n} from \"react-native-reanimated\";\n\nimport type { GestureKind } from \"./types\";\n\n/**\n * Abstract gesture pictogram shown beside a coachmark tooltip. Pure reanimated —\n * every animation runs on the UI thread (shared values + worklets), never\n * setState, so it costs nothing on the JS thread. Honors Reduce Motion by\n * dropping the motion loop and rendering the glyph static.\n *\n * NO drawn-hand anatomy. Swipes render the standard two-finger swipe glyph: two\n * rounded vertical capsules (an abstract index + middle finger, slightly\n * different heights) with a short, fading motion track trailing behind them along\n * the swipe axis. The whole glyph is rotated per direction (up/down/left/right)\n * and the capsules slide along the axis, fading, in a UI-thread loop. Pinch/zoom\n * reuse the two-capsule abstraction spreading apart. Tap / double-tap render a\n * minimal pulsing tap-target (ring + dot).\n *\n * Every glyph is a self-drawn primitive built from Views/borders (no vector-icon\n * dependency, like AnimatedSparkle). Pass `renderHand` — the glyph slot — to\n * substitute your own node while keeping the tuned motion. (The name is kept for\n * backward compatibility; despite it, the default is a glyph, not a drawn hand.)\n *\n * NOTE: the coachmark path (SpotlightOverlay) and the showcase path\n * (FeatureShowcase) both SKIP this component for `tap` / `double_tap` — those\n * steps show only the highlight ring + tooltip, no glyph. The tap visual here\n * exists for direct / standalone use.\n */\nexport interface GestureHintProps {\n type: GestureKind;\n /** Overall glyph size in px. */\n size?: number;\n /** Glyph color. Defaults to white (reads on the dark blur backdrop). */\n color?: string;\n /**\n * Provide a custom glyph node in place of the default self-drawn primitive; it\n * inherits the tuned motion (slide+fade for swipes, pulse for tap). Kept as\n * `renderHand` for backward compatibility — despite the name it is just the\n * glyph slot, no longer a drawn hand.\n */\n renderHand?: (props: { size: number; color: string }) => ReactNode;\n}\n\n/** Travel distance for the directional swipe loop (local glyph axis). */\nconst TRAVEL = 26;\nconst SWIPE_MS = 700;\nconst TAP_MS = 320;\nconst SPREAD_MS = 620;\n\n/**\n * Per-direction rotation of the whole glyph. Canonical orientation is \"up\":\n * capsules point up, travel up, motion track trailing below. Rotating the\n * container reorients both the capsules and the track together, and rotates the\n * local translate axis so the same upward loop reads correctly per direction.\n */\nconst GLYPH_ROTATION: Record<string, string> = {\n swipe_up: \"0deg\",\n swipe_down: \"180deg\",\n swipe_left: \"-90deg\",\n swipe_right: \"90deg\",\n};\n\n/**\n * The two-finger abstraction: two rounded vertical capsules side by side with\n * slightly different heights (index taller than middle), aligned at the base —\n * the standard swipe pictogram look. Self-drawn from Views only.\n */\nconst FingerPair: React.FC<{ size: number; color: string }> = ({ size, color }) => {\n const { row, index, middle } = useMemo(() => {\n const w = size * 0.2;\n const r = w / 2;\n return {\n row: { flexDirection: \"row\" as const, alignItems: \"flex-end\" as const, gap: size * 0.08 },\n index: { width: w, height: size * 0.54, borderRadius: r, backgroundColor: color },\n middle: { width: w, height: size * 0.44, borderRadius: r, backgroundColor: color },\n };\n }, [size, color]);\n\n return (\n <View style={row} pointerEvents=\"none\">\n <View style={index} />\n <View style={middle} />\n </View>\n );\n};\n\n/**\n * A short, tapering motion track that sits behind the capsules (below, in the\n * canonical \"up\" orientation) — two slim, fading ticks that shrink and dim with\n * distance. Subtle: it reads as the path the fingers just travelled, not a comet\n * tail. Static (the capsules carry the motion via the slide loop).\n */\nconst MotionTrack: React.FC<{ size: number; color: string }> = ({ size, color }) => {\n const { wrap, tick0, tick1 } = useMemo(() => {\n const h = Math.max(2, size * 0.05);\n return {\n wrap: { alignItems: \"center\" as const, marginTop: size * 0.1, gap: size * 0.06 },\n tick0: {\n width: size * 0.3,\n height: h,\n borderRadius: h / 2,\n backgroundColor: color,\n opacity: 0.4,\n },\n tick1: {\n width: size * 0.18,\n height: h,\n borderRadius: h / 2,\n backgroundColor: color,\n opacity: 0.16,\n },\n };\n }, [size, color]);\n\n return (\n <View style={wrap} pointerEvents=\"none\">\n <View style={tick0} />\n <View style={tick1} />\n </View>\n );\n};\n\nconst GestureHintComponent: React.FC<GestureHintProps> = ({\n type,\n size = 34,\n color = \"white\",\n renderHand,\n}) => {\n const translate = useSharedValue(0);\n const opacity = useSharedValue(1);\n const pulse = useSharedValue(1);\n const spread = useSharedValue(0);\n const reduced = useReducedMotion();\n\n const isSwipe =\n type === \"swipe_up\" ||\n type === \"swipe_down\" ||\n type === \"swipe_left\" ||\n type === \"swipe_right\";\n const isSpread = type === \"pinch\" || type === \"zoom\";\n const isTap = type === \"tap\" || type === \"double_tap\";\n\n useEffect(() => {\n if (type === \"none\" || reduced) return undefined;\n\n if (isTap) {\n pulse.value = withRepeat(\n withSequence(\n withTiming(0.82, { duration: TAP_MS }),\n withTiming(1, { duration: TAP_MS }),\n ),\n -1,\n false,\n );\n opacity.value = withRepeat(\n withSequence(\n withTiming(0.55, { duration: TAP_MS }),\n withTiming(1, { duration: TAP_MS }),\n ),\n -1,\n false,\n );\n return undefined;\n }\n\n if (isSpread) {\n // Two capsules easing apart and back — a minimal pinch/zoom abstraction.\n spread.value = withRepeat(\n withSequence(\n withTiming(size * 0.26, { duration: SPREAD_MS }),\n withTiming(0, { duration: SPREAD_MS }),\n ),\n -1,\n false,\n );\n return undefined;\n }\n\n // Directional swipe: slide the capsules along the local \"up\" axis, fading\n // out, then snap back and repeat. The container rotation (below) reorients\n // this loop per direction, so the local motion is always the same.\n translate.value = withRepeat(\n withSequence(\n withTiming(0, { duration: 0 }),\n withTiming(-TRAVEL, { duration: SWIPE_MS }),\n withDelay(140, withTiming(0, { duration: 0 })),\n ),\n -1,\n false,\n );\n opacity.value = withRepeat(\n withSequence(\n withTiming(1, { duration: SWIPE_MS / 2 }),\n withTiming(0, { duration: SWIPE_MS / 2 }),\n withDelay(140, withTiming(1, { duration: 0 })),\n ),\n -1,\n false,\n );\n return undefined;\n }, [type, reduced, isTap, isSpread, translate, opacity, pulse, spread, size]);\n\n const swipeAnimatedStyle = useAnimatedStyle(() => ({\n opacity: opacity.value,\n transform: [{ translateY: translate.value }],\n }));\n const tapAnimatedStyle = useAnimatedStyle(() => ({\n opacity: opacity.value,\n transform: [{ scale: pulse.value }],\n }));\n const spreadLeftStyle = useAnimatedStyle(() => ({\n transform: [{ translateX: -spread.value }],\n }));\n const spreadRightStyle = useAnimatedStyle(() => ({\n transform: [{ translateX: spread.value }],\n }));\n\n if (type === \"none\") return null;\n\n const customGlyph = renderHand ? renderHand({ size, color }) : null;\n\n if (isSwipe) {\n return (\n <View\n style={{ transform: [{ rotate: GLYPH_ROTATION[type] ?? \"0deg\" }] }}\n pointerEvents=\"none\"\n >\n <View style={styles.swipeStack}>\n <Animated.View style={swipeAnimatedStyle} pointerEvents=\"none\">\n {customGlyph ?? <FingerPair size={size} color={color} />}\n </Animated.View>\n <MotionTrack size={size} color={color} />\n </View>\n </View>\n );\n }\n\n if (isSpread) {\n const w = size * 0.2;\n const capsule = {\n width: w,\n height: size * 0.5,\n borderRadius: w / 2,\n backgroundColor: color,\n };\n return (\n <View style={[styles.spreadRow, { gap: size * 0.12 }]} pointerEvents=\"none\">\n <Animated.View style={spreadLeftStyle}>\n <View style={capsule} />\n </Animated.View>\n <Animated.View style={spreadRightStyle}>\n <View style={capsule} />\n </Animated.View>\n </View>\n );\n }\n\n // tap / double_tap — a minimal pulsing tap-target (ring + center dot).\n const ringSize = size * 0.86;\n const ringBorder = Math.max(2, size * 0.08);\n const dotSize = size * 0.32;\n return (\n <View style={[styles.tapWrap, { width: size, height: size }]} pointerEvents=\"none\">\n {customGlyph ? (\n <Animated.View style={tapAnimatedStyle} pointerEvents=\"none\">\n {customGlyph}\n </Animated.View>\n ) : (\n <>\n <Animated.View\n style={[\n styles.tapRing,\n {\n width: ringSize,\n height: ringSize,\n borderRadius: ringSize / 2,\n borderWidth: ringBorder,\n borderColor: color,\n },\n tapAnimatedStyle,\n ]}\n pointerEvents=\"none\"\n />\n <View\n style={{\n position: \"absolute\",\n width: dotSize,\n height: dotSize,\n borderRadius: dotSize / 2,\n backgroundColor: color,\n }}\n />\n </>\n )}\n </View>\n );\n};\n\nconst styles = StyleSheet.create({\n swipeStack: {\n alignItems: \"center\",\n justifyContent: \"center\",\n },\n spreadRow: {\n flexDirection: \"row\",\n alignItems: \"center\",\n justifyContent: \"center\",\n },\n tapWrap: {\n alignItems: \"center\",\n justifyContent: \"center\",\n },\n tapRing: {\n backgroundColor: \"transparent\",\n },\n});\n\nexport const GestureHint = React.memo(GestureHintComponent);\nGestureHint.displayName = \"GestureHint\";\n\nexport default GestureHint;\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 */\nimport type { CoachmarkStorage } from \"./types\";\n\nlet storageSingleton: CoachmarkStorage | null = null;\nlet testing = false;\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.\nlet coachmarksEnabled = true;\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 coachmarksEnabled = value;\n};\n\n/** Whether the coachmarks module is enabled. False → tours/overlays are silently skipped. */\nexport const areCoachmarksEnabled = (): boolean => coachmarksEnabled;\n\n/** Set (or clear) the injected sync gate storage. Called by CoachmarkProvider. */\nexport const setCoachmarkStorage = (storage: CoachmarkStorage | null): void => {\n storageSingleton = storage;\n};\n\n/** The currently-injected gate storage, or null if no provider is mounted. */\nexport const getCoachmarkStorage = (): CoachmarkStorage | null => storageSingleton;\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 testing = value;\n};\n\n/** Whether replay-everything QA mode is on. */\nexport const isCoachmarkTesting = (): boolean => 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 ?? testing) return false;\n const storage = storageOverride ?? storageSingleton;\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 ?? testing) return;\n const storage = storageOverride ?? storageSingleton;\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 * 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","import type { OnboardingColors } from \"@blazejkustra/react-native-onboarding\";\n\nimport type { OnboardingTheme } from \"../theme/types\";\n\n/**\n * Map the kit's semantic `OnboardingTheme` onto the color contract of\n * `@blazejkustra/react-native-onboarding` (what `FeatureShowcase` wraps).\n *\n * ⚠️ CRITICAL — that package's color NAMES do NOT line up with the kit's roles.\n * From its source (`utils/theme.ts` + the button/panel components):\n * • `background.primary` → the PRIMARY BUTTON background, the highlighted intro\n * title word (`OnboardingIntroPanel`), and the image\n * hero panel (`OnboardingImageContainer`). It is the\n * package's BRAND / ACTION color — NOT a screen bg.\n * • `background.secondary` → the screen / modal / step-panel background.\n * • `background.label` → the step-label chip background.\n * • `background.accent` → the SECONDARY (outline) button border ONLY.\n * • `text.contrast` → the primary button label (sits on `background.primary`).\n * • `text.primary` → body copy; `text.secondary` → hints/subtitles.\n *\n * The earlier mapping set `background.primary` to the kit's `background`, which\n * painted the primary CTA in the app's SCREEN background color — an invisible /\n * wrong-colored button (white-on-white in light, dark-on-dark in dark mode). The\n * primary button must be the brand `accent`. Keep this in sync with the package's\n * actual usage, not its key names.\n *\n * ⚠️ CONSEQUENCE of the fix above (the 0.3.2 flood): because `background.primary`\n * is ONE key driving THREE surfaces, painting it `accent` also paints the large\n * IMAGE HERO PANEL (`OnboardingImageContainer`'s `colorBg`) in the accent — a\n * full-bleed wash that dominates the first slide and \"hides\" the app's brand.\n * The package offers no per-surface color key to split them, BUT it does expose a\n * `background?: () => ReactNode` render prop that REPLACES that hero panel fill\n * outright (see `showcasePanelBackground` + `FeatureShowcase`). So the button\n * keeps `accent` here and the panel is calmed separately — one accent value, two\n * different surfaces, cleanly split.\n */\nexport const showcaseColorsFromTheme = (\n t: OnboardingTheme,\n accent: string,\n): OnboardingColors => ({\n background: {\n // Brand action color → primary button bg + intro highlight + image hero panel.\n // (The hero panel is overridden to a calm surface via the package's\n // `background` render prop — see showcasePanelBackground — so in practice\n // this only paints the primary button + the intro highlight word.)\n primary: accent,\n // Neutral screen / modal / step-panel background.\n secondary: t.colors.background,\n // Step-label chip background (a soft wash of the brand).\n label: t.colors.primarySoft,\n // Secondary (outline) button border.\n accent,\n },\n text: {\n primary: t.colors.text,\n secondary: t.colors.textMuted,\n // Primary button label — sits on `background.primary` (the accent).\n contrast: t.colors.onPrimary,\n },\n});\n\n/**\n * Opacity of the accent tint layered over the theme surface for the showcase's\n * image hero panel. Kept in a calm 8–12% band so the panel reads as a subtle\n * brand wash — a hint of the accent, never the full-bleed flood that `accent`\n * as a solid fill produced (the 0.3.1 → 0.3.2 bug seen in a host app).\n */\nexport const SHOWCASE_PANEL_TINT_OPACITY = 0.1;\n\n/** The calm hero-panel background: a neutral surface base + a faint accent tint. */\nexport interface ShowcasePanelBackground {\n /** The calm base fill — the theme SURFACE, deliberately NOT the accent. */\n base: string;\n /** A very subtle accent wash layered over the base (8–12% opacity). */\n tint: { color: string; opacity: number };\n}\n\n/**\n * Background for the showcase's image/hero panel, fed to the underlying\n * package's `background` render prop (which REPLACES its `bg.primary`-filled\n * `colorBg`). Returns a neutral surface base with a faint accent tint on top, so\n * the panel is calm and on-brand instead of an accent flood. The primary button\n * and intro highlight keep the full accent (they read `bg.primary` directly).\n */\nexport const showcasePanelBackground = (\n t: OnboardingTheme,\n accent: string,\n): ShowcasePanelBackground => ({\n base: t.colors.surface,\n tint: { color: accent, opacity: SHOWCASE_PANEL_TINT_OPACITY },\n});\n","import Onboarding, {\n type OnboardingColors,\n type OnboardingFonts,\n type OnboardingProps,\n type OnboardingStep,\n} from \"@blazejkustra/react-native-onboarding\";\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { type ImageSourcePropType, StyleSheet, View } from \"react-native\";\n\nimport { GestureHint } from \"../coachmarks/GestureHint\";\nimport { hasSeenGate, markSeenGate, showcaseGateKey } from \"../coachmarks/runtime\";\nimport { useResolvedFeatures } from \"../features/WireFeaturesProvider\";\nimport { useOnboardingTheme } from \"../theme/ThemeContext\";\nimport type { OnboardingTheme } from \"../theme/types\";\nimport { showcaseColorsFromTheme, showcasePanelBackground } from \"./showcaseColors\";\nimport type { FeatureShowcaseProps } from \"./types\";\n\n/** 1×1 transparent GIF — the fallback for a slide with no image (the underlying\n * package requires an image area per step). */\nconst TRANSPARENT_PX: ImageSourcePropType = {\n uri: \"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\",\n};\n\nconst 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/**\n * FeatureShowcase — the pre-onboarding \"app intro\" slides.\n *\n * Wraps `@blazejkustra/react-native-onboarding`: the app supplies a declarative\n * `ShowcaseConfig` (slides + optional intro panel) and the kit maps it to the\n * package's API, bakes in the Wire onboarding theme colors, overlays the\n * perf-tuned GestureHint on any slide that asks for one, and gates the whole\n * thing once through the same sync storage as the coachmark tours\n * (`wire_showcase_<id>_seen`; the global testing flag bypasses it).\n *\n * When the gate says \"seen\", it renders nothing and calls `onDone` from an\n * effect (never during render).\n */\nconst _FeatureShowcase: React.FC<FeatureShowcaseProps> = ({\n config,\n onDone,\n theme: themeOverride,\n accentColor,\n buttonLabel = \"Next\",\n storage,\n isTesting,\n features,\n featuresConfig,\n}) => {\n const active = useOnboardingTheme();\n const t = useMemo(\n () => mergeThemeOver(active, themeOverride),\n [active, themeOverride],\n );\n const accent = accentColor ?? t.colors.primary;\n\n // Feature kill switch: disabled → treat like \"already seen\" (render nothing, call onDone so the\n // host flow continues) but WITHOUT writing the seen gate, so re-enabling replays the showcase.\n const flags = useResolvedFeatures({ flags: features, config: featuresConfig });\n const disabled = !flags.showcase.enabled;\n\n const gateKey = showcaseGateKey(config.id);\n const seen = useMemo(\n () => hasSeenGate(gateKey, storage, isTesting),\n [gateKey, storage, isTesting],\n );\n // Either already-seen OR feature-disabled short-circuits the showcase. Only the seen path is a\n // gate write (in `finish`); the disabled path never persists, so it replays when re-enabled.\n const skip = seen || disabled;\n\n const doneRef = useRef(false);\n const finish = useCallback(() => {\n if (doneRef.current) return;\n doneRef.current = true;\n markSeenGate(gateKey, storage, isTesting);\n onDone();\n }, [gateKey, storage, isTesting, onDone]);\n\n // Already seen OR disabled → skip without rendering; call onDone from an effect so the host's\n // navigation continues (never write the gate here — `finish` owns the seen write).\n useEffect(() => {\n if (skip && !doneRef.current) {\n doneRef.current = true;\n onDone();\n }\n }, [skip, onDone]);\n\n const [activeIndex, setActiveIndex] = useState(0);\n\n const steps: OnboardingStep[] = useMemo(\n () =>\n config.slides.map((slide) => ({\n title: slide.title,\n description: slide.description,\n buttonLabel: slide.buttonLabel ?? buttonLabel,\n image: slide.image ?? TRANSPARENT_PX,\n position: slide.imagePosition ?? \"top\",\n })),\n [config.slides, buttonLabel],\n );\n\n const introPanel: OnboardingProps[\"introPanel\"] = useMemo(\n () =>\n config.introPanel\n ? {\n title: config.introPanel.title,\n subtitle: config.introPanel.subtitle,\n button: config.introPanel.button,\n image: config.introPanel.image,\n }\n : { button: buttonLabel },\n [config.introPanel, buttonLabel],\n );\n\n // The package's color KEYS don't match the kit's role names — most importantly\n // its `background.primary` drives the PRIMARY BUTTON (not the screen bg), so the\n // accent must land there. See showcaseColorsFromTheme for the full contract.\n const colors: OnboardingColors = useMemo(\n () => showcaseColorsFromTheme(t, accent),\n [t, accent],\n );\n\n // Calm the large image/hero panel. The package's `background.primary` is a\n // SINGLE key that fills the primary button, the intro highlight AND this hero\n // panel — painting it `accent` (correct for the button) also flooded the first\n // slide with the app color (0.3.1). The package exposes a\n // `background` render prop that REPLACES that panel fill outright, so we keep\n // the button accent (via `colors`) and paint the panel a calm surface + faint\n // accent tint here. See showcasePanelBackground.\n const panelBg = useMemo(() => showcasePanelBackground(t, accent), [t, accent]);\n const renderPanelBackground = useCallback(\n () => (\n <View\n style={[\n styles.panelBase,\n { backgroundColor: panelBg.base, borderRadius: t.radius.md },\n ]}\n >\n <View\n style={[\n StyleSheet.absoluteFill,\n {\n backgroundColor: panelBg.tint.color,\n opacity: panelBg.tint.opacity,\n },\n ]}\n />\n </View>\n ),\n [panelBg, t.radius.md],\n );\n\n const fonts: OnboardingFonts = useMemo(\n () => ({\n introTitle: t.fonts.bold,\n introSubtitle: t.fonts.regular,\n introButton: t.fonts.bold,\n stepLabel: t.fonts.medium,\n stepTitle: t.fonts.bold,\n stepDescription: t.fonts.regular,\n stepButton: t.fonts.bold,\n primaryButton: t.fonts.bold,\n secondaryButton: t.fonts.medium,\n }),\n [t],\n );\n\n if (skip) return null;\n\n const activeGesture = config.slides[activeIndex]?.gesture;\n // Same rule as the coachmark path: tap / double-tap slides show no glyph.\n const showGesture =\n !!activeGesture &&\n activeGesture !== \"none\" &&\n activeGesture !== \"tap\" &&\n activeGesture !== \"double_tap\";\n\n return (\n <View style={styles.fill}>\n <Onboarding\n introPanel={introPanel}\n steps={steps}\n colors={colors}\n fonts={fonts}\n background={renderPanelBackground}\n onComplete={finish}\n onSkip={finish}\n onStepChange={setActiveIndex}\n />\n\n {/* Perf-tuned gesture glyph overlaid on the active slide, if it asks for\n one (tap / double-tap slides show no glyph — same rule as coachmarks). */}\n {showGesture && (\n <View style={styles.gestureLayer} pointerEvents=\"none\">\n <GestureHint type={activeGesture} color={accent} />\n </View>\n )}\n </View>\n );\n};\n\nexport const FeatureShowcase = React.memo(_FeatureShowcase);\nFeatureShowcase.displayName = \"FeatureShowcase\";\n\nconst styles = StyleSheet.create({\n fill: { flex: 1 },\n // Fills the package's animated background box; clips the accent tint to the\n // rounded corners so the calm panel reads as a card, not a bleed.\n panelBase: { flex: 1, overflow: \"hidden\" },\n gestureLayer: {\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n alignItems: \"center\",\n justifyContent: \"flex-end\",\n paddingBottom: 140,\n },\n});\n\nexport default FeatureShowcase;\n","/**\n * selectShowcaseSlides — the value-bridge selection seam for the feature showcase.\n *\n * The recommended shape (v0.2.1+) renders the showcase AS A PERSONALIZED VALUE\n * BRIDGE right AFTER the AI onboarding completes: the user just told you what they\n * want in `result.answers`, so you show only the 2-3 slides that answer it, in the\n * most-relevant-first order — \"you said you want X → here's how the app does X\"\n * (the Duolingo / Headspace terminal-moment pattern). The host declares the full\n * slide catalog once and passes an ordered id list computed from the answers:\n *\n * // Full deck, declared order (pre-onboarding placement, or thin answers):\n * const slides = selectShowcaseSlides(catalog);\n *\n * // Personalized bridge, post-onboarding: subset + order chosen from answers:\n * const slides = selectShowcaseSlides(catalog, idsFromAnswers(result.answers));\n *\n * Contract (identical to `selectTourSteps`):\n * - `selection` absent → returns the catalog unchanged (identity / declared order).\n * - `selection` present → returns only catalog entries whose `id` appears in\n * `selection`, ordered by `selection` (unknown ids are skipped; duplicates in\n * `selection` are ignored after the first match).\n *\n * v2 SERVER SEAM (document-only, not built here): the onboarding server's final\n * turn will later return an ordered `showcase: string[]` slide-key list chosen\n * server-side from the same answers. The host passes it straight in as `selection`\n * — same catalog, same contract, AI is a drop-in. Old kits that don't read the new\n * field simply ignore it, so shipping the extra field is backward-safe.\n */\nexport const selectShowcaseSlides = <T extends { id: string }>(\n catalog: T[],\n selection?: string[],\n): T[] => {\n if (!selection) return catalog;\n\n const byId = new Map(catalog.map((item) => [item.id, item]));\n const seen = new Set<string>();\n const ordered: T[] = [];\n for (const id of selection) {\n if (seen.has(id)) continue;\n seen.add(id);\n const item = byId.get(id);\n if (item) ordered.push(item);\n }\n return ordered;\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/coachmarks/GestureHint.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/theme/defaultTheme.ts","../../src/theme/ThemeContext.tsx","../../src/showcase/showcaseColors.ts","../../src/showcase/FeatureShowcase.tsx","../../src/showcase/selectShowcaseSlides.ts"],"names":["useEffect","createContext","useContext","useMemo","useRef","useState","_a","jsx","View","styles","StyleSheet","jsxs","React"],"mappings":"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","file":"index.mjs","sourcesContent":["import React, { useEffect, useMemo, type ReactNode } from \"react\";\nimport { StyleSheet, View } from \"react-native\";\nimport Animated, {\n useAnimatedStyle,\n useReducedMotion,\n useSharedValue,\n withDelay,\n withRepeat,\n withSequence,\n withTiming,\n} from \"react-native-reanimated\";\n\nimport type { GestureKind } from \"./types\";\n\n/**\n * Abstract gesture pictogram shown beside a coachmark tooltip. Pure reanimated —\n * every animation runs on the UI thread (shared values + worklets), never\n * setState, so it costs nothing on the JS thread. Honors Reduce Motion by\n * dropping the motion loop and rendering the glyph static.\n *\n * NO drawn-hand anatomy. Swipes render the standard two-finger swipe glyph: two\n * rounded vertical capsules (an abstract index + middle finger, slightly\n * different heights) with a short, fading motion track trailing behind them along\n * the swipe axis. The whole glyph is rotated per direction (up/down/left/right)\n * and the capsules slide along the axis, fading, in a UI-thread loop. Pinch/zoom\n * reuse the two-capsule abstraction spreading apart. Tap / double-tap render a\n * minimal pulsing tap-target (ring + dot).\n *\n * Every glyph is a self-drawn primitive built from Views/borders (no vector-icon\n * dependency, like AnimatedSparkle). Pass `renderHand` — the glyph slot — to\n * substitute your own node while keeping the tuned motion. (The name is kept for\n * backward compatibility; despite it, the default is a glyph, not a drawn hand.)\n *\n * NOTE: the coachmark path (SpotlightOverlay) and the showcase path\n * (FeatureShowcase) both SKIP this component for `tap` / `double_tap` — those\n * steps show only the highlight ring + tooltip, no glyph. The tap visual here\n * exists for direct / standalone use.\n */\nexport interface GestureHintProps {\n type: GestureKind;\n /** Overall glyph size in px. */\n size?: number;\n /** Glyph color. Defaults to white (reads on the dark blur backdrop). */\n color?: string;\n /**\n * Provide a custom glyph node in place of the default self-drawn primitive; it\n * inherits the tuned motion (slide+fade for swipes, pulse for tap). Kept as\n * `renderHand` for backward compatibility — despite the name it is just the\n * glyph slot, no longer a drawn hand.\n */\n renderHand?: (props: { size: number; color: string }) => ReactNode;\n}\n\n/** Travel distance for the directional swipe loop (local glyph axis). */\nconst TRAVEL = 26;\nconst SWIPE_MS = 700;\nconst TAP_MS = 320;\nconst SPREAD_MS = 620;\n\n/**\n * Per-direction rotation of the whole glyph. Canonical orientation is \"up\":\n * capsules point up, travel up, motion track trailing below. Rotating the\n * container reorients both the capsules and the track together, and rotates the\n * local translate axis so the same upward loop reads correctly per direction.\n */\nconst GLYPH_ROTATION: Record<string, string> = {\n swipe_up: \"0deg\",\n swipe_down: \"180deg\",\n swipe_left: \"-90deg\",\n swipe_right: \"90deg\",\n};\n\n/**\n * The two-finger abstraction: two rounded vertical capsules side by side with\n * slightly different heights (index taller than middle), aligned at the base —\n * the standard swipe pictogram look. Self-drawn from Views only.\n */\nconst FingerPair: React.FC<{ size: number; color: string }> = ({ size, color }) => {\n const { row, index, middle } = useMemo(() => {\n const w = size * 0.2;\n const r = w / 2;\n return {\n row: { flexDirection: \"row\" as const, alignItems: \"flex-end\" as const, gap: size * 0.08 },\n index: { width: w, height: size * 0.54, borderRadius: r, backgroundColor: color },\n middle: { width: w, height: size * 0.44, borderRadius: r, backgroundColor: color },\n };\n }, [size, color]);\n\n return (\n <View style={row} pointerEvents=\"none\">\n <View style={index} />\n <View style={middle} />\n </View>\n );\n};\n\n/**\n * A short, tapering motion track that sits behind the capsules (below, in the\n * canonical \"up\" orientation) — two slim, fading ticks that shrink and dim with\n * distance. Subtle: it reads as the path the fingers just travelled, not a comet\n * tail. Static (the capsules carry the motion via the slide loop).\n */\nconst MotionTrack: React.FC<{ size: number; color: string }> = ({ size, color }) => {\n const { wrap, tick0, tick1 } = useMemo(() => {\n const h = Math.max(2, size * 0.05);\n return {\n wrap: { alignItems: \"center\" as const, marginTop: size * 0.1, gap: size * 0.06 },\n tick0: {\n width: size * 0.3,\n height: h,\n borderRadius: h / 2,\n backgroundColor: color,\n opacity: 0.4,\n },\n tick1: {\n width: size * 0.18,\n height: h,\n borderRadius: h / 2,\n backgroundColor: color,\n opacity: 0.16,\n },\n };\n }, [size, color]);\n\n return (\n <View style={wrap} pointerEvents=\"none\">\n <View style={tick0} />\n <View style={tick1} />\n </View>\n );\n};\n\nconst GestureHintComponent: React.FC<GestureHintProps> = ({\n type,\n size = 34,\n color = \"white\",\n renderHand,\n}) => {\n const translate = useSharedValue(0);\n const opacity = useSharedValue(1);\n const pulse = useSharedValue(1);\n const spread = useSharedValue(0);\n const reduced = useReducedMotion();\n\n const isSwipe =\n type === \"swipe_up\" ||\n type === \"swipe_down\" ||\n type === \"swipe_left\" ||\n type === \"swipe_right\";\n const isSpread = type === \"pinch\" || type === \"zoom\";\n const isTap = type === \"tap\" || type === \"double_tap\";\n\n useEffect(() => {\n if (type === \"none\" || reduced) return undefined;\n\n if (isTap) {\n pulse.value = withRepeat(\n withSequence(\n withTiming(0.82, { duration: TAP_MS }),\n withTiming(1, { duration: TAP_MS }),\n ),\n -1,\n false,\n );\n opacity.value = withRepeat(\n withSequence(\n withTiming(0.55, { duration: TAP_MS }),\n withTiming(1, { duration: TAP_MS }),\n ),\n -1,\n false,\n );\n return undefined;\n }\n\n if (isSpread) {\n // Two capsules easing apart and back — a minimal pinch/zoom abstraction.\n spread.value = withRepeat(\n withSequence(\n withTiming(size * 0.26, { duration: SPREAD_MS }),\n withTiming(0, { duration: SPREAD_MS }),\n ),\n -1,\n false,\n );\n return undefined;\n }\n\n // Directional swipe: slide the capsules along the local \"up\" axis, fading\n // out, then snap back and repeat. The container rotation (below) reorients\n // this loop per direction, so the local motion is always the same.\n translate.value = withRepeat(\n withSequence(\n withTiming(0, { duration: 0 }),\n withTiming(-TRAVEL, { duration: SWIPE_MS }),\n withDelay(140, withTiming(0, { duration: 0 })),\n ),\n -1,\n false,\n );\n opacity.value = withRepeat(\n withSequence(\n withTiming(1, { duration: SWIPE_MS / 2 }),\n withTiming(0, { duration: SWIPE_MS / 2 }),\n withDelay(140, withTiming(1, { duration: 0 })),\n ),\n -1,\n false,\n );\n return undefined;\n }, [type, reduced, isTap, isSpread, translate, opacity, pulse, spread, size]);\n\n const swipeAnimatedStyle = useAnimatedStyle(() => ({\n opacity: opacity.value,\n transform: [{ translateY: translate.value }],\n }));\n const tapAnimatedStyle = useAnimatedStyle(() => ({\n opacity: opacity.value,\n transform: [{ scale: pulse.value }],\n }));\n const spreadLeftStyle = useAnimatedStyle(() => ({\n transform: [{ translateX: -spread.value }],\n }));\n const spreadRightStyle = useAnimatedStyle(() => ({\n transform: [{ translateX: spread.value }],\n }));\n\n if (type === \"none\") return null;\n\n const customGlyph = renderHand ? renderHand({ size, color }) : null;\n\n if (isSwipe) {\n return (\n <View\n style={{ transform: [{ rotate: GLYPH_ROTATION[type] ?? \"0deg\" }] }}\n pointerEvents=\"none\"\n >\n <View style={styles.swipeStack}>\n <Animated.View style={swipeAnimatedStyle} pointerEvents=\"none\">\n {customGlyph ?? <FingerPair size={size} color={color} />}\n </Animated.View>\n <MotionTrack size={size} color={color} />\n </View>\n </View>\n );\n }\n\n if (isSpread) {\n const w = size * 0.2;\n const capsule = {\n width: w,\n height: size * 0.5,\n borderRadius: w / 2,\n backgroundColor: color,\n };\n return (\n <View style={[styles.spreadRow, { gap: size * 0.12 }]} pointerEvents=\"none\">\n <Animated.View style={spreadLeftStyle}>\n <View style={capsule} />\n </Animated.View>\n <Animated.View style={spreadRightStyle}>\n <View style={capsule} />\n </Animated.View>\n </View>\n );\n }\n\n // tap / double_tap — a minimal pulsing tap-target (ring + center dot).\n const ringSize = size * 0.86;\n const ringBorder = Math.max(2, size * 0.08);\n const dotSize = size * 0.32;\n return (\n <View style={[styles.tapWrap, { width: size, height: size }]} pointerEvents=\"none\">\n {customGlyph ? (\n <Animated.View style={tapAnimatedStyle} pointerEvents=\"none\">\n {customGlyph}\n </Animated.View>\n ) : (\n <>\n <Animated.View\n style={[\n styles.tapRing,\n {\n width: ringSize,\n height: ringSize,\n borderRadius: ringSize / 2,\n borderWidth: ringBorder,\n borderColor: color,\n },\n tapAnimatedStyle,\n ]}\n pointerEvents=\"none\"\n />\n <View\n style={{\n position: \"absolute\",\n width: dotSize,\n height: dotSize,\n borderRadius: dotSize / 2,\n backgroundColor: color,\n }}\n />\n </>\n )}\n </View>\n );\n};\n\nconst styles = StyleSheet.create({\n swipeStack: {\n alignItems: \"center\",\n justifyContent: \"center\",\n },\n spreadRow: {\n flexDirection: \"row\",\n alignItems: \"center\",\n justifyContent: \"center\",\n },\n tapWrap: {\n alignItems: \"center\",\n justifyContent: \"center\",\n },\n tapRing: {\n backgroundColor: \"transparent\",\n },\n});\n\nexport const GestureHint = React.memo(GestureHintComponent);\nGestureHint.displayName = \"GestureHint\";\n\nexport default GestureHint;\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 * 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","import type { OnboardingColors } from \"@blazejkustra/react-native-onboarding\";\n\nimport type { OnboardingTheme } from \"../theme/types\";\n\n/**\n * Map the kit's semantic `OnboardingTheme` onto the color contract of\n * `@blazejkustra/react-native-onboarding` (what `FeatureShowcase` wraps).\n *\n * ⚠️ CRITICAL — that package's color NAMES do NOT line up with the kit's roles.\n * From its source (`utils/theme.ts` + the button/panel components):\n * • `background.primary` → the PRIMARY BUTTON background, the highlighted intro\n * title word (`OnboardingIntroPanel`), and the image\n * hero panel (`OnboardingImageContainer`). It is the\n * package's BRAND / ACTION color — NOT a screen bg.\n * • `background.secondary` → the screen / modal / step-panel background.\n * • `background.label` → the step-label chip background.\n * • `background.accent` → the SECONDARY (outline) button border ONLY.\n * • `text.contrast` → the primary button label (sits on `background.primary`).\n * • `text.primary` → body copy; `text.secondary` → hints/subtitles.\n *\n * The earlier mapping set `background.primary` to the kit's `background`, which\n * painted the primary CTA in the app's SCREEN background color — an invisible /\n * wrong-colored button (white-on-white in light, dark-on-dark in dark mode). The\n * primary button must be the brand `accent`. Keep this in sync with the package's\n * actual usage, not its key names.\n *\n * ⚠️ CONSEQUENCE of the fix above (the 0.3.2 flood): because `background.primary`\n * is ONE key driving THREE surfaces, painting it `accent` also paints the large\n * IMAGE HERO PANEL (`OnboardingImageContainer`'s `colorBg`) in the accent — a\n * full-bleed wash that dominates the first slide and \"hides\" the app's brand.\n * The package offers no per-surface color key to split them, BUT it does expose a\n * `background?: () => ReactNode` render prop that REPLACES that hero panel fill\n * outright (see `showcasePanelBackground` + `FeatureShowcase`). So the button\n * keeps `accent` here and the panel is calmed separately — one accent value, two\n * different surfaces, cleanly split.\n */\nexport const showcaseColorsFromTheme = (\n t: OnboardingTheme,\n accent: string,\n): OnboardingColors => ({\n background: {\n // Brand action color → primary button bg + intro highlight + image hero panel.\n // (The hero panel is overridden to a calm surface via the package's\n // `background` render prop — see showcasePanelBackground — so in practice\n // this only paints the primary button + the intro highlight word.)\n primary: accent,\n // Neutral screen / modal / step-panel background.\n secondary: t.colors.background,\n // Step-label chip background (a soft wash of the brand).\n label: t.colors.primarySoft,\n // Secondary (outline) button border.\n accent,\n },\n text: {\n primary: t.colors.text,\n secondary: t.colors.textMuted,\n // Primary button label — sits on `background.primary` (the accent).\n contrast: t.colors.onPrimary,\n },\n});\n\n/**\n * Opacity of the accent tint layered over the theme surface for the showcase's\n * image hero panel. Kept in a calm 8–12% band so the panel reads as a subtle\n * brand wash — a hint of the accent, never the full-bleed flood that `accent`\n * as a solid fill produced (the 0.3.1 → 0.3.2 bug seen in a host app).\n */\nexport const SHOWCASE_PANEL_TINT_OPACITY = 0.1;\n\n/** The calm hero-panel background: a neutral surface base + a faint accent tint. */\nexport interface ShowcasePanelBackground {\n /** The calm base fill — the theme SURFACE, deliberately NOT the accent. */\n base: string;\n /** A very subtle accent wash layered over the base (8–12% opacity). */\n tint: { color: string; opacity: number };\n}\n\n/**\n * Background for the showcase's image/hero panel, fed to the underlying\n * package's `background` render prop (which REPLACES its `bg.primary`-filled\n * `colorBg`). Returns a neutral surface base with a faint accent tint on top, so\n * the panel is calm and on-brand instead of an accent flood. The primary button\n * and intro highlight keep the full accent (they read `bg.primary` directly).\n */\nexport const showcasePanelBackground = (\n t: OnboardingTheme,\n accent: string,\n): ShowcasePanelBackground => ({\n base: t.colors.surface,\n tint: { color: accent, opacity: SHOWCASE_PANEL_TINT_OPACITY },\n});\n","import Onboarding, {\n type OnboardingColors,\n type OnboardingFonts,\n type OnboardingProps,\n type OnboardingStep,\n} from \"@blazejkustra/react-native-onboarding\";\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { type ImageSourcePropType, StyleSheet, View } from \"react-native\";\n\nimport { GestureHint } from \"../coachmarks/GestureHint\";\nimport { hasSeenGate, markSeenGate, showcaseGateKey } from \"../coachmarks/runtime\";\nimport { useResolvedFeatures } from \"../features/WireFeaturesProvider\";\nimport { useOnboardingTheme } from \"../theme/ThemeContext\";\nimport type { OnboardingTheme } from \"../theme/types\";\nimport { showcaseColorsFromTheme, showcasePanelBackground } from \"./showcaseColors\";\nimport type { FeatureShowcaseProps } from \"./types\";\n\n/** 1×1 transparent GIF — the fallback for a slide with no image (the underlying\n * package requires an image area per step). */\nconst TRANSPARENT_PX: ImageSourcePropType = {\n uri: \"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\",\n};\n\nconst 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/**\n * FeatureShowcase — the pre-onboarding \"app intro\" slides.\n *\n * Wraps `@blazejkustra/react-native-onboarding`: the app supplies a declarative\n * `ShowcaseConfig` (slides + optional intro panel) and the kit maps it to the\n * package's API, bakes in the Wire onboarding theme colors, overlays the\n * perf-tuned GestureHint on any slide that asks for one, and gates the whole\n * thing once through the same sync storage as the coachmark tours\n * (`wire_showcase_<id>_seen`; the global testing flag bypasses it).\n *\n * When the gate says \"seen\", it renders nothing and calls `onDone` from an\n * effect (never during render).\n */\nconst _FeatureShowcase: React.FC<FeatureShowcaseProps> = ({\n config,\n onDone,\n theme: themeOverride,\n accentColor,\n buttonLabel = \"Next\",\n storage,\n isTesting,\n features,\n featuresConfig,\n}) => {\n const active = useOnboardingTheme();\n const t = useMemo(\n () => mergeThemeOver(active, themeOverride),\n [active, themeOverride],\n );\n const accent = accentColor ?? t.colors.primary;\n\n // Feature kill switch: disabled → treat like \"already seen\" (render nothing, call onDone so the\n // host flow continues) but WITHOUT writing the seen gate, so re-enabling replays the showcase.\n const flags = useResolvedFeatures({ flags: features, config: featuresConfig });\n const disabled = !flags.showcase.enabled;\n\n const gateKey = showcaseGateKey(config.id);\n const seen = useMemo(\n () => hasSeenGate(gateKey, storage, isTesting),\n [gateKey, storage, isTesting],\n );\n // Either already-seen OR feature-disabled short-circuits the showcase. Only the seen path is a\n // gate write (in `finish`); the disabled path never persists, so it replays when re-enabled.\n const skip = seen || disabled;\n\n const doneRef = useRef(false);\n const finish = useCallback(() => {\n if (doneRef.current) return;\n doneRef.current = true;\n markSeenGate(gateKey, storage, isTesting);\n onDone();\n }, [gateKey, storage, isTesting, onDone]);\n\n // Already seen OR disabled → skip without rendering; call onDone from an effect so the host's\n // navigation continues (never write the gate here — `finish` owns the seen write).\n useEffect(() => {\n if (skip && !doneRef.current) {\n doneRef.current = true;\n onDone();\n }\n }, [skip, onDone]);\n\n const [activeIndex, setActiveIndex] = useState(0);\n\n const steps: OnboardingStep[] = useMemo(\n () =>\n config.slides.map((slide) => ({\n title: slide.title,\n description: slide.description,\n buttonLabel: slide.buttonLabel ?? buttonLabel,\n image: slide.image ?? TRANSPARENT_PX,\n position: slide.imagePosition ?? \"top\",\n })),\n [config.slides, buttonLabel],\n );\n\n const introPanel: OnboardingProps[\"introPanel\"] = useMemo(\n () =>\n config.introPanel\n ? {\n title: config.introPanel.title,\n subtitle: config.introPanel.subtitle,\n button: config.introPanel.button,\n image: config.introPanel.image,\n }\n : { button: buttonLabel },\n [config.introPanel, buttonLabel],\n );\n\n // The package's color KEYS don't match the kit's role names — most importantly\n // its `background.primary` drives the PRIMARY BUTTON (not the screen bg), so the\n // accent must land there. See showcaseColorsFromTheme for the full contract.\n const colors: OnboardingColors = useMemo(\n () => showcaseColorsFromTheme(t, accent),\n [t, accent],\n );\n\n // Calm the large image/hero panel. The package's `background.primary` is a\n // SINGLE key that fills the primary button, the intro highlight AND this hero\n // panel — painting it `accent` (correct for the button) also flooded the first\n // slide with the app color (0.3.1). The package exposes a\n // `background` render prop that REPLACES that panel fill outright, so we keep\n // the button accent (via `colors`) and paint the panel a calm surface + faint\n // accent tint here. See showcasePanelBackground.\n const panelBg = useMemo(() => showcasePanelBackground(t, accent), [t, accent]);\n const renderPanelBackground = useCallback(\n () => (\n <View\n style={[\n styles.panelBase,\n { backgroundColor: panelBg.base, borderRadius: t.radius.md },\n ]}\n >\n <View\n style={[\n StyleSheet.absoluteFill,\n {\n backgroundColor: panelBg.tint.color,\n opacity: panelBg.tint.opacity,\n },\n ]}\n />\n </View>\n ),\n [panelBg, t.radius.md],\n );\n\n const fonts: OnboardingFonts = useMemo(\n () => ({\n introTitle: t.fonts.bold,\n introSubtitle: t.fonts.regular,\n introButton: t.fonts.bold,\n stepLabel: t.fonts.medium,\n stepTitle: t.fonts.bold,\n stepDescription: t.fonts.regular,\n stepButton: t.fonts.bold,\n primaryButton: t.fonts.bold,\n secondaryButton: t.fonts.medium,\n }),\n [t],\n );\n\n if (skip) return null;\n\n const activeGesture = config.slides[activeIndex]?.gesture;\n // Same rule as the coachmark path: tap / double-tap slides show no glyph.\n const showGesture =\n !!activeGesture &&\n activeGesture !== \"none\" &&\n activeGesture !== \"tap\" &&\n activeGesture !== \"double_tap\";\n\n return (\n <View style={styles.fill}>\n <Onboarding\n introPanel={introPanel}\n steps={steps}\n colors={colors}\n fonts={fonts}\n background={renderPanelBackground}\n onComplete={finish}\n onSkip={finish}\n onStepChange={setActiveIndex}\n />\n\n {/* Perf-tuned gesture glyph overlaid on the active slide, if it asks for\n one (tap / double-tap slides show no glyph — same rule as coachmarks). */}\n {showGesture && (\n <View style={styles.gestureLayer} pointerEvents=\"none\">\n <GestureHint type={activeGesture} color={accent} />\n </View>\n )}\n </View>\n );\n};\n\nexport const FeatureShowcase = React.memo(_FeatureShowcase);\nFeatureShowcase.displayName = \"FeatureShowcase\";\n\nconst styles = StyleSheet.create({\n fill: { flex: 1 },\n // Fills the package's animated background box; clips the accent tint to the\n // rounded corners so the calm panel reads as a card, not a bleed.\n panelBase: { flex: 1, overflow: \"hidden\" },\n gestureLayer: {\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n alignItems: \"center\",\n justifyContent: \"flex-end\",\n paddingBottom: 140,\n },\n});\n\nexport default FeatureShowcase;\n","/**\n * selectShowcaseSlides — the value-bridge selection seam for the feature showcase.\n *\n * The recommended shape (v0.2.1+) renders the showcase AS A PERSONALIZED VALUE\n * BRIDGE right AFTER the AI onboarding completes: the user just told you what they\n * want in `result.answers`, so you show only the 2-3 slides that answer it, in the\n * most-relevant-first order — \"you said you want X → here's how the app does X\"\n * (the Duolingo / Headspace terminal-moment pattern). The host declares the full\n * slide catalog once and passes an ordered id list computed from the answers:\n *\n * // Full deck, declared order (pre-onboarding placement, or thin answers):\n * const slides = selectShowcaseSlides(catalog);\n *\n * // Personalized bridge, post-onboarding: subset + order chosen from answers:\n * const slides = selectShowcaseSlides(catalog, idsFromAnswers(result.answers));\n *\n * Contract (identical to `selectTourSteps`):\n * - `selection` absent → returns the catalog unchanged (identity / declared order).\n * - `selection` present → returns only catalog entries whose `id` appears in\n * `selection`, ordered by `selection` (unknown ids are skipped; duplicates in\n * `selection` are ignored after the first match).\n *\n * v2 SERVER SEAM (document-only, not built here): the onboarding server's final\n * turn will later return an ordered `showcase: string[]` slide-key list chosen\n * server-side from the same answers. The host passes it straight in as `selection`\n * — same catalog, same contract, AI is a drop-in. Old kits that don't read the new\n * field simply ignore it, so shipping the extra field is backward-safe.\n */\nexport const selectShowcaseSlides = <T extends { id: string }>(\n catalog: T[],\n selection?: string[],\n): T[] => {\n if (!selection) return catalog;\n\n const byId = new Map(catalog.map((item) => [item.id, item]));\n const seen = new Set<string>();\n const ordered: T[] = [];\n for (const id of selection) {\n if (seen.has(id)) continue;\n seen.add(id);\n const item = byId.get(id);\n if (item) ordered.push(item);\n }\n return ordered;\n};\n"]}
|
package/llms.txt
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
- **Metro (required):** `module.exports = withWireOnboarding(getDefaultConfig(__dirname))` from `@wireai/activation/metro`, which pins one copy of react/react-native/wireai-rn/zod (prevents the dual-React crash).
|
|
13
13
|
- **Two secrets:** an app `apiKey` (a `wai_…` key that resolves the tenant server-side) and the backend `serverUrl`. Created in the getwireai console or via the backend's `register_<app>.py`. Nothing renders without both.
|
|
14
14
|
- **Render:** drop `<WireOnboarding config={wireConfigFromEnv({ appId })} theme={...} onComplete={persist} fallbackFlow={<YourStaticOnboarding/>} />` into the signup flow.
|
|
15
|
+
- **The join key.** Pass `userContext={activationJoinContext(deviceKey)}` on `<WireOnboarding>` (or `activationJoinContext(resolveAutoDeviceKey({ appId, storage }))` when the app owns no device id). `user_context.device_key` is the ONLY thing that joins an onboarding session to the app's later events; omit it and the `activated` funnel reads zero with no error.
|
|
16
|
+
- **Lifecycle (mount it once):** `useLifecycleEvents(config, { deviceKey?, sessionCount?, userId? })` at the app root, before anything else touches analytics. It is the only path that emits `app.first_open`, it registers the per-open session id `createAnalytics` / `wire.track` / the gates all correlate to, and with `config.storage` it stamps the persisted auto `device_key` on `app.session_started`, which is what the server counts `min_sessions` from. `useSessionStart` / `reportSessionStart` are the alternatives for a host that already owns an open counter; neither emits `first_open`.
|
|
15
17
|
- **Capture is deterministic.** `onComplete` returns `{ answers, raw }`; no second LLM call. Persist all answers through the app's normal profile-update path.
|
|
16
18
|
- **It can never break onboarding.** Pass `fallbackFlow` (your existing static onboarding) so a backend error/timeout degrades instead of dead-ending.
|
|
17
19
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wireai/activation",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Premium, fully-themable drop-in AI onboarding kit for React Native / Expo, on top of the open-source wireai-rn SDK.",
|
|
6
6
|
"author": "Malik Chohra <malik@getwireai.com>",
|
package/src/WireOnboarding.tsx
CHANGED
|
@@ -22,6 +22,8 @@ import { OnboardingFlow, DEFAULT_COPY } from "./OnboardingFlow";
|
|
|
22
22
|
import { onboardingComponents } from "./cards";
|
|
23
23
|
import { makeSessionId, reportClientEvent, type ClientEventTarget } from "./analytics/reportClientEvent";
|
|
24
24
|
import { collectDeviceContext, type DeviceContext } from "./device/deviceContext";
|
|
25
|
+
import { hydrateAutoDeviceKey } from "./context/deviceId";
|
|
26
|
+
import { activationJoinContext } from "./context/userContext";
|
|
25
27
|
import { sanitizeUserId } from "./identity/userIdentity";
|
|
26
28
|
import {
|
|
27
29
|
clearPersistedSession,
|
|
@@ -33,6 +35,21 @@ import {
|
|
|
33
35
|
} from "./session/persistedSession";
|
|
34
36
|
import type { OnboardingResult, WireOnboardingProps } from "./types";
|
|
35
37
|
|
|
38
|
+
/** RN sets this global; absent under node/SSR. Read defensively via {@link warnInDev}. */
|
|
39
|
+
declare const __DEV__: boolean | undefined;
|
|
40
|
+
|
|
41
|
+
/** Emit a one-line developer warning, but ONLY in a dev build (RN `__DEV__`). No-op in prod/tests.
|
|
42
|
+
* Same idiom as `analytics/analyticsFacade.warnInDev`. */
|
|
43
|
+
const warnInDev = (message: string): void => {
|
|
44
|
+
if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
|
|
45
|
+
console.warn(message);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** Ceiling on the auto-join-key storage read — a hung adapter degrades to no key, never a stuck
|
|
50
|
+
* loader. Matches the persisted-session read's own ceiling. */
|
|
51
|
+
const AUTO_JOIN_HYDRATION_TIMEOUT_MS = 1_500;
|
|
52
|
+
|
|
36
53
|
export const WireOnboarding: React.FC<WireOnboardingProps> = ({
|
|
37
54
|
config,
|
|
38
55
|
theme,
|
|
@@ -56,6 +73,7 @@ export const WireOnboarding: React.FC<WireOnboardingProps> = ({
|
|
|
56
73
|
retainSessionOnComplete,
|
|
57
74
|
userContext,
|
|
58
75
|
userId,
|
|
76
|
+
autoJoinKey = true,
|
|
59
77
|
}) => {
|
|
60
78
|
// The host's own opaque user id (trimmed + capped, NO PII) so onboarding sessions can be
|
|
61
79
|
// reconciled to real users later. `sanitizeUserId` is a pure string transform → the memoized
|
|
@@ -87,12 +105,125 @@ export const WireOnboarding: React.FC<WireOnboardingProps> = ({
|
|
|
87
105
|
[userContextKey],
|
|
88
106
|
);
|
|
89
107
|
|
|
108
|
+
// THE JOIN KEY, or the absence of it. `user_context.device_key` is the ONLY thing that stitches an
|
|
109
|
+
// onboarding session to everything the app reports later, and the prop that carries it is optional
|
|
110
|
+
// and named `userContext` — so forgetting it is the default, and the failure is a silent zero in the
|
|
111
|
+
// `activated` funnel rather than an error. Two real consumers shipped without it.
|
|
112
|
+
const missingJoinKey =
|
|
113
|
+
typeof userContextStable?.device_key !== "string" || !userContextStable.device_key.trim();
|
|
114
|
+
|
|
115
|
+
// AUTO-JOIN (0.12.2). When the host supplied no join key, the kit supplies its OWN — the same
|
|
116
|
+
// per-install `device_key` the analytics surfaces auto-mint and persist — so the default wiring
|
|
117
|
+
// produces a joined funnel instead of a silent zero. Three conditions, all necessary:
|
|
118
|
+
//
|
|
119
|
+
// • `missingJoinKey` — a host-supplied key ALWAYS wins, verbatim, and is never touched.
|
|
120
|
+
// • `autoJoinKey !== false` — the documented opt-out for a host that genuinely wants an
|
|
121
|
+
// unlinked onboarding session; opting out restores the pre-0.12.2 behavior exactly.
|
|
122
|
+
// • `storage` — WITHOUT persistence `resolveAutoDeviceKey` is process-scoped, so every launch
|
|
123
|
+
// would carry a DIFFERENT key. That is worse than no key: the server counts `min_sessions`
|
|
124
|
+
// by distinct opens grouped on `device_key`, so a per-launch key corrupts the counter rather
|
|
125
|
+
// than leaving it empty. Same reason `useLifecycleEvents` gates its own fallback on storage.
|
|
126
|
+
//
|
|
127
|
+
// It resolves through `hydrateAutoDeviceKey`, never the sync `resolveAutoDeviceKey`: the sync
|
|
128
|
+
// contract returns the freshly minted id and adopts the persisted one milliseconds later, which
|
|
129
|
+
// is exactly the 0.12.1 H1 defect — the key stamped here must be the one the analytics side
|
|
130
|
+
// stamps, not a fresh mint per launch. The value is therefore awaited BEHIND THE LOADER GATE
|
|
131
|
+
// below (the same gate the persisted-session read already holds, and it is only ever open when
|
|
132
|
+
// `storage` is present anyway), because `userContext` feeds the `llm` memo: swapping it after the
|
|
133
|
+
// provider mounted would recreate the A2A adapter and drop the server-learned `contextId`.
|
|
134
|
+
const autoJoinPossible = Boolean(storage) && autoJoinKey !== false;
|
|
135
|
+
const wantsAutoJoin = missingJoinKey && autoJoinPossible;
|
|
136
|
+
// `undefined` = the hydration is still in flight (the gate below holds); a string = the key to
|
|
137
|
+
// inject; `null` = gave up (see the timeout note in the effect) so the gate opens with no key.
|
|
138
|
+
const [autoJoinValue, setAutoJoinValue] = useState<string | null | undefined>(undefined);
|
|
139
|
+
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
if (!wantsAutoJoin || autoJoinValue !== undefined || !storage) return;
|
|
142
|
+
let cancelled = false;
|
|
143
|
+
// Ceiling on the read, the same discipline as `loadPersistedSession`: a hung storage adapter
|
|
144
|
+
// must degrade to "no auto key", never to a stuck loader over the host's onboarding. Giving up
|
|
145
|
+
// is deliberately NOT "fall back to the synchronous mint" — that key is per-launch, which is
|
|
146
|
+
// the corruption this feature is gated on `storage` to avoid.
|
|
147
|
+
const timer = setTimeout(() => {
|
|
148
|
+
if (!cancelled) setAutoJoinValue(null);
|
|
149
|
+
}, AUTO_JOIN_HYDRATION_TIMEOUT_MS);
|
|
150
|
+
void hydrateAutoDeviceKey({ appId: config.appId, storage }).then((key) => {
|
|
151
|
+
if (cancelled) return;
|
|
152
|
+
clearTimeout(timer);
|
|
153
|
+
setAutoJoinValue(key || null);
|
|
154
|
+
});
|
|
155
|
+
return () => {
|
|
156
|
+
cancelled = true;
|
|
157
|
+
clearTimeout(timer);
|
|
158
|
+
};
|
|
159
|
+
}, [wantsAutoJoin, autoJoinValue, config.appId, storage]);
|
|
160
|
+
|
|
161
|
+
const injectedJoinKey = wantsAutoJoin && typeof autoJoinValue === "string" ? autoJoinValue : undefined;
|
|
162
|
+
const autoJoinPending = wantsAutoJoin && autoJoinValue === undefined;
|
|
163
|
+
|
|
164
|
+
// What actually goes on the wire: the host's context, with the auto join key merged in ONLY when
|
|
165
|
+
// the host left the slot empty. Every other key the host passed survives untouched, and a host
|
|
166
|
+
// that passed a valid `device_key` gets a byte-identical payload to 0.12.1.
|
|
167
|
+
const effectiveUserContext = useMemo<Record<string, string | number | boolean> | undefined>(
|
|
168
|
+
() =>
|
|
169
|
+
injectedJoinKey
|
|
170
|
+
? { ...userContextStable, ...activationJoinContext(injectedJoinKey) }
|
|
171
|
+
: userContextStable,
|
|
172
|
+
[userContextStable, injectedJoinKey],
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
// Dev-only warning (never a throw, never a wire change), once per mount, naming the exact fix.
|
|
176
|
+
// Reconciled with auto-join: it fires only when the gap is still OPEN — injection was impossible
|
|
177
|
+
// (no `storage`), the host opted out, or the read gave up. When auto-join covered the gap the
|
|
178
|
+
// session IS joined, so there is nothing to warn about; while it is still in flight, nothing is
|
|
179
|
+
// decided yet, so it stays quiet.
|
|
180
|
+
const warnMissingJoinKey = missingJoinKey && !injectedJoinKey && !autoJoinPending;
|
|
181
|
+
const autoJoinReason =
|
|
182
|
+
autoJoinKey === false
|
|
183
|
+
? "you passed autoJoinKey={false}."
|
|
184
|
+
: !storage
|
|
185
|
+
? "it got no `storage` prop, and without persistence its key would be different on every " +
|
|
186
|
+
"launch, which corrupts min_sessions instead of merely leaving the join empty."
|
|
187
|
+
: "your `storage` adapter did not answer in time.";
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
if (!warnMissingJoinKey) return;
|
|
190
|
+
warnInDev(
|
|
191
|
+
"[wireai] <WireOnboarding> got no user_context.device_key, so this onboarding session can " +
|
|
192
|
+
"never be joined to the app's later events and the `activated` funnel will read zero. Pass " +
|
|
193
|
+
"userContext={activationJoinContext(deviceKey)} — and if your app owns no device id, " +
|
|
194
|
+
"userContext={activationJoinContext(resolveAutoDeviceKey({ appId, storage }))} returns the " +
|
|
195
|
+
"SAME id the analytics side stamps. Never hand-write userContext={{ deviceKey }}. " +
|
|
196
|
+
"The kit did NOT auto-inject its own key here because " +
|
|
197
|
+
autoJoinReason,
|
|
198
|
+
);
|
|
199
|
+
}, [warnMissingJoinKey, autoJoinReason]);
|
|
200
|
+
|
|
201
|
+
// The one case auto-join would otherwise SILENCE and must not: the host hand-wrote
|
|
202
|
+
// `userContext={{ deviceKey }}`. That bucket is not the wire key, so the kit injects its own —
|
|
203
|
+
// but the host demonstrably owns an id, and their app-side events carry THAT one, so the two
|
|
204
|
+
// families still land in disjoint id spaces. Injection makes the onboarding side self-consistent;
|
|
205
|
+
// only the host can make it join.
|
|
206
|
+
const misspelledJoinKey =
|
|
207
|
+
missingJoinKey &&
|
|
208
|
+
typeof userContextStable?.deviceKey === "string" &&
|
|
209
|
+
Boolean(userContextStable.deviceKey.trim());
|
|
210
|
+
useEffect(() => {
|
|
211
|
+
if (!misspelledJoinKey || !injectedJoinKey) return;
|
|
212
|
+
warnInDev(
|
|
213
|
+
"[wireai] <WireOnboarding> got userContext={{ deviceKey }}, which is NOT the wire key — the " +
|
|
214
|
+
"server's device lookup reads `device_key`. The kit auto-injected its own device_key so this " +
|
|
215
|
+
"session is at least self-consistent, but your app's own events carry YOUR id, so the two " +
|
|
216
|
+
"still will not join and the `activated` funnel stays zero. Pass " +
|
|
217
|
+
"userContext={activationJoinContext(deviceKey)} instead.",
|
|
218
|
+
);
|
|
219
|
+
}, [misspelledJoinKey, injectedJoinKey]);
|
|
220
|
+
|
|
90
221
|
// The context payload shared by the A2A metadata + client-event paths. `userContext` +
|
|
91
222
|
// `userId` are host-injected and forwarded verbatim (the server sanitizes them). Omitted keys
|
|
92
223
|
// stay absent, so `dropped`/`client_fallback` events carry the user id when it's known.
|
|
93
224
|
const clientContext = useMemo(
|
|
94
|
-
() => ({ device, userContext:
|
|
95
|
-
[device,
|
|
225
|
+
() => ({ device, userContext: effectiveUserContext, userId: boundUserId }),
|
|
226
|
+
[device, effectiveUserContext, boundUserId],
|
|
96
227
|
);
|
|
97
228
|
// One stable session id per onboarding SESSION (not per mount). It's used as the
|
|
98
229
|
// client event `session_id` AND forwarded to the backend as `metadata.sessionId` so
|
|
@@ -180,7 +311,7 @@ export const WireOnboarding: React.FC<WireOnboardingProps> = ({
|
|
|
180
311
|
// Device snapshot + host-injected user context ride the session-start metadata so the
|
|
181
312
|
// backend can segment the funnel. Old servers ignore these unknown keys (backward compat).
|
|
182
313
|
device,
|
|
183
|
-
...(
|
|
314
|
+
...(effectiveUserContext ? { userContext: effectiveUserContext } : {}),
|
|
184
315
|
// The session-start user id (from the ref) rides the session-start metadata so the
|
|
185
316
|
// server binds the session to a real user at creation. Reading the ref — not
|
|
186
317
|
// `boundUserId` — keeps this memo off the userId dependency, so a mid-session change
|
|
@@ -189,7 +320,7 @@ export const WireOnboarding: React.FC<WireOnboardingProps> = ({
|
|
|
189
320
|
},
|
|
190
321
|
timeoutMs: 60_000,
|
|
191
322
|
};
|
|
192
|
-
}, [config.serverUrl, config.appId, config.apiKey, metadataStable, sessionId, componentsStable, device,
|
|
323
|
+
}, [config.serverUrl, config.appId, config.apiKey, metadataStable, sessionId, componentsStable, device, effectiveUserContext]);
|
|
193
324
|
|
|
194
325
|
// Where client-reported events are POSTed (`/v1/events`). Same tenant creds as the flow.
|
|
195
326
|
const reportTarget = useMemo<ClientEventTarget>(
|
|
@@ -228,7 +359,11 @@ export const WireOnboarding: React.FC<WireOnboardingProps> = ({
|
|
|
228
359
|
// the provider with a throwaway id and swapping later would re-init it mid-flight.
|
|
229
360
|
// The read is timeout-capped in loadPersistedSession, so this gate is one storage
|
|
230
361
|
// read (~a frame), never an indefinite hold.
|
|
231
|
-
|
|
362
|
+
//
|
|
363
|
+
// The auto join key rides the SAME gate for the same reason (`effectiveUserContext` is also an
|
|
364
|
+
// `llm` dependency). It costs no extra hold in practice: auto-join requires `storage`, and with
|
|
365
|
+
// `storage` this gate is already closed on the persisted-session read.
|
|
366
|
+
if (!session || autoJoinPending) {
|
|
232
367
|
return (
|
|
233
368
|
<OnboardingThemeProvider theme={theme}>
|
|
234
369
|
<LoadingScreen
|