@wireai/activation 0.8.0 → 0.9.1

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 (91) hide show
  1. package/AGENTS.md +29 -5
  2. package/CHANGELOG.md +202 -6
  3. package/README.md +105 -10
  4. package/dist/analytics/index.d.mts +6 -6
  5. package/dist/analytics/index.d.ts +6 -6
  6. package/dist/analytics/index.js +447 -49
  7. package/dist/analytics/index.js.map +1 -1
  8. package/dist/analytics/index.mjs +447 -49
  9. package/dist/analytics/index.mjs.map +1 -1
  10. package/dist/coachmarks/index.d.mts +2 -2
  11. package/dist/coachmarks/index.d.ts +2 -2
  12. package/dist/coachmarks/index.js.map +1 -1
  13. package/dist/coachmarks/index.mjs.map +1 -1
  14. package/dist/{currentSession-f7LWcdWG.d.ts → currentSession-61dcm3V-.d.ts} +10 -2
  15. package/dist/{currentSession-d9CrBxwe.d.mts → currentSession-Bs2JfTJ8.d.mts} +10 -2
  16. package/dist/{decision-BzbiKwk3.d.mts → decision-Bl_M2y3r.d.mts} +1 -1
  17. package/dist/{decision-plDEOCkt.d.ts → decision-Cau5KmP6.d.ts} +1 -1
  18. package/dist/index.d.mts +558 -15
  19. package/dist/index.d.ts +558 -15
  20. package/dist/index.js +1144 -375
  21. package/dist/index.js.map +1 -1
  22. package/dist/index.mjs +969 -207
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/questionnaire/index.d.mts +69 -23
  25. package/dist/questionnaire/index.d.ts +69 -23
  26. package/dist/questionnaire/index.js +643 -172
  27. package/dist/questionnaire/index.js.map +1 -1
  28. package/dist/questionnaire/index.mjs +608 -137
  29. package/dist/questionnaire/index.mjs.map +1 -1
  30. package/dist/reviews/index.d.mts +24 -12
  31. package/dist/reviews/index.d.ts +24 -12
  32. package/dist/reviews/index.js +507 -173
  33. package/dist/reviews/index.js.map +1 -1
  34. package/dist/reviews/index.mjs +462 -129
  35. package/dist/reviews/index.mjs.map +1 -1
  36. package/dist/showcase/index.d.mts +2 -2
  37. package/dist/showcase/index.d.ts +2 -2
  38. package/dist/showcase/index.js.map +1 -1
  39. package/dist/showcase/index.mjs.map +1 -1
  40. package/dist/transport-B31G0Cib.d.ts +128 -0
  41. package/dist/transport-Bzb-bcB2.d.mts +128 -0
  42. package/dist/{types-GL_hQ0TN.d.mts → types-BcmagF6K.d.mts} +1 -1
  43. package/dist/{types-GL_hQ0TN.d.ts → types-BcmagF6K.d.ts} +1 -1
  44. package/dist/{types-BhpXJGlg.d.ts → types-Buj9Lw9t.d.ts} +15 -3
  45. package/dist/{types-CMuOexw0.d.mts → types-CKFhyrMu.d.mts} +1 -1
  46. package/dist/{types-CMuOexw0.d.ts → types-CKFhyrMu.d.ts} +1 -1
  47. package/dist/{types-A6pTxIZV.d.mts → types-CNUqMK0D.d.mts} +15 -3
  48. package/metro/index.d.ts +18 -1
  49. package/metro/index.js +80 -0
  50. package/package.json +9 -1
  51. package/src/WireOnboarding.tsx +4 -0
  52. package/src/analytics/currentSession.ts +1 -1
  53. package/src/cards/CardGridSelectCard.tsx +253 -0
  54. package/src/cards/ChipSelectCard.tsx +1 -1
  55. package/src/cards/SelectionCard.tsx +15 -19
  56. package/src/cards/index.ts +13 -1
  57. package/src/cards/optionSchema.ts +65 -0
  58. package/src/coachmarks/types.ts +1 -1
  59. package/src/components/CardHandoff.tsx +39 -7
  60. package/src/components/DoneCheck.tsx +94 -0
  61. package/src/components/ModalPaneHost.tsx +141 -0
  62. package/src/components/StepDots.tsx +112 -0
  63. package/src/device/appVersion.ts +29 -8
  64. package/src/device/deviceModel.ts +21 -8
  65. package/src/features/cache.ts +1 -1
  66. package/src/haptics/expo-haptics.d.ts +31 -0
  67. package/src/haptics/haptics.ts +58 -0
  68. package/src/icons/IconRegistry.tsx +53 -0
  69. package/src/icons/WireIcon.tsx +85 -0
  70. package/src/icons/expoIcons.ts +165 -0
  71. package/src/icons/index.ts +18 -0
  72. package/src/icons/vocabulary.ts +154 -0
  73. package/src/index.ts +20 -0
  74. package/src/questionnaire/QuestionnaireGate.tsx +315 -164
  75. package/src/questionnaire/decision.ts +4 -2
  76. package/src/questionnaire/index.ts +3 -2
  77. package/src/questionnaire/steps.ts +261 -0
  78. package/src/questionnaire/types.ts +33 -12
  79. package/src/reviews/ReviewGate.tsx +250 -152
  80. package/src/reviews/index.ts +7 -3
  81. package/src/reviews/transport.ts +119 -3
  82. package/src/reviews/types.ts +13 -0
  83. package/src/session/persistedSession.ts +1 -1
  84. package/src/session-analytics/lifecycle.ts +1 -1
  85. package/src/session-analytics/useSessionStart.ts +2 -2
  86. package/src/showcase/FeatureShowcase.tsx +1 -1
  87. package/src/showcase/showcaseColors.ts +1 -1
  88. package/src/theme/mergeThemeOver.ts +27 -0
  89. package/src/types.ts +8 -0
  90. package/dist/transport-BeO_Brcu.d.mts +0 -40
  91. package/dist/transport-DLpd1v5_.d.ts +0 -40
@@ -3,15 +3,16 @@
3
3
  * (analytics/reviews must never break the app). Mirrors analytics/reportClientEvent: a
4
4
  * thin fetch wrapper, Bearer tenant key, swallow every error.
5
5
  *
6
- * • submitReview → POST {serverUrl}/v1/reviews (the 1-4 feedback body)
7
- * • reportAppEvent POST {serverUrl}/v1/events (generic app.* namespace)
6
+ * • submitReview → POST {serverUrl}/v1/reviews (the review row)
7
+ * • fetchReviewDecision GET {serverUrl}/v1/reviews/decision (best-effort, the AI seam)
8
+ * • reportAppEvent → POST {serverUrl}/v1/events (generic app.* namespace)
8
9
  *
9
10
  * `reportAppEvent` is the strategic extension: it lets a host report arbitrary in-app
10
11
  * events through the SAME transport (stored server-side as event_type='app_event',
11
12
  * question_key=<name>), which is what the backend review-firing rules evaluate on — and
12
13
  * it seeds the broader app-analytics stream. Keep payloads minimal + non-PII.
13
14
  */
14
- import type { ReviewSubmission, ReviewTarget } from "./types";
15
+ import type { ReviewDecisionResponse, ReviewSubmission, ReviewTarget } from "./types";
15
16
 
16
17
  /**
17
18
  * POST a review (the 1-4 feedback path). Fire-and-forget: a missing target, a build error,
@@ -38,6 +39,121 @@ export const submitReview = (
38
39
  }
39
40
  };
40
41
 
42
+ /**
43
+ * Options for the best-effort review decision fetch.
44
+ *
45
+ * ── WHAT THE DEPLOYED SERVER ACTUALLY READS (verified 2026-07-17) ────────────────────────
46
+ *
47
+ * `GET /v1/reviews/decision` declares exactly two query params — `session_id` and
48
+ * `device_key` — plus the `Authorization` header. That is the whole wire. Verified against
49
+ * the deployed OpenAPI schema, not against intent.
50
+ *
51
+ * This type is CLOSED on purpose. Hosts that hand-rolled this fetch invented `user_id` and
52
+ * `session_count` query params believing "the server ignores what it doesn't read, so passing
53
+ * it is always safe". Both are no-ops: the route declares neither. `session_count` is real, but
54
+ * only on the questionnaire POST body — which is precisely how it copy-pasted its way into a
55
+ * reviews call site and sat there doing nothing. They cost a wire lie — a call site that reads as though
56
+ * identity and a session counter reach the firing brain when neither does. So they are not
57
+ * offered here. Pass identity as `deviceKey`; the session count the server reasons about is the
58
+ * one IT derives from the event stream keyed by `deviceKey`, not one the client asserts.
59
+ *
60
+ * The client-side session floor is a LOCAL rule, not a wire param: use `ReviewConfig.minSessions`
61
+ * (evaluated by `useReviewGate` against the kit's own `wire_review_<id>_sessions` counter).
62
+ */
63
+ export interface FetchReviewDecisionOptions {
64
+ /**
65
+ * The onboarding session id, when there IS one. OPTIONAL on purpose: the review gate lives on
66
+ * the home feed, where a user legitimately has no onboarding session. `deviceKey` is the real
67
+ * identity for this call. (The server relaxed `session_id` to optional on 2026-07-16 and the
68
+ * relaxation IS deployed — the route no longer 422s without it. Permissive wire: do NOT make
69
+ * this required here.)
70
+ */
71
+ sessionId?: string;
72
+ /**
73
+ * A stable, non-PII device id — THE identity for this call. The decision endpoint reads it for
74
+ * cooldown + min-sessions, and it is the key the server groups a device's events under. A host
75
+ * whose own identity is a user id passes that id here rather than reaching for a `user_id`
76
+ * param the route does not declare.
77
+ */
78
+ deviceKey?: string;
79
+ }
80
+
81
+ /**
82
+ * Best-effort fetch of the SERVER's review firing decision. The mirror of
83
+ * `fetchQuestionnaireDecision`, and the primitive whose absence caused a live incident.
84
+ *
85
+ * ── WHY THIS LIVES IN THE KIT (Malik, 2026-07-16) ────────────────────────────────────────
86
+ *
87
+ * The questionnaire module has always had its decision fetch; reviews never did. So a host
88
+ * hand-rolled one, and got it subtly wrong: its helper collapsed an explicit `{fire:false}`
89
+ * into `undefined`. `decideReview` is `(local, decision) => decision ?? local`, so `undefined`
90
+ * means "the server has no opinion, use the local rules" — and the local timer fired. The
91
+ * server could therefore only ever turn review prompts ON, never OFF. A real user was asked to
92
+ * rate the app ~3 minutes into their FIRST session, having seen nothing yet, and left 1 star.
93
+ *
94
+ * The bug was not that the host was careless. It was that the kit made every host invent this.
95
+ * So the primitive moves here and hosts keep a thin call site.
96
+ *
97
+ * ── THE CONTRACT THAT MATTERS ────────────────────────────────────────────────────────────
98
+ *
99
+ * • 2xx → the FULL `{fire, reason, arm}`, INCLUDING `fire:false`. Never collapse a false
100
+ * into null. That collapse IS the bug: a false must reach `decideReview` intact so
101
+ * it can override the local rules and keep the gate shut.
102
+ * • else → null, and ONLY then. Null means "the server genuinely has no opinion", which is
103
+ * the one case where falling back to local rules is correct.
104
+ *
105
+ * Never throws: unreachable, non-2xx, bad JSON, or a missing `fetch` all resolve to null. The
106
+ * kit does not call this internally; a host awaits it and passes the result straight to
107
+ * `useReviewGate({ decision })`.
108
+ *
109
+ * ── DON'T RACE THIS AGAINST A LOCAL TIMER ────────────────────────────────────────────────
110
+ *
111
+ * The return type is `ReviewDecisionResponse`, which carries `arm` alongside `{fire, reason}`.
112
+ * Hand the WHOLE object to the gate and echo `arm` into the submission's `meta.firing_arm`;
113
+ * narrowing it to `{fire, reason}` on the way through silently kills per-arm attribution
114
+ * across a reweighting of the experiment.
115
+ *
116
+ * A host that starts its own dwell timer in parallel with this fetch has built a race a slow
117
+ * server loses: the timer fires, the local rules show the prompt, and the `{fire:false}` still
118
+ * in flight arrives too late to stop it. Do not hand-roll that. `useReviewGate` already owns
119
+ * the wait — set `ReviewConfig.timeoutFallbackMs` and the local rules stay parked until either
120
+ * the decision lands or the window expires, whichever comes first.
121
+ *
122
+ * const decision = await fetchReviewDecision(target, { deviceKey });
123
+ * const gate = useReviewGate({
124
+ * config: { id: "home", minSessions: 2, timeoutFallbackMs: 3000 },
125
+ * decision: decision ?? undefined, // pass it whole — keep `arm`
126
+ * storage,
127
+ * });
128
+ */
129
+ export const fetchReviewDecision = async (
130
+ target: ReviewTarget | undefined,
131
+ options: FetchReviewDecisionOptions = {},
132
+ ): Promise<ReviewDecisionResponse | null> => {
133
+ if (!target?.serverUrl) return null;
134
+ try {
135
+ const base = target.serverUrl.replace(/\/$/, "");
136
+ const params = new URLSearchParams();
137
+ if (options.sessionId) params.set("session_id", options.sessionId);
138
+ if (options.deviceKey) params.set("device_key", options.deviceKey);
139
+ const qs = params.toString();
140
+ const url = `${base}/v1/reviews/decision${qs ? `?${qs}` : ""}`;
141
+ const headers: Record<string, string> = {};
142
+ if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
143
+ const res = await fetch(url, { headers });
144
+ if (!res || !res.ok) return null;
145
+ const json = (await res.json()) as ReviewDecisionResponse | null;
146
+ // A body without a boolean `fire` is not a decision. Guard it explicitly rather than
147
+ // letting `{}` through as a truthy object that `decideReview` would treat as a verdict
148
+ // (`{}.fire === undefined` is falsy, so it would silently read as "never fire").
149
+ if (!json || typeof json.fire !== "boolean") return null;
150
+ return json;
151
+ } catch {
152
+ /* unreachable / non-2xx / bad JSON / missing-fetch → the server has no opinion */
153
+ return null;
154
+ }
155
+ };
156
+
41
157
  /** Options for a reported app event. `deviceKey` groups a device's sessions server-side. */
42
158
  export interface ReportAppEventOptions {
43
159
  /** The onboarding/session id to correlate with, when known. */
@@ -19,6 +19,19 @@ export interface ReviewDecision {
19
19
  reason?: string;
20
20
  }
21
21
 
22
+ /**
23
+ * The `GET /v1/reviews/decision` response: `ReviewDecision` plus the additive `arm`.
24
+ *
25
+ * `arm` is the firing experiment's sticky assignment for this device/session. Echo it back in
26
+ * the submission's `meta.firing_arm` and the server keeps it verbatim (`_stamp_firing_arm`:
27
+ * "an arm the kit already echoed is left as-is"); omit it and the server recomputes from the
28
+ * sticky hash. Echoing is what makes per-arm attribution survive a later reweighting of the
29
+ * experiment, so a host running one should pass it through.
30
+ */
31
+ export interface ReviewDecisionResponse extends ReviewDecision {
32
+ arm?: string | null;
33
+ }
34
+
22
35
  /** Where to route a rating: 5 stars → the store, 1-4 → the feedback form. */
23
36
  export type RatingRoute = "store" | "feedback";
24
37
 
@@ -39,7 +39,7 @@ export const DEFAULT_SESSION_TTL_MS = 3_600_000;
39
39
  /** Ceiling on the storage read — a hung adapter degrades to a fresh mint, never a stuck gate. */
40
40
  const READ_TIMEOUT_MS = 1_500;
41
41
 
42
- /** Storage key for an app's cached session, e.g. `wireai:session:myelino`. */
42
+ /** Storage key for an app's cached session, e.g. `wireai:session:acme`. */
43
43
  export const sessionStorageKey = (appId: string): string => `wireai:session:${appId}`;
44
44
 
45
45
  type PersistedSession = { id: string; ts: number };
@@ -44,7 +44,7 @@ import { reportSessionStart, SESSION_STARTED_EVENT } from "./reportSessionStart"
44
44
  * {@link SESSION_STARTED_EVENT}; a trigger keys off this exact string. */
45
45
  export const FIRST_OPEN_EVENT = "app.first_open" as const;
46
46
 
47
- /** Storage key for the once-ever first-open flag, e.g. `wireai:first_open:myelino`. Mirrors the
47
+ /** Storage key for the once-ever first-open flag, e.g. `wireai:first_open:acme`. Mirrors the
48
48
  * `wireai:<concern>:<appId>` namespacing of {@link sessionStorageKey}. */
49
49
  export const firstOpenStorageKey = (appId: string): string => `wireai:first_open:${appId}`;
50
50
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * useSessionStart — the optional convenience hook that fires {@link reportSessionStart} for you.
3
3
  *
4
- * Two firing moments, mirroring the Myelino/Morrow session-counter semantics:
4
+ * Two firing moments, mirroring the reference apps' session-counter semantics:
5
5
  * • ON MOUNT — the app opened (cold start or the provider first rendered).
6
6
  * • ON FOREGROUND after a real background — when AppState returns to `active` having been
7
7
  * backgrounded for at least {@link BACKGROUND_SESSION_MS} (30 min), that's a NEW open, so a
@@ -10,7 +10,7 @@
10
10
  * Each firing mints its OWN per-open `session_id` (held in a ref so a re-render never re-fires),
11
11
  * so grouping over time is by `deviceKey`/`userId`, never by session_id (see reportSessionStart).
12
12
  *
13
- * The hook is OPTIONAL. A host that already owns a session counter (Myelino/Morrow do) can skip
13
+ * The hook is OPTIONAL. A host that already owns a session counter (most hosts do) can skip
14
14
  * the hook and call `reportSessionStart(...)` directly from its own "app opened" path — BOTH
15
15
  * paths are first-class. This hook is the batteries-included option for a host that has none.
16
16
  *
@@ -135,7 +135,7 @@ const _FeatureShowcase: React.FC<FeatureShowcaseProps> = ({
135
135
  // Calm the large image/hero panel. The package's `background.primary` is a
136
136
  // SINGLE key that fills the primary button, the intro highlight AND this hero
137
137
  // panel — painting it `accent` (correct for the button) also flooded the first
138
- // slide with the app color (Malik, Myelino, 0.3.1). The package exposes a
138
+ // slide with the app color (0.3.1). The package exposes a
139
139
  // `background` render prop that REPLACES that panel fill outright, so we keep
140
140
  // the button accent (via `colors`) and paint the panel a calm surface + faint
141
141
  // accent tint here. See showcasePanelBackground.
@@ -63,7 +63,7 @@ export const showcaseColorsFromTheme = (
63
63
  * Opacity of the accent tint layered over the theme surface for the showcase's
64
64
  * image hero panel. Kept in a calm 8–12% band so the panel reads as a subtle
65
65
  * brand wash — a hint of the accent, never the full-bleed flood that `accent`
66
- * as a solid fill produced (the 0.3.1 → 0.3.2 bug Malik saw in Myelino).
66
+ * as a solid fill produced (the 0.3.1 → 0.3.2 bug seen in a host app).
67
67
  */
68
68
  export const SHOWCASE_PANEL_TINT_OPACITY = 0.1;
69
69
 
@@ -0,0 +1,27 @@
1
+ /**
2
+ * mergeThemeOver — merge a partial theme over an ARBITRARY base theme.
3
+ *
4
+ * Sibling of `mergeTheme` (defaultTheme.ts), which merges over the DEFAULT theme only.
5
+ * Gates need the other shape: a `theme` prop merged over whatever the host's active
6
+ * ThemeContext currently is. That helper was copy-pasted verbatim into ReviewGate and
7
+ * QuestionnaireGate; it lives here once instead, so a new theme section can never be
8
+ * added to one gate and forgotten in the other.
9
+ *
10
+ * Two levels deep, matching `mergeTheme`: the theme is intentionally shallow, so each
11
+ * known sub-object is merged and scalars overwrite.
12
+ */
13
+ import type { OnboardingTheme } from "./types";
14
+
15
+ export const mergeThemeOver = (
16
+ base: OnboardingTheme,
17
+ override?: Partial<OnboardingTheme>,
18
+ ): OnboardingTheme => {
19
+ if (!override) return base;
20
+ return {
21
+ colors: { ...base.colors, ...override.colors },
22
+ fonts: { ...base.fonts, ...override.fonts },
23
+ radius: { ...base.radius, ...override.radius },
24
+ spacing: { ...base.spacing, ...override.spacing },
25
+ button: { ...base.button, ...override.button },
26
+ };
27
+ };
package/src/types.ts CHANGED
@@ -108,6 +108,14 @@ export type WireOnboardingProps = {
108
108
  * via `illustration`; the kit slots the matching node (keeping itself dependency-free).
109
109
  */
110
110
  illustrations?: Record<string, import("react").ReactNode>;
111
+ /**
112
+ * App-supplied ICON nodes, keyed by the semantic vocabulary name the AI emits (e.g.
113
+ * `{ instagram: <BrandIg/> }`). Checked FIRST, so a host can override any icon with its own
114
+ * brand mark, supply icons without installing `@expo/vector-icons`, or extend the vocabulary
115
+ * with names of its own. Anything not listed falls back to `@expo/vector-icons` when that
116
+ * optional peer is present, and to no icon at all when it isn't — never a crash.
117
+ */
118
+ icons?: Record<string, import("react").ReactNode>;
111
119
  /** Per-step validators keyed by base-question key, e.g. `{ username: checkUsername }`. */
112
120
  validators?: Record<string, StepValidator>;
113
121
  /** Fired once the flow reaches its terminal StatusCard. */
@@ -1,40 +0,0 @@
1
- import { j as ReviewTarget, i as ReviewSubmission } from './types-A6pTxIZV.mjs';
2
-
3
- /**
4
- * transport.ts — kit → Wire server requests for the review module, all fire-and-forget
5
- * (analytics/reviews must never break the app). Mirrors analytics/reportClientEvent: a
6
- * thin fetch wrapper, Bearer tenant key, swallow every error.
7
- *
8
- * • submitReview → POST {serverUrl}/v1/reviews (the 1-4 feedback body)
9
- * • reportAppEvent → POST {serverUrl}/v1/events (generic app.* namespace)
10
- *
11
- * `reportAppEvent` is the strategic extension: it lets a host report arbitrary in-app
12
- * events through the SAME transport (stored server-side as event_type='app_event',
13
- * question_key=<name>), which is what the backend review-firing rules evaluate on — and
14
- * it seeds the broader app-analytics stream. Keep payloads minimal + non-PII.
15
- */
16
-
17
- /**
18
- * POST a review (the 1-4 feedback path). Fire-and-forget: a missing target, a build error,
19
- * a missing `fetch`, or a network failure is swallowed and the call returns immediately.
20
- */
21
- declare const submitReview: (target: ReviewTarget | undefined, review: ReviewSubmission) => void;
22
- /** Options for a reported app event. `deviceKey` groups a device's sessions server-side. */
23
- interface ReportAppEventOptions {
24
- /** The onboarding/session id to correlate with, when known. */
25
- sessionId?: string;
26
- /** A stable, non-PII device id — the review-decision endpoint reads it for min-sessions. */
27
- deviceKey?: string;
28
- /** Small non-PII extras. */
29
- meta?: Record<string, unknown>;
30
- }
31
- /**
32
- * Report a generic in-app event through the existing events transport. Stored server-side
33
- * as `event_type='app_event'`, `question_key=<name>`. Fire-and-forget. Keep `name` a short
34
- * stable identifier and `meta` small + non-PII.
35
- *
36
- * reportAppEvent(target, "content_share", { sessionId, deviceKey });
37
- */
38
- declare const reportAppEvent: (target: ReviewTarget | undefined, name: string, options?: ReportAppEventOptions) => void;
39
-
40
- export { type ReportAppEventOptions as R, reportAppEvent as r, submitReview as s };
@@ -1,40 +0,0 @@
1
- import { j as ReviewTarget, i as ReviewSubmission } from './types-BhpXJGlg.js';
2
-
3
- /**
4
- * transport.ts — kit → Wire server requests for the review module, all fire-and-forget
5
- * (analytics/reviews must never break the app). Mirrors analytics/reportClientEvent: a
6
- * thin fetch wrapper, Bearer tenant key, swallow every error.
7
- *
8
- * • submitReview → POST {serverUrl}/v1/reviews (the 1-4 feedback body)
9
- * • reportAppEvent → POST {serverUrl}/v1/events (generic app.* namespace)
10
- *
11
- * `reportAppEvent` is the strategic extension: it lets a host report arbitrary in-app
12
- * events through the SAME transport (stored server-side as event_type='app_event',
13
- * question_key=<name>), which is what the backend review-firing rules evaluate on — and
14
- * it seeds the broader app-analytics stream. Keep payloads minimal + non-PII.
15
- */
16
-
17
- /**
18
- * POST a review (the 1-4 feedback path). Fire-and-forget: a missing target, a build error,
19
- * a missing `fetch`, or a network failure is swallowed and the call returns immediately.
20
- */
21
- declare const submitReview: (target: ReviewTarget | undefined, review: ReviewSubmission) => void;
22
- /** Options for a reported app event. `deviceKey` groups a device's sessions server-side. */
23
- interface ReportAppEventOptions {
24
- /** The onboarding/session id to correlate with, when known. */
25
- sessionId?: string;
26
- /** A stable, non-PII device id — the review-decision endpoint reads it for min-sessions. */
27
- deviceKey?: string;
28
- /** Small non-PII extras. */
29
- meta?: Record<string, unknown>;
30
- }
31
- /**
32
- * Report a generic in-app event through the existing events transport. Stored server-side
33
- * as `event_type='app_event'`, `question_key=<name>`. Fire-and-forget. Keep `name` a short
34
- * stable identifier and `meta` small + non-PII.
35
- *
36
- * reportAppEvent(target, "content_share", { sessionId, deviceKey });
37
- */
38
- declare const reportAppEvent: (target: ReviewTarget | undefined, name: string, options?: ReportAppEventOptions) => void;
39
-
40
- export { type ReportAppEventOptions as R, reportAppEvent as r, submitReview as s };