@wireai/activation 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/AGENTS.md +73 -0
  2. package/CHANGELOG.md +572 -0
  3. package/INTEGRATION_PROMPT.md +55 -0
  4. package/LICENSE +21 -0
  5. package/README.md +923 -0
  6. package/dist/CenteredModal-C3qQBHsA.d.mts +38 -0
  7. package/dist/CenteredModal-Cdgns6--.d.ts +38 -0
  8. package/dist/coachmarks/index.d.mts +352 -0
  9. package/dist/coachmarks/index.d.ts +352 -0
  10. package/dist/coachmarks/index.js +944 -0
  11. package/dist/coachmarks/index.js.map +1 -0
  12. package/dist/coachmarks/index.mjs +918 -0
  13. package/dist/coachmarks/index.mjs.map +1 -0
  14. package/dist/decision-CFvGY6nP.d.ts +228 -0
  15. package/dist/decision-Cl8OFYzu.d.mts +228 -0
  16. package/dist/index.d.mts +1390 -0
  17. package/dist/index.d.ts +1390 -0
  18. package/dist/index.js +3260 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/index.mjs +3186 -0
  21. package/dist/index.mjs.map +1 -0
  22. package/dist/questionnaire/index.d.mts +249 -0
  23. package/dist/questionnaire/index.d.ts +249 -0
  24. package/dist/questionnaire/index.js +922 -0
  25. package/dist/questionnaire/index.js.map +1 -0
  26. package/dist/questionnaire/index.mjs +906 -0
  27. package/dist/questionnaire/index.mjs.map +1 -0
  28. package/dist/reviews/index.d.mts +213 -0
  29. package/dist/reviews/index.d.ts +213 -0
  30. package/dist/reviews/index.js +1001 -0
  31. package/dist/reviews/index.js.map +1 -0
  32. package/dist/reviews/index.mjs +971 -0
  33. package/dist/reviews/index.mjs.map +1 -0
  34. package/dist/showcase/index.d.mts +119 -0
  35. package/dist/showcase/index.d.ts +119 -0
  36. package/dist/showcase/index.js +660 -0
  37. package/dist/showcase/index.js.map +1 -0
  38. package/dist/showcase/index.mjs +651 -0
  39. package/dist/showcase/index.mjs.map +1 -0
  40. package/dist/types-BKfpdZzX.d.mts +84 -0
  41. package/dist/types-BKfpdZzX.d.ts +84 -0
  42. package/dist/types-CMuOexw0.d.mts +116 -0
  43. package/dist/types-CMuOexw0.d.ts +116 -0
  44. package/dist/types-GL_hQ0TN.d.mts +46 -0
  45. package/dist/types-GL_hQ0TN.d.ts +46 -0
  46. package/llms.txt +36 -0
  47. package/metro/index.d.ts +32 -0
  48. package/metro/index.js +83 -0
  49. package/package.json +171 -0
  50. package/src/DemoOnboarding.tsx +133 -0
  51. package/src/OnboardingFlow.tsx +504 -0
  52. package/src/WireOnboarding.tsx +248 -0
  53. package/src/analytics/analyticsEvent.ts +69 -0
  54. package/src/analytics/reportClientEvent.ts +119 -0
  55. package/src/analytics/sendPreview.ts +73 -0
  56. package/src/attribution/attribution.ts +46 -0
  57. package/src/cards/ChipSelectCard.tsx +265 -0
  58. package/src/cards/InterstitialCard.tsx +299 -0
  59. package/src/cards/NumberStepperCard.tsx +121 -0
  60. package/src/cards/SelectionCard.tsx +270 -0
  61. package/src/cards/StatusCard.tsx +170 -0
  62. package/src/cards/TextInputCard.tsx +147 -0
  63. package/src/cards/index.ts +23 -0
  64. package/src/coachmarks/CoachmarkOverlayHost.tsx +44 -0
  65. package/src/coachmarks/CoachmarkProvider.tsx +108 -0
  66. package/src/coachmarks/GestureHint.tsx +331 -0
  67. package/src/coachmarks/SpotlightOverlay.tsx +254 -0
  68. package/src/coachmarks/coachmarkAnchorRegistry.ts +70 -0
  69. package/src/coachmarks/coachmarkOverlayStore.ts +63 -0
  70. package/src/coachmarks/index.ts +62 -0
  71. package/src/coachmarks/runtime.ts +103 -0
  72. package/src/coachmarks/selectTourSteps.ts +45 -0
  73. package/src/coachmarks/types.ts +58 -0
  74. package/src/coachmarks/useCoachmarkAnchor.ts +49 -0
  75. package/src/coachmarks/useCoachmarkTour.ts +200 -0
  76. package/src/components/AnimatedSparkle.tsx +101 -0
  77. package/src/components/Button.tsx +129 -0
  78. package/src/components/CardHandoff.tsx +212 -0
  79. package/src/components/CardLayout.tsx +103 -0
  80. package/src/components/CenteredModal.tsx +184 -0
  81. package/src/components/CompletionView.tsx +186 -0
  82. package/src/components/DoneBlock.tsx +37 -0
  83. package/src/components/ErrorBlock.tsx +42 -0
  84. package/src/components/Illustration.tsx +36 -0
  85. package/src/components/LoadingBlock.tsx +182 -0
  86. package/src/components/LoadingScreen.tsx +35 -0
  87. package/src/components/OnboardingScaffold.tsx +105 -0
  88. package/src/components/StepProgress.tsx +85 -0
  89. package/src/components/loaderChrome.ts +28 -0
  90. package/src/config/onboardingFlag.ts +39 -0
  91. package/src/config/wireConfigFromEnv.ts +52 -0
  92. package/src/device/deviceContext.ts +158 -0
  93. package/src/features/WireFeaturesProvider.tsx +81 -0
  94. package/src/features/cache.ts +92 -0
  95. package/src/features/defaults.ts +67 -0
  96. package/src/features/fetchWireFeatures.ts +86 -0
  97. package/src/features/index.ts +39 -0
  98. package/src/features/types.ts +61 -0
  99. package/src/features/useWireFeatures.ts +57 -0
  100. package/src/identity/userIdentity.ts +100 -0
  101. package/src/illustrations/defaultIllustrations.tsx +99 -0
  102. package/src/index.ts +159 -0
  103. package/src/motion/centeredModalMotion.ts +45 -0
  104. package/src/motion/motionSpec.ts +133 -0
  105. package/src/motion/useReducedMotion.ts +43 -0
  106. package/src/questionnaire/QuestionnaireGate.tsx +235 -0
  107. package/src/questionnaire/decision.ts +63 -0
  108. package/src/questionnaire/index.ts +54 -0
  109. package/src/questionnaire/runtime.ts +20 -0
  110. package/src/questionnaire/transport.ts +83 -0
  111. package/src/questionnaire/types.ts +182 -0
  112. package/src/questionnaire/useQuestionnaireGate.ts +159 -0
  113. package/src/reviews/ReviewGate.tsx +316 -0
  114. package/src/reviews/ReviewModal.tsx +13 -0
  115. package/src/reviews/decision.ts +120 -0
  116. package/src/reviews/equality.ts +54 -0
  117. package/src/reviews/expo-store-review.d.ts +16 -0
  118. package/src/reviews/feedbackForm.ts +41 -0
  119. package/src/reviews/index.ts +78 -0
  120. package/src/reviews/presentation.ts +29 -0
  121. package/src/reviews/runtime.ts +45 -0
  122. package/src/reviews/storeReview.ts +62 -0
  123. package/src/reviews/transport.ts +89 -0
  124. package/src/reviews/types.ts +172 -0
  125. package/src/reviews/useReviewGate.ts +137 -0
  126. package/src/session/persistedSession.ts +143 -0
  127. package/src/session-analytics/index.ts +18 -0
  128. package/src/session-analytics/reportSessionStart.ts +144 -0
  129. package/src/session-analytics/useSessionStart.ts +114 -0
  130. package/src/showcase/FeatureShowcase.tsx +233 -0
  131. package/src/showcase/index.ts +19 -0
  132. package/src/showcase/selectShowcaseSlides.ts +45 -0
  133. package/src/showcase/showcaseColors.ts +91 -0
  134. package/src/showcase/types.ts +74 -0
  135. package/src/theme/ThemeContext.tsx +38 -0
  136. package/src/theme/defaultTheme.ts +70 -0
  137. package/src/theme/themeFromBrand.ts +93 -0
  138. package/src/theme/types.ts +88 -0
  139. package/src/theme/typography.ts +46 -0
  140. package/src/types.ts +215 -0
  141. package/src/utils/deriveAnswers.ts +43 -0
  142. package/src/utils/readProgress.ts +33 -0
@@ -0,0 +1,249 @@
1
+ import React__default from 'react';
2
+ import { O as OnboardingTheme } from '../types-BKfpdZzX.mjs';
3
+ import { C as CoachmarkStorage } from '../types-GL_hQ0TN.mjs';
4
+ import { W as WireFeatures, a as WireFeaturesConfig } from '../types-CMuOexw0.mjs';
5
+ export { G as GateRules, a as GateSignals, e as evaluateGate, r as resolveRules } from '../decision-Cl8OFYzu.mjs';
6
+
7
+ /**
8
+ * The firing verdict - the SAME contract on both sides of the seam. `decideQuestionnaire`
9
+ * turns local rules into one of these; a server `{fire, reason}` (from
10
+ * GET /v1/questionnaires/decision) is the exact same shape and OVERRIDES the local one.
11
+ */
12
+ interface QuestionnaireDecision {
13
+ fire: boolean;
14
+ reason?: string;
15
+ }
16
+ /**
17
+ * The questionnaire the kit renders. Comes from the server decision (`questionnaire`
18
+ * field) or a local literal. `id` is required (the once-gate + keys derive from it); the
19
+ * labels and title are optional (sensible defaults render when absent). The optional
20
+ * firing-rule fields are the LOCAL fallback the decision hook evaluates when no server
21
+ * decision is passed, mirroring `ReviewConfig`.
22
+ */
23
+ interface QuestionnaireDefinition {
24
+ /** Gate key. Seen/last-shown/session keys derive from it (`wire_questionnaire_<id>_*`). */
25
+ id: string;
26
+ /** Popup heading. Defaults to a neutral prompt when absent. */
27
+ title?: string;
28
+ /** Label for the MANDATORY opinion box ("what do you think about the app"). */
29
+ opinion_label?: string;
30
+ /** Label for the first OPTIONAL box ("help us improve"). */
31
+ improve_label?: string;
32
+ /** Label for the second OPTIONAL box ("suggestions"). */
33
+ suggestions_label?: string;
34
+ /** Master local switch. Default true. */
35
+ enabled?: boolean;
36
+ /** Require at least this many sessions on the device before showing. Default 0. */
37
+ minSessions?: number;
38
+ /** Require at least this many tracked app events before showing. Default 0. */
39
+ minEvents?: number;
40
+ /** Suppress a re-prompt within this many days of the last shown. Default 0 (off). */
41
+ cooldownDays?: number;
42
+ /** Show at most once per app version (seen-gate keyed by `appVersion`). Default true. */
43
+ oncePerVersion?: boolean;
44
+ /** The current app version, for the once-per-version seen gate. */
45
+ appVersion?: string;
46
+ /**
47
+ * Client fallback ONLY: if no server decision arrives within this many ms of mount,
48
+ * fall back to the local rules. Null/absent → evaluate local rules immediately.
49
+ */
50
+ timeoutFallbackMs?: number | null;
51
+ }
52
+ /** The user's three answers. `opinion` is the only mandatory one. */
53
+ interface QuestionnaireAnswers {
54
+ /** The mandatory "what do you think about the app" free text. */
55
+ opinion: string;
56
+ /** The optional "help us improve" free text. */
57
+ improve?: string;
58
+ /** The optional "suggestions" free text. */
59
+ suggestions?: string;
60
+ }
61
+ /** Non-PII correlation extras sent with a submission (the `meta` bucket in the POST body). */
62
+ interface QuestionnaireMeta {
63
+ user_id?: string | null;
64
+ device_key?: string | null;
65
+ session_count?: number | null;
66
+ }
67
+ /**
68
+ * The POST body sent to `POST /v1/questionnaires/{id}/responses` (mirrors the server
69
+ * contract). `opinion` is always present; `improve`/`suggestions` are null when empty.
70
+ */
71
+ interface QuestionnaireSubmission {
72
+ session_id: string | null;
73
+ answers: {
74
+ opinion: string;
75
+ improve: string | null;
76
+ suggestions: string | null;
77
+ };
78
+ meta: QuestionnaireMeta;
79
+ }
80
+ /**
81
+ * The GET /v1/questionnaires/decision response. `fire` gates showing; `questionnaire` is
82
+ * the definition to render when firing. Hard fail-open on the transport: an unreachable
83
+ * endpoint or a 404 resolves to null so the host simply never shows the gate.
84
+ */
85
+ interface QuestionnaireDecisionResponse {
86
+ fire: boolean;
87
+ reason?: string;
88
+ questionnaire?: QuestionnaireDefinition | null;
89
+ }
90
+ /** The analytics moments, mirrored on the coachmark callback convention (no PII, no free text). */
91
+ type QuestionnaireGateEventName = "questionnaire_prompt_shown" | "questionnaire_submitted" | "questionnaire_dismissed";
92
+ /** One analytics moment. Carries the gate id only; answers ride ONLY in the POST body. */
93
+ interface QuestionnaireGateEvent {
94
+ name: QuestionnaireGateEventName;
95
+ /** The gate id (`questionnaire.id`). */
96
+ id: string;
97
+ }
98
+ /** Where kit → server requests go (same tenant creds as the onboarding flow). */
99
+ interface QuestionnaireTarget {
100
+ /** Base server URL (same as `WireOnboardingConfig.serverUrl`). */
101
+ serverUrl: string;
102
+ /** Tenant API key, sent as `Authorization: Bearer`. */
103
+ apiKey: string;
104
+ }
105
+ /** Props for the `QuestionnaireGate` popup component. */
106
+ interface QuestionnaireGateProps {
107
+ /** The questionnaire to render (title + labels + id). */
108
+ questionnaire: QuestionnaireDefinition;
109
+ /** Where to POST the submission. Omit to skip the network (callbacks still fire). */
110
+ target?: QuestionnaireTarget;
111
+ /** The onboarding/session id correlated with the response, when known. */
112
+ sessionId?: string | null;
113
+ /** Small non-PII extras stored with the response (user_id, device_key, session_count). */
114
+ meta?: QuestionnaireMeta;
115
+ /** Fired at each analytics moment (host wires it to its own analytics). No PII. */
116
+ onEvent?: (event: QuestionnaireGateEvent) => void;
117
+ /** Called once the gate is resolved (submitted or dismissed). */
118
+ onResolved?: () => void;
119
+ /** Called when the gate first renders to the user (also fires `questionnaire_prompt_shown`). */
120
+ onShown?: () => void;
121
+ /** Partial theme merged over the active onboarding theme. */
122
+ theme?: Partial<OnboardingTheme>;
123
+ /** Show a Skip/Not now control. Default true (always skippable, never a dead end). */
124
+ dismissible?: boolean;
125
+ }
126
+ /** Options for `useQuestionnaireGate` - the WHEN decision. */
127
+ interface UseQuestionnaireGateOptions {
128
+ /** The questionnaire definition (id + local firing rules). */
129
+ config: QuestionnaireDefinition;
130
+ /** Server-provided decision (from GET /v1/questionnaires/decision). OVERRIDES local rules. */
131
+ decision?: QuestionnaireDecision;
132
+ /** Current tracked app-event count (host-owned), for the `minEvents` rule. */
133
+ events?: number;
134
+ /** Sync gate storage. Defaults to the storage injected by CoachmarkProvider. */
135
+ storage?: CoachmarkStorage | null;
136
+ /** Testing override; defaults to the global `isTestingCoachmark` flag. */
137
+ isTesting?: boolean;
138
+ /**
139
+ * OPTIONAL feature-flag wiring - the questionnaire MASTER switch. When
140
+ * `flags.questionnaire.enabled` is false, the gate NEVER fires (`visible` stays false,
141
+ * verdict reason `feature_disabled`), whatever the local rules or a server decision say -
142
+ * it composes as an extra AND on top of the decision seam. Provide pre-resolved `features`,
143
+ * OR a `featuresConfig` to lazily fetch once, OR mount a `WireFeaturesProvider` above
144
+ * (context is read automatically). Omit all three and the questionnaire stays on (fail-open).
145
+ */
146
+ features?: WireFeatures;
147
+ /** Lazy-fetch config for the flags, used when `features` is absent and no provider is above. */
148
+ featuresConfig?: WireFeaturesConfig;
149
+ }
150
+ /** What `useQuestionnaireGate` returns. */
151
+ interface QuestionnaireGateController {
152
+ /** Whether the gate should be shown right now. */
153
+ visible: boolean;
154
+ /** The resolved verdict (local rules or the server override). For debugging/analytics. */
155
+ decision: QuestionnaireDecision;
156
+ /** Persist the last-shown timestamp. Call when the gate becomes visible. */
157
+ markShown: () => void;
158
+ /** Write the once-per-user seen gate. Call when the gate is resolved. */
159
+ markResolved: () => void;
160
+ }
161
+
162
+ /**
163
+ * QuestionnaireGate - the skippable pre-onboarding questionnaire popup.
164
+ *
165
+ * A centered popup (over the shared `CenteredModal` shell) with three inputs:
166
+ * • a MANDATORY multiline "what do you think about the app" box (~4 lines), and
167
+ * • two OPTIONAL smaller boxes ("help us improve" + "suggestions").
168
+ * Send is disabled ONLY until the opinion box has text; the two optional boxes never gate it.
169
+ * The gate is ALWAYS skippable (a "Not now"/"Skip" link plays the popup exit), so it is never
170
+ * a dead end. On send it builds the contract body and fire-and-forgets it to the Wire server,
171
+ * then shows a warm thank-you until the user taps the backdrop to close.
172
+ *
173
+ * The form NEVER asks for identity (no name / email / contact field): the host already
174
+ * identifies the user via `sessionId` + `meta` (user_id, device_key, session_count), which is
175
+ * all the POST body carries alongside the answers. Every visual value comes from the
176
+ * OnboardingTheme; analytics are callback-based (no PII - answers go ONLY in the POST body).
177
+ */
178
+
179
+ declare const QuestionnaireGate: React__default.FC<QuestionnaireGateProps>;
180
+
181
+ declare const useQuestionnaireGate: ({ config: configProp, decision: decisionProp, events, storage, isTesting, features, featuresConfig, }: UseQuestionnaireGateOptions) => QuestionnaireGateController;
182
+
183
+ /**
184
+ * The seam. `decision` absent → the local verdict stands; present → it overrides. Same
185
+ * contract philosophy as `decideReview(local, decision?)`.
186
+ */
187
+ declare const decideQuestionnaire: (local: QuestionnaireDecision, decision?: QuestionnaireDecision) => QuestionnaireDecision;
188
+ /**
189
+ * Build the `POST /v1/questionnaires/{id}/responses` body. `opinion` is the only mandatory
190
+ * answer (trimmed); `improve`/`suggestions` are trimmed and sent as `null` when empty. The
191
+ * `meta` bucket carries the non-PII correlation fields (user_id / device_key / session_count),
192
+ * each defaulting to null. Free text lives ONLY here, never in analytics events.
193
+ */
194
+ declare const buildQuestionnaireSubmission: (input: {
195
+ answers: QuestionnaireAnswers;
196
+ sessionId?: string | null;
197
+ meta?: QuestionnaireMeta;
198
+ }) => QuestionnaireSubmission;
199
+
200
+ /**
201
+ * transport.ts - kit → Wire server requests for the questionnaire module, all fire-and-forget
202
+ * (a pre-onboarding questionnaire must NEVER break the app). Mirrors `submitReview`: a thin
203
+ * fetch wrapper, Bearer tenant key, swallow every error.
204
+ *
205
+ * • submitQuestionnaireResponse → POST {serverUrl}/v1/questionnaires/{id}/responses
206
+ * • fetchQuestionnaireDecision → GET {serverUrl}/v1/questionnaires/decision (best-effort)
207
+ *
208
+ * The decision fetch is HARD fail-open on the "never show" side: an unreachable endpoint, a
209
+ * non-2xx (incl. 404), a missing `fetch`, or bad JSON all resolve to null, so a host wiring
210
+ * pure server-directed firing simply shows nothing - exactly the reviews decision seam.
211
+ */
212
+
213
+ /**
214
+ * POST a questionnaire response. Fire-and-forget: a missing target, a missing id, a build
215
+ * error, a missing `fetch`, or a network failure is swallowed and the call returns immediately.
216
+ */
217
+ declare const submitQuestionnaireResponse: (target: QuestionnaireTarget | undefined, id: string, submission: QuestionnaireSubmission) => void;
218
+ /** Options for the best-effort decision fetch. `deviceKey` groups a device's sessions server-side. */
219
+ interface FetchQuestionnaireDecisionOptions {
220
+ /** The onboarding/session id to correlate with, when known. */
221
+ sessionId?: string;
222
+ /** A stable, non-PII device id - the decision endpoint reads it for min-sessions. */
223
+ deviceKey?: string;
224
+ }
225
+ /**
226
+ * Best-effort fetch of the server firing decision. Returns the parsed
227
+ * `{ fire, reason, questionnaire }` on a 2xx, else null. NEVER throws - any failure resolves
228
+ * to null (fail-safe: the host shows nothing). The kit does not call this internally; a host
229
+ * that wants server-directed firing awaits it and passes the result to `useQuestionnaireGate`
230
+ * (the same AI seam the reviews gate uses).
231
+ */
232
+ declare const fetchQuestionnaireDecision: (target: QuestionnaireTarget | undefined, options?: FetchQuestionnaireDecisionOptions) => Promise<QuestionnaireDecisionResponse | null>;
233
+
234
+ /**
235
+ * Runtime helpers for the questionnaire gate: the `wire_questionnaire_*` gate keys. The
236
+ * gate reuses the SAME injected sync storage + global testing flag as the coachmark engine
237
+ * (via `resolveStorage`/`readInt`/`writeInt`, re-exported from the reviews runtime), so a
238
+ * host that mounted CoachmarkProvider gets questionnaire gating for free - no second storage
239
+ * to wire. Once-per-user is keyed on the questionnaire `id`.
240
+ */
241
+
242
+ /** Once-gate key. Keyed by app version when `oncePerVersion` is on, so a new release re-enables. */
243
+ declare const questionnaireSeenKey: (id: string, version?: string) => string;
244
+ /** Last-shown timestamp key (epoch ms), for the cooldown rule. */
245
+ declare const questionnaireLastShownKey: (id: string) => string;
246
+ /** Session-count key, incremented once per gate mount, for the min-sessions rule. */
247
+ declare const questionnaireSessionsKey: (id: string) => string;
248
+
249
+ export { type FetchQuestionnaireDecisionOptions, type QuestionnaireAnswers, type QuestionnaireDecision, type QuestionnaireDecisionResponse, type QuestionnaireDefinition, QuestionnaireGate, type QuestionnaireGateController, type QuestionnaireGateEvent, type QuestionnaireGateEventName, type QuestionnaireGateProps, type QuestionnaireMeta, type QuestionnaireSubmission, type QuestionnaireTarget, type UseQuestionnaireGateOptions, buildQuestionnaireSubmission, decideQuestionnaire, fetchQuestionnaireDecision, questionnaireLastShownKey, questionnaireSeenKey, questionnaireSessionsKey, submitQuestionnaireResponse, useQuestionnaireGate };
@@ -0,0 +1,249 @@
1
+ import React__default from 'react';
2
+ import { O as OnboardingTheme } from '../types-BKfpdZzX.js';
3
+ import { C as CoachmarkStorage } from '../types-GL_hQ0TN.js';
4
+ import { W as WireFeatures, a as WireFeaturesConfig } from '../types-CMuOexw0.js';
5
+ export { G as GateRules, a as GateSignals, e as evaluateGate, r as resolveRules } from '../decision-CFvGY6nP.js';
6
+
7
+ /**
8
+ * The firing verdict - the SAME contract on both sides of the seam. `decideQuestionnaire`
9
+ * turns local rules into one of these; a server `{fire, reason}` (from
10
+ * GET /v1/questionnaires/decision) is the exact same shape and OVERRIDES the local one.
11
+ */
12
+ interface QuestionnaireDecision {
13
+ fire: boolean;
14
+ reason?: string;
15
+ }
16
+ /**
17
+ * The questionnaire the kit renders. Comes from the server decision (`questionnaire`
18
+ * field) or a local literal. `id` is required (the once-gate + keys derive from it); the
19
+ * labels and title are optional (sensible defaults render when absent). The optional
20
+ * firing-rule fields are the LOCAL fallback the decision hook evaluates when no server
21
+ * decision is passed, mirroring `ReviewConfig`.
22
+ */
23
+ interface QuestionnaireDefinition {
24
+ /** Gate key. Seen/last-shown/session keys derive from it (`wire_questionnaire_<id>_*`). */
25
+ id: string;
26
+ /** Popup heading. Defaults to a neutral prompt when absent. */
27
+ title?: string;
28
+ /** Label for the MANDATORY opinion box ("what do you think about the app"). */
29
+ opinion_label?: string;
30
+ /** Label for the first OPTIONAL box ("help us improve"). */
31
+ improve_label?: string;
32
+ /** Label for the second OPTIONAL box ("suggestions"). */
33
+ suggestions_label?: string;
34
+ /** Master local switch. Default true. */
35
+ enabled?: boolean;
36
+ /** Require at least this many sessions on the device before showing. Default 0. */
37
+ minSessions?: number;
38
+ /** Require at least this many tracked app events before showing. Default 0. */
39
+ minEvents?: number;
40
+ /** Suppress a re-prompt within this many days of the last shown. Default 0 (off). */
41
+ cooldownDays?: number;
42
+ /** Show at most once per app version (seen-gate keyed by `appVersion`). Default true. */
43
+ oncePerVersion?: boolean;
44
+ /** The current app version, for the once-per-version seen gate. */
45
+ appVersion?: string;
46
+ /**
47
+ * Client fallback ONLY: if no server decision arrives within this many ms of mount,
48
+ * fall back to the local rules. Null/absent → evaluate local rules immediately.
49
+ */
50
+ timeoutFallbackMs?: number | null;
51
+ }
52
+ /** The user's three answers. `opinion` is the only mandatory one. */
53
+ interface QuestionnaireAnswers {
54
+ /** The mandatory "what do you think about the app" free text. */
55
+ opinion: string;
56
+ /** The optional "help us improve" free text. */
57
+ improve?: string;
58
+ /** The optional "suggestions" free text. */
59
+ suggestions?: string;
60
+ }
61
+ /** Non-PII correlation extras sent with a submission (the `meta` bucket in the POST body). */
62
+ interface QuestionnaireMeta {
63
+ user_id?: string | null;
64
+ device_key?: string | null;
65
+ session_count?: number | null;
66
+ }
67
+ /**
68
+ * The POST body sent to `POST /v1/questionnaires/{id}/responses` (mirrors the server
69
+ * contract). `opinion` is always present; `improve`/`suggestions` are null when empty.
70
+ */
71
+ interface QuestionnaireSubmission {
72
+ session_id: string | null;
73
+ answers: {
74
+ opinion: string;
75
+ improve: string | null;
76
+ suggestions: string | null;
77
+ };
78
+ meta: QuestionnaireMeta;
79
+ }
80
+ /**
81
+ * The GET /v1/questionnaires/decision response. `fire` gates showing; `questionnaire` is
82
+ * the definition to render when firing. Hard fail-open on the transport: an unreachable
83
+ * endpoint or a 404 resolves to null so the host simply never shows the gate.
84
+ */
85
+ interface QuestionnaireDecisionResponse {
86
+ fire: boolean;
87
+ reason?: string;
88
+ questionnaire?: QuestionnaireDefinition | null;
89
+ }
90
+ /** The analytics moments, mirrored on the coachmark callback convention (no PII, no free text). */
91
+ type QuestionnaireGateEventName = "questionnaire_prompt_shown" | "questionnaire_submitted" | "questionnaire_dismissed";
92
+ /** One analytics moment. Carries the gate id only; answers ride ONLY in the POST body. */
93
+ interface QuestionnaireGateEvent {
94
+ name: QuestionnaireGateEventName;
95
+ /** The gate id (`questionnaire.id`). */
96
+ id: string;
97
+ }
98
+ /** Where kit → server requests go (same tenant creds as the onboarding flow). */
99
+ interface QuestionnaireTarget {
100
+ /** Base server URL (same as `WireOnboardingConfig.serverUrl`). */
101
+ serverUrl: string;
102
+ /** Tenant API key, sent as `Authorization: Bearer`. */
103
+ apiKey: string;
104
+ }
105
+ /** Props for the `QuestionnaireGate` popup component. */
106
+ interface QuestionnaireGateProps {
107
+ /** The questionnaire to render (title + labels + id). */
108
+ questionnaire: QuestionnaireDefinition;
109
+ /** Where to POST the submission. Omit to skip the network (callbacks still fire). */
110
+ target?: QuestionnaireTarget;
111
+ /** The onboarding/session id correlated with the response, when known. */
112
+ sessionId?: string | null;
113
+ /** Small non-PII extras stored with the response (user_id, device_key, session_count). */
114
+ meta?: QuestionnaireMeta;
115
+ /** Fired at each analytics moment (host wires it to its own analytics). No PII. */
116
+ onEvent?: (event: QuestionnaireGateEvent) => void;
117
+ /** Called once the gate is resolved (submitted or dismissed). */
118
+ onResolved?: () => void;
119
+ /** Called when the gate first renders to the user (also fires `questionnaire_prompt_shown`). */
120
+ onShown?: () => void;
121
+ /** Partial theme merged over the active onboarding theme. */
122
+ theme?: Partial<OnboardingTheme>;
123
+ /** Show a Skip/Not now control. Default true (always skippable, never a dead end). */
124
+ dismissible?: boolean;
125
+ }
126
+ /** Options for `useQuestionnaireGate` - the WHEN decision. */
127
+ interface UseQuestionnaireGateOptions {
128
+ /** The questionnaire definition (id + local firing rules). */
129
+ config: QuestionnaireDefinition;
130
+ /** Server-provided decision (from GET /v1/questionnaires/decision). OVERRIDES local rules. */
131
+ decision?: QuestionnaireDecision;
132
+ /** Current tracked app-event count (host-owned), for the `minEvents` rule. */
133
+ events?: number;
134
+ /** Sync gate storage. Defaults to the storage injected by CoachmarkProvider. */
135
+ storage?: CoachmarkStorage | null;
136
+ /** Testing override; defaults to the global `isTestingCoachmark` flag. */
137
+ isTesting?: boolean;
138
+ /**
139
+ * OPTIONAL feature-flag wiring - the questionnaire MASTER switch. When
140
+ * `flags.questionnaire.enabled` is false, the gate NEVER fires (`visible` stays false,
141
+ * verdict reason `feature_disabled`), whatever the local rules or a server decision say -
142
+ * it composes as an extra AND on top of the decision seam. Provide pre-resolved `features`,
143
+ * OR a `featuresConfig` to lazily fetch once, OR mount a `WireFeaturesProvider` above
144
+ * (context is read automatically). Omit all three and the questionnaire stays on (fail-open).
145
+ */
146
+ features?: WireFeatures;
147
+ /** Lazy-fetch config for the flags, used when `features` is absent and no provider is above. */
148
+ featuresConfig?: WireFeaturesConfig;
149
+ }
150
+ /** What `useQuestionnaireGate` returns. */
151
+ interface QuestionnaireGateController {
152
+ /** Whether the gate should be shown right now. */
153
+ visible: boolean;
154
+ /** The resolved verdict (local rules or the server override). For debugging/analytics. */
155
+ decision: QuestionnaireDecision;
156
+ /** Persist the last-shown timestamp. Call when the gate becomes visible. */
157
+ markShown: () => void;
158
+ /** Write the once-per-user seen gate. Call when the gate is resolved. */
159
+ markResolved: () => void;
160
+ }
161
+
162
+ /**
163
+ * QuestionnaireGate - the skippable pre-onboarding questionnaire popup.
164
+ *
165
+ * A centered popup (over the shared `CenteredModal` shell) with three inputs:
166
+ * • a MANDATORY multiline "what do you think about the app" box (~4 lines), and
167
+ * • two OPTIONAL smaller boxes ("help us improve" + "suggestions").
168
+ * Send is disabled ONLY until the opinion box has text; the two optional boxes never gate it.
169
+ * The gate is ALWAYS skippable (a "Not now"/"Skip" link plays the popup exit), so it is never
170
+ * a dead end. On send it builds the contract body and fire-and-forgets it to the Wire server,
171
+ * then shows a warm thank-you until the user taps the backdrop to close.
172
+ *
173
+ * The form NEVER asks for identity (no name / email / contact field): the host already
174
+ * identifies the user via `sessionId` + `meta` (user_id, device_key, session_count), which is
175
+ * all the POST body carries alongside the answers. Every visual value comes from the
176
+ * OnboardingTheme; analytics are callback-based (no PII - answers go ONLY in the POST body).
177
+ */
178
+
179
+ declare const QuestionnaireGate: React__default.FC<QuestionnaireGateProps>;
180
+
181
+ declare const useQuestionnaireGate: ({ config: configProp, decision: decisionProp, events, storage, isTesting, features, featuresConfig, }: UseQuestionnaireGateOptions) => QuestionnaireGateController;
182
+
183
+ /**
184
+ * The seam. `decision` absent → the local verdict stands; present → it overrides. Same
185
+ * contract philosophy as `decideReview(local, decision?)`.
186
+ */
187
+ declare const decideQuestionnaire: (local: QuestionnaireDecision, decision?: QuestionnaireDecision) => QuestionnaireDecision;
188
+ /**
189
+ * Build the `POST /v1/questionnaires/{id}/responses` body. `opinion` is the only mandatory
190
+ * answer (trimmed); `improve`/`suggestions` are trimmed and sent as `null` when empty. The
191
+ * `meta` bucket carries the non-PII correlation fields (user_id / device_key / session_count),
192
+ * each defaulting to null. Free text lives ONLY here, never in analytics events.
193
+ */
194
+ declare const buildQuestionnaireSubmission: (input: {
195
+ answers: QuestionnaireAnswers;
196
+ sessionId?: string | null;
197
+ meta?: QuestionnaireMeta;
198
+ }) => QuestionnaireSubmission;
199
+
200
+ /**
201
+ * transport.ts - kit → Wire server requests for the questionnaire module, all fire-and-forget
202
+ * (a pre-onboarding questionnaire must NEVER break the app). Mirrors `submitReview`: a thin
203
+ * fetch wrapper, Bearer tenant key, swallow every error.
204
+ *
205
+ * • submitQuestionnaireResponse → POST {serverUrl}/v1/questionnaires/{id}/responses
206
+ * • fetchQuestionnaireDecision → GET {serverUrl}/v1/questionnaires/decision (best-effort)
207
+ *
208
+ * The decision fetch is HARD fail-open on the "never show" side: an unreachable endpoint, a
209
+ * non-2xx (incl. 404), a missing `fetch`, or bad JSON all resolve to null, so a host wiring
210
+ * pure server-directed firing simply shows nothing - exactly the reviews decision seam.
211
+ */
212
+
213
+ /**
214
+ * POST a questionnaire response. Fire-and-forget: a missing target, a missing id, a build
215
+ * error, a missing `fetch`, or a network failure is swallowed and the call returns immediately.
216
+ */
217
+ declare const submitQuestionnaireResponse: (target: QuestionnaireTarget | undefined, id: string, submission: QuestionnaireSubmission) => void;
218
+ /** Options for the best-effort decision fetch. `deviceKey` groups a device's sessions server-side. */
219
+ interface FetchQuestionnaireDecisionOptions {
220
+ /** The onboarding/session id to correlate with, when known. */
221
+ sessionId?: string;
222
+ /** A stable, non-PII device id - the decision endpoint reads it for min-sessions. */
223
+ deviceKey?: string;
224
+ }
225
+ /**
226
+ * Best-effort fetch of the server firing decision. Returns the parsed
227
+ * `{ fire, reason, questionnaire }` on a 2xx, else null. NEVER throws - any failure resolves
228
+ * to null (fail-safe: the host shows nothing). The kit does not call this internally; a host
229
+ * that wants server-directed firing awaits it and passes the result to `useQuestionnaireGate`
230
+ * (the same AI seam the reviews gate uses).
231
+ */
232
+ declare const fetchQuestionnaireDecision: (target: QuestionnaireTarget | undefined, options?: FetchQuestionnaireDecisionOptions) => Promise<QuestionnaireDecisionResponse | null>;
233
+
234
+ /**
235
+ * Runtime helpers for the questionnaire gate: the `wire_questionnaire_*` gate keys. The
236
+ * gate reuses the SAME injected sync storage + global testing flag as the coachmark engine
237
+ * (via `resolveStorage`/`readInt`/`writeInt`, re-exported from the reviews runtime), so a
238
+ * host that mounted CoachmarkProvider gets questionnaire gating for free - no second storage
239
+ * to wire. Once-per-user is keyed on the questionnaire `id`.
240
+ */
241
+
242
+ /** Once-gate key. Keyed by app version when `oncePerVersion` is on, so a new release re-enables. */
243
+ declare const questionnaireSeenKey: (id: string, version?: string) => string;
244
+ /** Last-shown timestamp key (epoch ms), for the cooldown rule. */
245
+ declare const questionnaireLastShownKey: (id: string) => string;
246
+ /** Session-count key, incremented once per gate mount, for the min-sessions rule. */
247
+ declare const questionnaireSessionsKey: (id: string) => string;
248
+
249
+ export { type FetchQuestionnaireDecisionOptions, type QuestionnaireAnswers, type QuestionnaireDecision, type QuestionnaireDecisionResponse, type QuestionnaireDefinition, QuestionnaireGate, type QuestionnaireGateController, type QuestionnaireGateEvent, type QuestionnaireGateEventName, type QuestionnaireGateProps, type QuestionnaireMeta, type QuestionnaireSubmission, type QuestionnaireTarget, type UseQuestionnaireGateOptions, buildQuestionnaireSubmission, decideQuestionnaire, fetchQuestionnaireDecision, questionnaireLastShownKey, questionnaireSeenKey, questionnaireSessionsKey, submitQuestionnaireResponse, useQuestionnaireGate };