@wireai/activation 0.12.2 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +145 -0
  2. package/README.md +5 -3
  3. package/dist/analytics/index.d.mts +2 -2
  4. package/dist/analytics/index.d.ts +2 -2
  5. package/dist/analytics/index.js +114 -35
  6. package/dist/analytics/index.js.map +1 -1
  7. package/dist/analytics/index.mjs +114 -36
  8. package/dist/analytics/index.mjs.map +1 -1
  9. package/dist/{currentSession-BxEB37xt.d.ts → currentSession-D7zabMXK.d.ts} +161 -9
  10. package/dist/{currentSession-BlCeDP0f.d.mts → currentSession-_GynvhzT.d.mts} +161 -9
  11. package/dist/index.d.mts +5 -15
  12. package/dist/index.d.ts +5 -15
  13. package/dist/index.js +232 -135
  14. package/dist/index.js.map +1 -1
  15. package/dist/index.mjs +229 -134
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/questionnaire/index.js.map +1 -1
  18. package/dist/questionnaire/index.mjs.map +1 -1
  19. package/dist/reviews/index.js +8 -6
  20. package/dist/reviews/index.js.map +1 -1
  21. package/dist/reviews/index.mjs +8 -6
  22. package/dist/reviews/index.mjs.map +1 -1
  23. package/package.json +1 -1
  24. package/src/OnboardingFlow.tsx +10 -3
  25. package/src/WireOnboarding.tsx +115 -32
  26. package/src/activation/wireActivation.ts +13 -7
  27. package/src/analytics/analyticsFacade.ts +11 -10
  28. package/src/analytics/currentSession.ts +6 -20
  29. package/src/analytics/eventQueue.ts +69 -1
  30. package/src/analytics/index.ts +1 -1
  31. package/src/analytics/reportClientEvent.ts +92 -29
  32. package/src/config/wireConfigFromEnv.ts +1 -10
  33. package/src/context/deviceId.ts +77 -16
  34. package/src/context/userContext.ts +4 -15
  35. package/src/identity/identityRecord.ts +123 -0
  36. package/src/identity/userIdentity.ts +45 -9
  37. package/src/index.ts +6 -4
  38. package/src/session-analytics/useLifecycleEvents.ts +10 -1
  39. package/src/types.ts +14 -0
  40. package/src/utils/deriveAnswers.ts +6 -2
  41. package/src/utils/readProgress.ts +4 -0
  42. package/src/utils/warnInDev.ts +33 -0
  43. package/src/components/DoneBlock.tsx +0 -37
package/src/types.ts CHANGED
@@ -272,6 +272,20 @@ export type OnboardingProgress = {
272
272
  total: number;
273
273
  /** Base-question key for the CURRENT screen, when known (used to pick a validator). */
274
274
  key?: string;
275
+ /**
276
+ * STABLE per-slot identity for the CURRENT screen, when the backend sends one. Preferred over
277
+ * {@link key} for both the answer key and the validator lookup.
278
+ *
279
+ * WHY IT EXISTS: `key` is authored from the question's prompt text (the tenant flows slugify it and
280
+ * cut at 32 chars), so re-wording a question mints a NEW key — the answer a host reads as
281
+ * `answers.interests` silently becomes `answers.what_are_you_into_v2`, with no error anywhere. A
282
+ * slot is the question's identity independent of its wording.
283
+ *
284
+ * FULLY ADDITIVE AND CURRENTLY INERT: no server emits it yet. Every fallback is PER-CARD, so a
285
+ * thread that mixes slotted and unslotted cards (the real shape during a rollout) keys each one
286
+ * correctly, and a backend that never sends it produces byte-identical behaviour to 0.12.2.
287
+ */
288
+ slot_id?: string;
275
289
  /** Whether the CURRENT screen may be skipped (backend-marked; default false → no Skip shown). */
276
290
  skippable?: boolean;
277
291
  };
@@ -29,10 +29,14 @@ export const deriveAnswers = (messages: Message[]): Record<string, unknown> => {
29
29
  if (!m || m.role !== "assistant" || !r || r.action !== "render") continue;
30
30
 
31
31
  const props = (r.props ?? {}) as Record<string, unknown>;
32
- const progress = props.progress as { key?: string } | undefined;
32
+ const progress = props.progress as { key?: string; slot_id?: string } | undefined;
33
33
  const questionText =
34
34
  (props.title as string) ?? (props.label as string) ?? (props.question as string);
35
- const key = progress?.key || questionText;
35
+ // slot_id > key > question text, decided PER CARD. A thread that mixes slotted and unslotted
36
+ // cards is the real shape during a server rollout, so caching a "this backend does slots" verdict
37
+ // off the first card would mis-key every later one. Against a backend that sends no `slot_id`
38
+ // this is byte-identical to 0.12.2.
39
+ const key = progress?.slot_id || progress?.key || questionText;
36
40
 
37
41
  const reply = messages[i + 1];
38
42
  if (key && reply?.role === "user") {
@@ -15,6 +15,8 @@ type PartialProgress = {
15
15
  step?: number;
16
16
  total?: number;
17
17
  key?: string;
18
+ /** The stable per-slot identity, when the backend sends one. See `OnboardingProgress.slot_id`. */
19
+ slot_id?: string;
18
20
  skippable?: boolean;
19
21
  };
20
22
 
@@ -28,6 +30,8 @@ export const readProgress = (response?: WireAIResponse): PartialProgress => {
28
30
  step: typeof p.step === "number" ? p.step : undefined,
29
31
  total: typeof p.total === "number" ? p.total : undefined,
30
32
  key: typeof p.key === "string" ? p.key : undefined,
33
+ // Whitelisted the same way as every other field: an old backend simply omits it.
34
+ slot_id: typeof p.slot_id === "string" ? p.slot_id : undefined,
31
35
  skippable: typeof p.skippable === "boolean" ? p.skippable : undefined,
32
36
  };
33
37
  };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * warnInDev — the ONE developer-warning primitive.
3
+ *
4
+ * The kit warns a developer in four places (the onboarding host, the env-config helper, the analytics
5
+ * façade, the current-session registry) and until 0.13.0 each carried its own copy of the same five
6
+ * lines. Three were byte-identical; the fourth differed only by returning whether it actually warned.
7
+ * Four copies of a guard is four chances for one of them to drift out of the `__DEV__` gate, which is
8
+ * the failure that matters: a warning that runs in production is a string built for nobody.
9
+ *
10
+ * ON THE TREE-SHAKING NOTE THIS REPLACES: `analytics/currentSession` used to justify its copy as
11
+ * keeping the module import-free for the tree-shaken `./analytics` bundle. That rationale had already
12
+ * lapsed — the module imports `makeSessionId` from `./reportClientEvent` — and this module has no
13
+ * imports of its own, so it adds one leaf to the graph and nothing to the bundle. The `treeShake`
14
+ * canary still holds the real guarantee (the analytics graph reaches no UI module).
15
+ */
16
+
17
+ /** RN sets this global; absent under node/SSR. Read defensively, never assumed. */
18
+ declare const __DEV__: boolean | undefined;
19
+
20
+ /**
21
+ * Emit a one-line developer warning, but ONLY in a dev build (RN `__DEV__`). No-op in prod/tests.
22
+ *
23
+ * Returns whether it ACTUALLY warned, which a caller holding a once-flag must honour: marking
24
+ * "already warned" after a no-op would burn the single warning in production, and the one dev build
25
+ * that needed it would then run silent.
26
+ */
27
+ export const warnInDev = (message: string): boolean => {
28
+ if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
29
+ console.warn(message);
30
+ return true;
31
+ }
32
+ return false;
33
+ };
@@ -1,37 +0,0 @@
1
- /**
2
- * DoneBlock — a brief themed "all set" state shown when the flow completes, while
3
- * the host persists results and navigates away. Purely cosmetic; the real
4
- * terminal signal is the SDK's StatusCard (handled in OnboardingFlow).
5
- */
6
- import React from "react";
7
- import { StyleSheet, Text, View } from "react-native";
8
- import { useOnboardingTheme } from "../theme/ThemeContext";
9
- import { bodyStyle, headingStyle } from "../theme/typography";
10
-
11
- export type DoneBlockProps = {
12
- title?: string;
13
- message?: string;
14
- };
15
-
16
- const _DoneBlock: React.FC<DoneBlockProps> = ({
17
- title = "You're all set",
18
- message = "Personalizing your experience…",
19
- }) => {
20
- const t = useOnboardingTheme();
21
- return (
22
- <View style={[styles.center, { gap: t.spacing.sm, padding: t.spacing.lg }]}>
23
- <Text style={[headingStyle(t.fonts), { color: t.colors.success, textAlign: "center" }]}>
24
- {title}
25
- </Text>
26
- <Text style={[bodyStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }]}>
27
- {message}
28
- </Text>
29
- </View>
30
- );
31
- };
32
-
33
- export const DoneBlock = React.memo(_DoneBlock);
34
-
35
- const styles = StyleSheet.create({
36
- center: { flex: 1, alignItems: "center", justifyContent: "center" },
37
- });