@wireai/activation 0.13.2 → 0.13.3

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 (69) hide show
  1. package/AGENTS.md +4 -3
  2. package/CHANGELOG.md +228 -4
  3. package/README.md +16 -2
  4. package/dist/analytics/index.d.mts +4 -4
  5. package/dist/analytics/index.d.ts +4 -4
  6. package/dist/analytics/index.js +104 -470
  7. package/dist/analytics/index.js.map +1 -1
  8. package/dist/analytics/index.mjs +102 -468
  9. package/dist/analytics/index.mjs.map +1 -1
  10. package/dist/coachmarks/index.d.mts +14 -1
  11. package/dist/coachmarks/index.d.ts +14 -1
  12. package/dist/coachmarks/index.js +58 -14
  13. package/dist/coachmarks/index.js.map +1 -1
  14. package/dist/coachmarks/index.mjs +58 -14
  15. package/dist/coachmarks/index.mjs.map +1 -1
  16. package/dist/{currentSession-ClkLjcJ0.d.mts → currentSession-BoWtr3Jp.d.mts} +2 -2
  17. package/dist/{currentSession-DOVZEWJl.d.ts → currentSession-CCOMlaQ2.d.ts} +2 -2
  18. package/dist/{decision-Bl_M2y3r.d.mts → decision-Cjw0jbEj.d.mts} +1 -1
  19. package/dist/{decision-Cau5KmP6.d.ts → decision-GfpXAsk_.d.ts} +1 -1
  20. package/dist/index.d.mts +25 -2
  21. package/dist/index.d.ts +25 -2
  22. package/dist/index.js +220 -573
  23. package/dist/index.js.map +1 -1
  24. package/dist/index.mjs +217 -570
  25. package/dist/index.mjs.map +1 -1
  26. package/dist/questionnaire/index.d.mts +42 -10
  27. package/dist/questionnaire/index.d.ts +42 -10
  28. package/dist/questionnaire/index.js +45 -13
  29. package/dist/questionnaire/index.js.map +1 -1
  30. package/dist/questionnaire/index.mjs +45 -13
  31. package/dist/questionnaire/index.mjs.map +1 -1
  32. package/dist/reviews/index.d.mts +4 -4
  33. package/dist/reviews/index.d.ts +4 -4
  34. package/dist/reviews/index.js +27 -7
  35. package/dist/reviews/index.js.map +1 -1
  36. package/dist/reviews/index.mjs +27 -7
  37. package/dist/reviews/index.mjs.map +1 -1
  38. package/dist/showcase/index.js +7 -2
  39. package/dist/showcase/index.js.map +1 -1
  40. package/dist/showcase/index.mjs +7 -2
  41. package/dist/showcase/index.mjs.map +1 -1
  42. package/dist/{transport-DsRe4epC.d.ts → transport-CefdERPs.d.mts} +44 -5
  43. package/dist/{transport-CF_eHwzC.d.mts → transport-DFuPyCoJ.d.ts} +44 -5
  44. package/dist/{types-Buj9Lw9t.d.ts → types-UVYt9BJe.d.ts} +41 -1
  45. package/dist/{types-CNUqMK0D.d.mts → types-l2tfg23P.d.mts} +41 -1
  46. package/package.json +4 -1
  47. package/src/OnboardingFlow.tsx +44 -15
  48. package/src/WireOnboarding.tsx +7 -6
  49. package/src/activation/wireActivation.ts +3 -3
  50. package/src/analytics/analyticsFacade.ts +2 -2
  51. package/src/analytics/eventQueue.ts +83 -20
  52. package/src/cards/CardGridSelectCard.tsx +1 -1
  53. package/src/cards/ChipSelectCard.tsx +2 -2
  54. package/src/cards/SelectionCard.tsx +1 -1
  55. package/src/coachmarks/runtime.ts +62 -2
  56. package/src/coachmarks/useCoachmarkTour.ts +62 -14
  57. package/src/context/deviceId.ts +4 -4
  58. package/src/features/WireFeaturesProvider.tsx +4 -1
  59. package/src/identity/userIdentity.ts +1 -1
  60. package/src/questionnaire/QuestionnaireGate.tsx +90 -10
  61. package/src/questionnaire/index.ts +9 -1
  62. package/src/questionnaire/transport.ts +46 -11
  63. package/src/reviews/ReviewGate.tsx +61 -6
  64. package/src/reviews/index.ts +10 -1
  65. package/src/reviews/transport.ts +58 -12
  66. package/src/session-analytics/useLifecycleEvents.ts +1 -1
  67. package/src/theme/ThemeContext.tsx +42 -1
  68. package/src/types.ts +1 -1
  69. package/src/utils/submitResult.ts +39 -0
@@ -3,12 +3,53 @@
3
3
  * themed card via `useOnboardingTheme()`. WHY a context rather than prop-drilling:
4
4
  * the cards are mounted by the SDK's ComponentRenderer (not by us), so we can't
5
5
  * pass them props directly — they must read the theme from context.
6
+ *
7
+ * ── WHY THE CONTEXT LIVES ON A `Symbol.for` SLOT ───────────────────────────────────────────────
8
+ *
9
+ * It was a plain `createContext(...)`, and tsup inlines this module into FIVE dist bundles
10
+ * (`.`, `./reviews`, `./questionnaire`, `./coachmarks`, `./showcase`) — so each one carried its own
11
+ * context instance. `OnboardingThemeProvider` is exported ONLY from the root barrel, so on any
12
+ * `dist` resolution path (plain RN CLI, `unstable_enablePackageExports: false`, node, jest, SSR) a
13
+ * host that wrapped its tree in `<OnboardingThemeProvider>` and rendered `<ReviewGate>` from
14
+ * `@wireai/activation/reviews` was writing one context and reading another. Nothing crashed and
15
+ * nothing warned: `useOnboardingTheme`'s `ctx ?? mergeTheme()` fallback quietly served the NEUTRAL
16
+ * default theme, so the tenant's brand simply did not apply to the gate. On-device this was masked
17
+ * only because Metro's `react-native` export condition collapses every subpath back to this one
18
+ * `src/` file — the same "bundler accident, not a guarantee" the `currentSession` header names, and
19
+ * the same root cause as the optional-peers bug 0.13.3 fixed.
20
+ *
21
+ * The bundler-agnostic fix, exactly as `coachmarks/runtime`, `analytics/currentSession`,
22
+ * `context/deviceId` and `features/WireFeaturesProvider` already do it: park the ONE context on a
23
+ * well-known `globalThis` slot keyed by `Symbol.for(...)`. `Symbol.for` uses the runtime-global
24
+ * symbol registry, so every inlined copy of this module resolves the SAME symbol, finds the slot
25
+ * already filled, and reuses that instance — one context identity however many copies exist.
26
+ * `globalThis` is present and identical in Hermes/React Native, Node and SSR (we never touch
27
+ * `window`), so this is safe on every host. `test/canary/themeContextIdentity.test.tsx` measures it
28
+ * in the BUILT artifact, because the defect is invisible in source.
6
29
  */
7
30
  import React, { createContext, useContext, useMemo } from "react";
8
31
  import { mergeTheme } from "./defaultTheme";
9
32
  import type { OnboardingTheme } from "./types";
10
33
 
11
- const OnboardingThemeContext = createContext<OnboardingTheme | null>(null);
34
+ /**
35
+ * Well-known key into the runtime-global symbol registry. `Symbol.for` (NOT a plain `Symbol()`) is
36
+ * what makes this cross-bundle: it returns the SAME symbol for the same string across every copy of
37
+ * this module, so duplicated inlined copies all address one slot.
38
+ */
39
+ const THEME_CONTEXT_SLOT: unique symbol = Symbol.for("@wireai/activation:themeContext");
40
+
41
+ type GlobalWithThemeContext = typeof globalThis & {
42
+ [THEME_CONTEXT_SLOT]?: React.Context<OnboardingTheme | null>;
43
+ };
44
+
45
+ const themeGlobal = globalThis as GlobalWithThemeContext;
46
+
47
+ // First copy to load creates the context; every later copy adopts it. Never overwrite an existing
48
+ // slot — a second `createContext` here would re-split the identity this guard exists to keep whole.
49
+ if (!themeGlobal[THEME_CONTEXT_SLOT]) {
50
+ themeGlobal[THEME_CONTEXT_SLOT] = createContext<OnboardingTheme | null>(null);
51
+ }
52
+ const OnboardingThemeContext = themeGlobal[THEME_CONTEXT_SLOT] as React.Context<OnboardingTheme | null>;
12
53
 
13
54
  export type OnboardingThemeProviderProps = {
14
55
  /** Partial overrides merged over the neutral default theme. */
package/src/types.ts CHANGED
@@ -340,7 +340,7 @@ export type OnboardingProgress = {
340
340
  * `answers.interests` silently becomes `answers.what_are_you_into_v2`, with no error anywhere. A
341
341
  * slot is the question's identity independent of its wording.
342
342
  *
343
- * FULLY ADDITIVE, AND LIVE SINCE 2026-07-28 (server `47dae92`). The deployed server sends it on
343
+ * FULLY ADDITIVE, AND LIVE SINCE 2026-07-28. The deployed server sends it on
344
344
  * `progress` for every AI-GENERATED question, as `adaptive_<n>` 1-based over adaptive answers, and
345
345
  * for a CONFIGURED question only when the tenant set one. Where the tenant set none the field is
346
346
  * simply absent. Every fallback is PER-CARD, so a thread that mixes slotted and unslotted cards
@@ -0,0 +1,39 @@
1
+ /**
2
+ * submitResult — the ONE delivery verdict every user-payload POST in this kit reports back.
3
+ *
4
+ * WHY IT IS SHARED, AND WHY IT IS NOT A BOOLEAN. Two surfaces post a payload the user typed —
5
+ * `submitReview` (`reviews/transport.ts`) and `submitQuestionnaireResponse`
6
+ * (`questionnaire/transport.ts`) — and NEITHER has a persisted queue behind it (unlike
7
+ * `analytics/eventQueue`, with its `maxSize`/`maxRetries`/backoff). Both sit behind a permanent
8
+ * once-gate: the host writes `wire_review_<id>_seen` / `wire_questionnaire_<id>_<ver>_seen` when the
9
+ * gate resolves, so a payload that dies in the socket is lost AND the user is never asked again.
10
+ * Reading the fate of the request is the only thing that lets a caller keep an undelivered payload
11
+ * recoverable, so the two surfaces must answer the same question in the same words.
12
+ *
13
+ * • `accepted` — the server answered 2xx. Stored.
14
+ * • `rejected` — the server ANSWERED, non-2xx. It was reached; it either stored the payload and
15
+ * lost the ack (a gateway 502 after the write commits) or deliberately refused it.
16
+ * Either way the server owns the outcome and a re-post risks a SECOND row.
17
+ * • `unsent` — nothing came back at all: no target, a missing/throwing `fetch`, a rejected
18
+ * request. **This is the ONLY outcome a caller may retry on.**
19
+ *
20
+ * ── WHY THREE VALUES AND NOT TWO (pinned here so the defect cannot move surface) ───────────────
21
+ *
22
+ * `submitReview` was a boolean for exactly one unpublished release, and the boolean WAS the bug.
23
+ * `false` meant both "nothing reached the server" and "the server answered non-2xx", and the caller
24
+ * treats `false` as "still owed" and re-posts. But the server mints its own row id
25
+ * (`create_review` / `_new_id()`), and neither `CreateReviewRequest` nor the questionnaire response
26
+ * body carries a client id, so there is NO idempotency key on the wire: a 502 returned AFTER the
27
+ * insert commits means the re-post writes a SECOND row. A response of any status proves the server
28
+ * was reached, and that is a different question from whether it liked the payload.
29
+ *
30
+ * ── THE RESIDUAL, STATED HONESTLY ─────────────────────────────────────────────────────────────
31
+ *
32
+ * `unsent` is not proof the server never got it. A connection dropped after the request was written
33
+ * — or after the row committed — surfaces as a thrown/rejected `fetch`, exactly like an offline
34
+ * device. Retrying only on `unsent` is therefore SAFER, not SAFE. Closing that window needs a
35
+ * CLIENT-MINTED IDEMPOTENCY KEY the server upserts on, which is a server change and not something
36
+ * the kit can fake. Until it exists, prefer losing a row over inventing one: a lost detractor is a
37
+ * gap in the data, a duplicated one is a lie in the data.
38
+ */
39
+ export type SubmitResult = "accepted" | "rejected" | "unsent";