@wireai/activation 0.13.6-next.0 → 0.13.6
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 +1 -1
- package/CHANGELOG.md +87 -3
- package/README.md +32 -34
- package/dist/analytics/index.d.mts +2 -2
- package/dist/analytics/index.d.ts +2 -2
- package/dist/{currentSession-DD6dKB0i.d.ts → currentSession-C5976akx.d.ts} +27 -1
- package/dist/{currentSession-Cs3lweFZ.d.mts → currentSession-DngW-QoD.d.mts} +27 -1
- package/dist/index.d.mts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +11 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +11 -3
- package/dist/index.mjs.map +1 -1
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/src/OnboardingFlow.tsx +19 -2
- package/src/types.ts +27 -2
- package/src/utils/readPlan.ts +18 -5
- package/src/utils/readProgress.ts +5 -0
package/llms.txt
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
- **Render:** drop `<WireOnboarding config={wireConfigFromEnv({ appId })} theme={...} onComplete={persist} fallbackFlow={<YourStaticOnboarding/>} />` into the signup flow.
|
|
15
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
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`.
|
|
17
|
-
- **Capture is deterministic.** `onComplete` returns `
|
|
17
|
+
- **Capture is deterministic.** `onComplete` returns an `OnboardingResult`: `answers` and `raw` always, plus (since 0.13.6) `plan` when the backend sent an onboarding plan and `variant` when the tenant runs an experiment and the backend assigned an arm. No second LLM call. Persist all answers through the app's normal profile-update path. Those two extra keys are set ONLY when the backend actually sent them, so a run without them returns exactly the object earlier versions returned, and the kit interprets neither: validate `plan` before applying it.
|
|
18
18
|
- **It can never break onboarding.** Pass `fallbackFlow` (your existing static onboarding) so a backend error/timeout degrades instead of dead-ending.
|
|
19
19
|
|
|
20
20
|
## Helpers (the reusable substrate)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wireai/activation",
|
|
3
|
-
"version": "0.13.6
|
|
3
|
+
"version": "0.13.6",
|
|
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/OnboardingFlow.tsx
CHANGED
|
@@ -177,6 +177,12 @@ export const OnboardingFlow: React.FC<OnboardingFlowProps> = ({
|
|
|
177
177
|
// Last 0-based screen index we showed the user (matches the server's `screen_shown`
|
|
178
178
|
// index). Used as the `dropped` event's screen_index. -1 = nothing shown yet.
|
|
179
179
|
const lastScreenIndexRef = useRef(-1);
|
|
180
|
+
// The backend's experiment arm for this session, LATCHED at first sight and never cleared.
|
|
181
|
+
// The server's assignment is sticky per session but it only rides the render envelope, so a
|
|
182
|
+
// later card that omits `progress.variant` must not un-assign what an earlier card declared.
|
|
183
|
+
// `undefined` = no arm seen, which is every turn of every tenant running no experiment.
|
|
184
|
+
// The kit stores the string and nothing else: it is not parsed, branded, defaulted or logged.
|
|
185
|
+
const variantRef = useRef<string | undefined>(undefined);
|
|
180
186
|
// Trailing-edge debounce timer for prefetch-on-select (see previewOnSelect below).
|
|
181
187
|
const previewTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
182
188
|
|
|
@@ -269,15 +275,22 @@ export const OnboardingFlow: React.FC<OnboardingFlowProps> = ({
|
|
|
269
275
|
// A new card arrived → clear any prior inline validation error + emit a turn event.
|
|
270
276
|
useEffect(() => {
|
|
271
277
|
setValidationError(undefined);
|
|
278
|
+
// Latch the arm BEFORE the turn event fires, so the card that first declares one already
|
|
279
|
+
// carries it. An empty string is not an assignment, so it is ignored like an absent key.
|
|
280
|
+
if (variantRef.current === undefined && progress.variant) variantRef.current = progress.variant;
|
|
272
281
|
if (!lastCard?.id) return;
|
|
273
282
|
const step = progress.step ?? renderedCount;
|
|
274
283
|
// Remember the latest screen we showed (0-based) for a possible `dropped` event.
|
|
275
284
|
lastScreenIndexRef.current = Math.max(0, step - 1);
|
|
276
|
-
|
|
285
|
+
const turn: Extract<OnboardingEvent, { type: "turn" }> = {
|
|
277
286
|
type: "turn",
|
|
278
287
|
step,
|
|
279
288
|
component: lastCard.response?.component,
|
|
280
|
-
}
|
|
289
|
+
};
|
|
290
|
+
// Set the key ONLY when an arm was seen, for the same reason `handleFinish` does it on the
|
|
291
|
+
// result: a host reading `Object.keys(event)` sees no change when no experiment is running.
|
|
292
|
+
if (variantRef.current !== undefined) turn.variant = variantRef.current;
|
|
293
|
+
onEventRef.current?.(turn);
|
|
281
294
|
// Prefetch: an InterstitialCard's answer is always "continue", so we can warm the next
|
|
282
295
|
// turn's cache during the momentum window — by the time the user taps Continue the following
|
|
283
296
|
// card is already generated. The preview MUST use the exact text the real Continue tap sends,
|
|
@@ -373,6 +386,10 @@ export const OnboardingFlow: React.FC<OnboardingFlowProps> = ({
|
|
|
373
386
|
// object — a host that inspects `Object.keys(result)` or `"plan" in result` sees no change.
|
|
374
387
|
const plan = readPlan(messages);
|
|
375
388
|
if (plan !== undefined) result.plan = plan;
|
|
389
|
+
// The experiment arm, latched off the render envelopes during the flow. Set under exactly the
|
|
390
|
+
// same rule as the plan: no arm seen → no key, so the result stays byte-identical for the
|
|
391
|
+
// tenants (most of them) that run no experiment.
|
|
392
|
+
if (variantRef.current !== undefined) result.variant = variantRef.current;
|
|
376
393
|
onCompleteRef.current(result);
|
|
377
394
|
}, [messages]);
|
|
378
395
|
|
package/src/types.ts
CHANGED
|
@@ -66,6 +66,16 @@ export type OnboardingResult = {
|
|
|
66
66
|
* the host's decision, not the kit's.
|
|
67
67
|
*/
|
|
68
68
|
plan?: unknown;
|
|
69
|
+
/**
|
|
70
|
+
* The experiment ARM the backend assigned this session, when it is running one. Absent when
|
|
71
|
+
* the tenant runs no experiment, which is the common case, and absent is NOT an error.
|
|
72
|
+
* The kit does NOT interpret it, does not log it, does not brand it and never attaches it
|
|
73
|
+
* to an analytics event. What an arm key MEANS is the host's decision.
|
|
74
|
+
*
|
|
75
|
+
* Like {@link plan}, the KEY is set only when an arm was actually seen, so a run with no
|
|
76
|
+
* experiment leaves the result byte-identical to what every host already reads.
|
|
77
|
+
*/
|
|
78
|
+
variant?: string;
|
|
69
79
|
};
|
|
70
80
|
|
|
71
81
|
/**
|
|
@@ -78,7 +88,11 @@ export type OnboardingResult = {
|
|
|
78
88
|
* - `resumed`: a persisted session was restored after an app kill (fires INSTEAD of
|
|
79
89
|
* `started`, so host funnels don't double-count the same session). Also carries
|
|
80
90
|
* `contextId`. Requires the `storage` prop.
|
|
81
|
-
* - `turn`: a new assistant card arrived (`step` = 1-based index of cards seen).
|
|
91
|
+
* - `turn`: a new assistant card arrived (`step` = 1-based index of cards seen). Carries
|
|
92
|
+
* `variant`, the backend's experiment arm for this session, from the first card that
|
|
93
|
+
* declared one onwards — this is how the arm is available DURING the flow rather than
|
|
94
|
+
* only at completion. The key is absent whenever no arm has been seen, which is every
|
|
95
|
+
* turn of every tenant running no experiment.
|
|
82
96
|
* - `error`: the backend errored or the first-card watchdog timed out.
|
|
83
97
|
* - `retry`: a transient failure is being auto-retried (`attempt` = 1-based).
|
|
84
98
|
* - `fallback`: retries are exhausted; the kit degraded to the static `fallbackFlow`
|
|
@@ -92,7 +106,7 @@ export type OnboardingResult = {
|
|
|
92
106
|
export type OnboardingEvent =
|
|
93
107
|
| { type: "started"; contextId: string }
|
|
94
108
|
| { type: "resumed"; contextId: string }
|
|
95
|
-
| { type: "turn"; step: number; component?: string }
|
|
109
|
+
| { type: "turn"; step: number; component?: string; variant?: string }
|
|
96
110
|
| { type: "error"; reason: "backend" | "timeout" }
|
|
97
111
|
| { type: "retry"; reason: "backend" | "timeout"; attempt: number }
|
|
98
112
|
| { type: "fallback"; reason: "backend" | "timeout" }
|
|
@@ -363,4 +377,15 @@ export type OnboardingProgress = {
|
|
|
363
377
|
slot_id?: string;
|
|
364
378
|
/** Whether the CURRENT screen may be skipped (backend-marked; default false → no Skip shown). */
|
|
365
379
|
skippable?: boolean;
|
|
380
|
+
/**
|
|
381
|
+
* The EXPERIMENT ARM the backend assigned this session, when the tenant is running one. Rides the
|
|
382
|
+
* render envelope as a sibling of {@link step}/{@link total}/{@link key}/{@link slot_id}, and is
|
|
383
|
+
* OMITTED entirely for a tenant running no experiment — which is the common case, and is not an
|
|
384
|
+
* error. The assignment is sticky for the session, so a later card that omits it does not
|
|
385
|
+
* un-assign it.
|
|
386
|
+
*
|
|
387
|
+
* The kit does NOT interpret this. It is whitelisted, latched and handed to the host exactly as
|
|
388
|
+
* received; what an arm key MEANS is the host's decision. See {@link OnboardingResult.variant}.
|
|
389
|
+
*/
|
|
390
|
+
variant?: string;
|
|
366
391
|
};
|
package/src/utils/readPlan.ts
CHANGED
|
@@ -24,9 +24,16 @@
|
|
|
24
24
|
import type { Message } from "wireai-rn";
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* The read-back member the SDK surfaces for every A2A DataPart past the first,
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* The read-back member the SDK surfaces for every A2A DataPart past the first, uninterpreted.
|
|
28
|
+
*
|
|
29
|
+
* NOT in wire order, and nothing here may assume it is. The SDK FLATTENS a task's parts across
|
|
30
|
+
* agent messages LATEST-FIRST (and then artifacts latest-first) before dropping the first one, so
|
|
31
|
+
* this array is a cross-message collection, not one turn's parts in the order they arrived. On a
|
|
32
|
+
* task carrying history, a previous turn's component envelope lands here beside this turn's plan.
|
|
33
|
+
* `readPlan` is safe from that because it matches the `kind` MARKER below, never a position.
|
|
34
|
+
*
|
|
35
|
+
* Declared HERE, structurally, because the installed `wireai-rn` peer (0.2.4) does not declare it
|
|
36
|
+
* on `Message` yet.
|
|
30
37
|
*
|
|
31
38
|
* TODO(re-pin): delete this local type and read the member straight off `Message` once a
|
|
32
39
|
* `wireai-rn` that declares it is published and the peer range is re-pinned. Until then the two
|
|
@@ -58,8 +65,14 @@ const planFromPart = (part: unknown): unknown => {
|
|
|
58
65
|
*
|
|
59
66
|
* ORDERING, stated so it is not undefined behavior: the thread is scanned NEWEST TURN FIRST, so a
|
|
60
67
|
* thread carrying two plans resolves to the LAST one — the same turn `handleFinish` completes on.
|
|
61
|
-
*
|
|
62
|
-
*
|
|
68
|
+
*
|
|
69
|
+
* Inside one message's `dataParts` the first WELL-FORMED plan part wins, and that is a scan order,
|
|
70
|
+
* NOT a wire-order guarantee. Wire order does not hold even within a single turn: `dataParts` is
|
|
71
|
+
* the SDK's flattened, cross-agent-message collection (latest-first, see `WithDataParts` above), so
|
|
72
|
+
* on a task carrying history a previous turn's parts sit in the same array. This is exactly why the
|
|
73
|
+
* match is on the `onboarding_plan` MARKER and never on a position. A part that is not a
|
|
74
|
+
* well-formed plan is skipped rather than treated as a terminator, so neither a stale envelope nor
|
|
75
|
+
* a broken payload can shadow a good plan.
|
|
63
76
|
*/
|
|
64
77
|
export const readPlan = (messages: readonly Message[]): unknown => {
|
|
65
78
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -18,6 +18,8 @@ type PartialProgress = {
|
|
|
18
18
|
/** The stable per-slot identity, when the backend sends one. See `OnboardingProgress.slot_id`. */
|
|
19
19
|
slot_id?: string;
|
|
20
20
|
skippable?: boolean;
|
|
21
|
+
/** The experiment arm assigned to this session, when the tenant runs one. See `OnboardingProgress.variant`. */
|
|
22
|
+
variant?: string;
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
export const readProgress = (response?: WireAIResponse): PartialProgress => {
|
|
@@ -33,5 +35,8 @@ export const readProgress = (response?: WireAIResponse): PartialProgress => {
|
|
|
33
35
|
// Whitelisted the same way as every other field: an old backend simply omits it.
|
|
34
36
|
slot_id: typeof p.slot_id === "string" ? p.slot_id : undefined,
|
|
35
37
|
skippable: typeof p.skippable === "boolean" ? p.skippable : undefined,
|
|
38
|
+
// Whitelisted like the rest: a non-string (or absent) arm key reads as "no experiment", never
|
|
39
|
+
// as an error. The kit does not interpret the value — see `OnboardingProgress.variant`.
|
|
40
|
+
variant: typeof p.variant === "string" ? p.variant : undefined,
|
|
36
41
|
};
|
|
37
42
|
};
|