@wireai/activation 0.13.5 → 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 CHANGED
@@ -60,7 +60,7 @@ subpaths are optional secondary feature modules; import one only if you use it.
60
60
  | Prop | Type | Required | Notes |
61
61
  |---|---|---|---|
62
62
  | `config` | `WireOnboardingConfig` | yes | A2A transport + tenant. See the `config` fields table below. |
63
- | `onComplete` | `(result: { answers, raw }) => void` | yes | Terminal recap CTA tapped. Persist `answers` via your profile-update path, then navigate on. |
63
+ | `onComplete` | `(result: OnboardingResult) => void` | yes | Terminal recap CTA tapped. Persist `answers` via your profile-update path, then navigate on. `result` always has `answers` + `raw`; since 0.13.6 it also carries `plan` (the backend's onboarding plan, AI path only) and `variant` (the assigned experiment arm) when the backend sent them. Both keys are ABSENT otherwise, so an integration written before 0.13.6 sees no change. The kit does not interpret either one: validate `plan` before applying it. |
64
64
  | `theme` | `Partial<OnboardingTheme>` | no | Colors + font family names + radius/spacing, deep-merged over a neutral default. For dark/light, pass a different theme per app theme-state. |
65
65
  | `components` | `WireAIComponent[]` | no | Override registered cards (default `onboardingComponents`). |
66
66
  | `illustrations` | `Record<string, ReactNode>` | no | Host artwork for `InterstitialCard`, keyed by name. `{ ...defaultIllustrations, ...myArt }`. |
package/CHANGELOG.md CHANGED
@@ -3,6 +3,81 @@
3
3
  All notable changes to `@wireai/activation` (formerly `wireai-onboarding`).
4
4
  Historical entries below the rename keep the old package name on purpose.
5
5
 
6
+ ## [0.13.6] - 2026-08-09
7
+
8
+ Two things the backend already decides, handed to the host instead of guessed at. Both ride the
9
+ same boundary: the kit reads a payload the server sent, carries it out through `onComplete`, and
10
+ stops. It does not interpret either one, does not validate their contents, does not log them and
11
+ never attaches them to an analytics event.
12
+
13
+ The `plan` half of this release was published as `0.13.6-next.0` on 2026-08-08 with no changelog
14
+ entry. This entry is that debt paid, plus the `variant` half, released together as 0.13.6.
15
+
16
+ ### Added
17
+
18
+ - **`OnboardingResult.plan`, the backend's onboarding plan reaching the host uninterpreted.**
19
+ On the AI path the server appends a SECOND A2A DataPart to the turn it finishes on,
20
+ `{ kind: "onboarding_plan", plan: {...} }`, alongside the component envelope the renderer already
21
+ consumes. A new pure reader, `src/utils/readPlan.ts`, lifts it off the thread and `handleFinish`
22
+ puts it on the result. The static (non-AI) flow carries no plan and is unchanged.
23
+
24
+ The one thing the reader is allowed to reject is a structural fact: a plan is an object. A scalar,
25
+ an array, a misspelled marker or a missing payload yields `undefined` and the host falls back to
26
+ exactly its pre-plan behaviour. No field is required and no unknown field is stripped, because a
27
+ kit-side schema would make the kit the thing that rejects a plan the host could have used, and the
28
+ server's lenient path adds markers the kit must neither read nor strip. **The host must validate
29
+ the payload before applying it.**
30
+
31
+ **It finds the plan by MARKER, never by position**, and that is the load-bearing part of the read.
32
+ The kit walks the thread newest turn first and, inside a turn, returns the first part whose `kind`
33
+ is `onboarding_plan` and whose payload is an object. Anything else is skipped rather than treated
34
+ as a terminator, so a thread carrying two plans resolves to the last one, the turn the flow
35
+ completes on, and a malformed part can never shadow a good one. Position would be the wrong tool
36
+ here: the SDK collects a task's parts across agent messages LATEST-FIRST, so on a task that
37
+ carries history a previous turn's envelope can sit in the same array as this turn's plan. The
38
+ marker makes that harmless, and it is also why nothing in this pipe promises wire order.
39
+
40
+ It needs an SDK that surfaces the DataParts PAST the first one. The first DataPart is the
41
+ component envelope the renderer already consumes, and it is the one the SDK turns into the string
42
+ it returns for the turn, so the plan is one of the parts left behind after it.
43
+ `wireai-rn@0.2.5-next.1` hands those over on `Message.dataParts`, and a turn that carried nothing
44
+ but the envelope leaves the member ABSENT rather than an empty array (checked against the
45
+ published tarball, not against a paraphrase of it). The peer range here is unchanged at `>=0.2.3`,
46
+ so this degrades rather than breaks: an older SDK, `0.2.4` included, sets no `dataParts`, the kit
47
+ reads no plan, and the host lands on exactly the path a tenant that sends none already takes.
48
+
49
+ - **`OnboardingResult.variant`, the experiment arm the backend assigned, surfaced to the host.**
50
+ The server rides the arm key on the render envelope at `props.progress.variant`, a sibling of
51
+ `step` / `total` / `key` / `slot_id`. The kit had no way to hand it over, so a host that wanted to
52
+ branch on the arm had to INFER it from the shape of the flow (counting answers against a
53
+ hard-coded maximum), an inference that goes silently wrong the day the server changes a flow
54
+ length.
55
+
56
+ `readProgress` whitelists `variant` exactly the way it already whitelists `slot_id`: a non-string
57
+ or absent key reads as "no experiment", never as an error. `OnboardingFlow` latches the arm at
58
+ first sight into a ref that is never cleared, because the assignment is sticky per session but
59
+ only rides the render envelope, and a later card that omits it must not un-assign it.
60
+
61
+ It is also available DURING the flow: the `turn` event carries `variant` from the first card that
62
+ declares one onwards, reusing an event hosts already subscribe to instead of adding a prop for a
63
+ scalar. What an arm key MEANS is the host's decision; the kit does not map it to a name, brand it,
64
+ default it or log it. **It is deliberately never attached to an analytics event either**, on
65
+ either surface, so a host that wants arm-segmented funnels has to put the arm on its own events.
66
+
67
+ ### Unchanged
68
+
69
+ - **Both keys are byte-identical no-ops when the backend sends nothing.** `plan` and `variant` are
70
+ each set on the result ONLY when one was actually seen, so a run without them leaves
71
+ `Object.keys(result)` as `["answers", "raw"]` and `"plan" in result` / `"variant" in result`
72
+ false. Setting a key to `undefined` would pass an `=== undefined` check while breaking every host
73
+ deep-equality assertion, so the canaries assert the KEY SET, not the value. The same rule governs
74
+ the `turn` event.
75
+ - **Neither read can throw.** Both sit on the single code path to `onComplete`, where a throw would
76
+ cost the user the completion of an onboarding they already finished. Every read is runtime-guarded
77
+ and `readPlan` declares its own structural type rather than casting: an `as` cast silences the
78
+ compiler in both directions, including the day the published SDK type disagrees.
79
+ - No new runtime dependency, no export renamed or removed, no change to the exports map.
80
+
6
81
  ## [0.13.5] - 2026-08-03
7
82
 
8
83
  Two halves of one idea: stop finding out about a broken events integration from a funnel report.
@@ -866,7 +941,11 @@ true, and pins the wire with tests.
866
941
  - Version reset to `0.1.0` to mark the start of the new package line. The GitHub repository
867
942
  stays `chohra-med/wireai-onboarding`.
868
943
 
869
- ## [Unreleased]
944
+ ## [0.9.0] — 2026-07-17
945
+
946
+ *This section carried the title `[Unreleased]` until 2026-08-09, long after it shipped. Everything
947
+ below went out in 0.9.0; the three analytics/device items it also held belong to 0.8.0 and have been
948
+ moved to their own section under this one.*
870
949
 
871
950
  ### Added: `fetchReviewDecision` — the kit now owns the review firing decision (live user harm)
872
951
 
@@ -1025,6 +1104,11 @@ All four from Malik's on-device test, 2026-07-16. The **review** gate's chrome i
1025
1104
  button. The 5-star → native store review route (`routeRating`) is untouched.
1026
1105
  - `theme/mergeThemeOver` extracts the helper both gates had copy-pasted.
1027
1106
 
1107
+ ## [0.8.0] — 2026-07-15
1108
+
1109
+ *Also mis-filed under `[Unreleased]` until 2026-08-09. These three items shipped in 0.8.0, one
1110
+ release before everything in the section above.*
1111
+
1028
1112
  ### Added: "device" is now fully automatic — auto-minted, persisted per-install `device_key`
1029
1113
 
1030
1114
  - The analytics façade (`createAnalytics`) now auto-mints a stable, NON-PII per-install `device_key`
@@ -1058,7 +1142,7 @@ All four from Malik's on-device test, 2026-07-16. The **review** gate's chrome i
1058
1142
  - Backward compatible: no existing export renamed or removed; zero new dependencies. The one
1059
1143
  behavior change is intentional — events now carry an auto `device_key` when the host omits one.
1060
1144
 
1061
- ## [0.7.0] — Unreleased
1145
+ ## [0.7.0] — 2026-07-15
1062
1146
 
1063
1147
  ### Added: `WireUserContext` — one rich user-context object → every event's `user_context`
1064
1148
 
@@ -1439,7 +1523,7 @@ All four from Malik's on-device test, 2026-07-16. The **review** gate's chrome i
1439
1523
  `showcaseColorsFromTheme(theme, accent)` (new `src/showcase/showcaseColors.ts`) with a
1440
1524
  regression test asserting the primary button is the accent and never the background.
1441
1525
 
1442
- ## [0.3.0] — Unreleased
1526
+ ## [0.3.0] — 2026-07-12
1443
1527
 
1444
1528
  > Pending release. `0.2.1` is already published on npm (coachmarks + showcase, no
1445
1529
  > identity API), so this identity surface — alongside the unpublished `/reviews`
package/README.md CHANGED
@@ -231,7 +231,7 @@ import { WireOnboarding } from "@wireai/activation";
231
231
  | Prop | Type | Notes |
232
232
  |---|---|---|
233
233
  | `config` | `{ apiKey, serverUrl, appId, metadata?, appVersion? }` | A2A transport; the key resolves the tenant server-side. `appVersion` is host-injected (e.g. `Constants.expoConfig?.version`) and forwarded for analytics segmentation. **Required.** |
234
- | `onComplete` | `(result: { answers, raw }) => void` | Fires when the user taps the terminal recap's CTA. **Required.** |
234
+ | `onComplete` | `(result: OnboardingResult) => void` | Fires when the user taps the terminal recap's CTA. `result` always carries `answers` and `raw`. Since 0.13.6 it may also carry `plan` (the backend's onboarding plan, AI path only) and `variant` (the experiment arm the backend assigned, when the tenant runs one). Each of those two keys is set ONLY when the backend actually sent one, so a run without them returns exactly the object earlier versions returned: `"plan" in result` and `"variant" in result` both stay false. The kit does not interpret either: **validate `plan` before you apply it.** **Required.** |
235
235
  | `theme` | `Partial<OnboardingTheme>` | Brand colors/fonts/radius/spacing, deep-merged over a neutral default. |
236
236
  | `illustrations` | `Record<string, ReactNode>` | App artwork for `InterstitialCard`, keyed by name. |
237
237
  | `icons` | `Record<string, ReactNode>` | Your own icon nodes, keyed by the semantic name the AI emits (`{ instagram: <BrandIg/> }`). Checked first, so use it to put your brand mark on a choice card, to add names the vocabulary does not carry, or to get icons at all without installing `@expo/vector-icons`. Anything you leave out falls back to that optional peer, then to no icon. Never a crash. See [Icons](#icons). |
@@ -241,7 +241,7 @@ import { WireOnboarding } from "@wireai/activation";
241
241
  | `onError` | `(err) => void` | Backend error or first-card timeout, fired **after retries are exhausted**, not on the first failure. When `fallbackFlow` is not supplied, the host owns recovery (e.g. route to a static flow). With neither, the kit shows an inline retry. `fallbackFlow` takes precedence over this. |
242
242
  | `fallbackFlow` | `ReactNode` | Your predefined STATIC onboarding, rendered in place once the AI flow fails and retries are exhausted. **Takes precedence over `onError`.** This is the "it can never break your onboarding" guarantee: pass the same flow you shipped before adding Wire AI and the user always keeps moving. |
243
243
  | `maxRetries` | `number` | Consecutive failures to auto-retry before degrading to `fallbackFlow`/`onError`. Default `1` (one silent retry, then degrade). `0` degrades on the first failure. Budget it deliberately: each retry restarts the flow, so with `startTimeoutMs` at its `15000` default the default `maxRetries` puts 30s of loader in front of a user before they see the fallback. A retry also sends a skip sentinel, so the user loses one question and a retry is never recorded as their first answer. |
244
- | `onEvent` | `(e: OnboardingEvent) => void` | `started` / `turn` / `error` recover per-turn analytics since the kit owns the loop. |
244
+ | `onEvent` | `(e: OnboardingEvent) => void` | `started` / `resumed` / `turn` / `error` / `retry` / `fallback` / `permission`, the full union in `OnboardingEvent`. Recover per-turn analytics since the kit owns the loop. See the note under the helpers table. |
245
245
  | `copy` | `Partial<OnboardingCopy>` | Localize the kit's built-in English loader/completion strings. |
246
246
  | `approxScreens` | `number` | Approx total screens — paces the bar as `step/total` (capped, never shown). A backend `progress.total` (the screen budget, sent every turn incl. init) wins, so usually unnecessary. |
247
247
  | `components` | `WireAIComponent[]` | Override the registered cards (defaults to `onboardingComponents`). |
@@ -271,7 +271,7 @@ You rarely hand-roll config, gating, analytics, or attribution — the kit ships
271
271
  | `themeFromBrand({ primary })` · `defaultIllustrations` | One-color theme; dependency-free fallback artwork. |
272
272
  | `motionSpec` | The motion constants behind every kit animation (durations, springs, the design ease). Read-only for hosts. `setMotionDurationScale` / `getMotionDurationScale` are QA tools for the playground's animation-speed knob: never call them in production code. |
273
273
 
274
- > **`onEvent`** fires `started` / `resumed` / `turn` / `error` / `retry` / `fallback` (the kit owns the loop, so this is how you recover per-turn analytics). `resumed` fires INSTEAD of `started` when a persisted session was restored (see the `storage` prop) — don't count both as flow starts. Completion is signalled via `onComplete`, not `onEvent` — log `WIRE_ONBOARDING_EVENTS.completed` there. `started` and `resumed` also carry a `contextId` (the A2A session id): capture it if you might bind a `userId` after the flow finishes (see [User identity](#user-identity)).
274
+ > **`onEvent`** fires `started` / `resumed` / `turn` / `error` / `retry` / `fallback` / `permission` (the kit owns the loop, so this is how you recover per-turn analytics). `resumed` fires INSTEAD of `started` when a persisted session was restored (see the `storage` prop) — don't count both as flow starts. Since 0.13.6 the `turn` event also carries `variant`, the experiment arm the backend assigned, from the first card that declares one onwards; that is how you read the arm DURING the flow rather than waiting for `onComplete`. The key is absent when no experiment is running, and the kit never attaches the arm to an analytics event, so arm-segmented funnels are the host's job. Completion is signalled via `onComplete`, not `onEvent` — log `WIRE_ONBOARDING_EVENTS.completed` there. `started` and `resumed` also carry a `contextId` (the A2A session id): capture it if you might bind a `userId` after the flow finishes (see [User identity](#user-identity)).
275
275
 
276
276
  **Integrating with an AI agent?** See [`INTEGRATION_PROMPT.md`](./INTEGRATION_PROMPT.md) (copy-paste prompt for Claude Code) and [`llms.txt`](./llms.txt). Fastest of all: the `wire-rn-integration` Claude skill.
277
277
 
@@ -1310,49 +1310,47 @@ then *improve* Wire AI, not just eyeball it.
1310
1310
 
1311
1311
  ### MCP server (`mcp/`)
1312
1312
 
1313
- A local Model Context Protocol server (`@wireai/mcp`, stdio, TypeScript). It is a
1314
- repo-local dev tool **not** published to npm (excluded from the package `files`
1315
- allowlist). Build it in place:
1313
+ A Model Context Protocol server (`@wireai/mcp`, TypeScript) that exposes the whole
1314
+ onboarding control plane as agent tools. It runs two ways, same tools either way:
1316
1315
 
1317
- ```bash
1318
- cd mcp && npm install && npm run build
1319
- ```
1320
-
1321
- Then add it to your agent client's MCP config:
1316
+ 1. **Hosted, and this is the default.** `https://wireai-mcp.fly.dev/mcp` over Streamable
1317
+ HTTP. You authenticate per request with an `Authorization: Bearer` header, so there is
1318
+ no build step, no login step, and nothing on disk.
1319
+ 2. **Local stdio.** `cd mcp && npm install && npm run build`, then point your client at
1320
+ `mcp/dist/index.js`. Auth is a one-time `node dist/index.js login`, or raw keys via
1321
+ `WIREAI_SERVER_URL` / `WIREAI_API_KEY` / `WIREAI_ADMIN_KEY`. This package is repo-local
1322
+ and **not** published to npm (it is excluded from the kit's `files` allowlist).
1322
1323
 
1323
1324
  ```json
1324
1325
  {
1325
1326
  "mcpServers": {
1326
1327
  "wireai": {
1327
- "command": "node",
1328
- "args": ["<abs-path>/@wireai/activation/mcp/dist/index.js"],
1329
- "env": {
1330
- "WIREAI_SERVER_URL": "https://<your-backend>.fly.dev",
1331
- "WIREAI_API_KEY": "wai_<tenant-key>",
1332
- "WIREAI_ADMIN_KEY": "wai_admin_<operator-key>"
1333
- }
1328
+ "type": "http",
1329
+ "url": "https://wireai-mcp.fly.dev/mcp",
1330
+ "headers": { "Authorization": "Bearer <your-token>" }
1334
1331
  }
1335
1332
  }
1336
1333
  }
1337
1334
  ```
1338
1335
 
1339
- Tools (P1 is read-biased; the only writes are the existing register/update):
1336
+ **Three credential classes, and you probably want the third.** A tenant `wai_` key reads
1337
+ your own app's funnel and files learning reports. The operator admin key reaches the
1338
+ cross-tenant management surface. A **client console token** (minted from a Wire console
1339
+ session) drives your own apps over the `/v1/me/*` console routes, which means a customer
1340
+ runs the full management surface from an agent *without* holding the platform admin key.
1340
1341
 
1341
- | Tool | Auth key | Does |
1342
- |---|---|---|
1343
- | `wireai_get_integration_guide` | none | the current integration recipe |
1344
- | `wireai_list_components` | none | the card vocabulary |
1345
- | `wireai_get_insights` | tenant `WIREAI_API_KEY` | **read** the funnel brief (`/v1/insights.md`), app resolved from the key; a valid "no data yet" brief when empty |
1346
- | `wireai_get_onboarding_config` | admin `WIREAI_ADMIN_KEY` | **read** the live generation config (context, first question, allowed components) |
1347
- | `wireai_register_onboarding_app` | admin `WIREAI_ADMIN_KEY` | create an app + get its key |
1348
- | `wireai_list_onboarding_apps` | admin `WIREAI_ADMIN_KEY` | list apps |
1349
- | `wireai_get_onboarding_app` | admin `WIREAI_ADMIN_KEY` | one app's raw record |
1350
- | `wireai_update_onboarding_app` | admin `WIREAI_ADMIN_KEY` | patch config, no release |
1351
- | `wireai_get_onboarding_analytics` | admin `WIREAI_ADMIN_KEY` | funnel JSON |
1352
- | `wireai_list_reviews` | admin `WIREAI_ADMIN_KEY` | **read** in-app reviews across tenants (stars, feedback, contact) |
1353
-
1354
- Least privilege: for the read-only loop, hand the agent just `WIREAI_SERVER_URL` +
1355
- `WIREAI_API_KEY` (tenant) and skip the admin key. Full detail: [`mcp/README.md`](./mcp/README.md).
1342
+ **Where the tool list lives.** Ask the server: a connected client's `tools/list` is the
1343
+ only copy that is current by construction, and it is already scoped to the credential you
1344
+ presented. `wireai_get_integration_guide` needs no auth at all and returns the recipe.
1345
+ Inside the repo the written table is `mcp/README.md`, and `mcp/src/toolAccess.test.ts`
1346
+ re-derives it from the handlers and goes red when the two disagree.
1347
+
1348
+ This section deliberately does not restate that table. It used to, and the copy drifted to
1349
+ 10 tools of 21 with a "the only writes are register/update" line the code had stopped
1350
+ honouring, because nothing gated it. A duplicated list with no test on it goes stale.
1351
+
1352
+ Least privilege still applies: for the read-only improve loop, hand the agent a tenant
1353
+ credential and skip the admin key entirely.
1356
1354
 
1357
1355
  ### `wire-ai` skill (`.claude/skills/wire-ai`)
1358
1356
 
@@ -1363,6 +1361,137 @@ generation context). For the full
1363
1361
  scaffold-the-screen integration job, the [`wire-rn-integration`](./INTEGRATION_PROMPT.md)
1364
1362
  skill drives it end to end.
1365
1363
 
1364
+ ## EU AI Act: acceptable use and our position
1365
+
1366
+ **Position as of 2026-08-06, against Regulation (EU) 2024/1689 as amended by Regulation (EU) 2026/1744.**
1367
+
1368
+ This is our own reading of where this kit sits inside the regulation, written down so an integrator
1369
+ can see the reasoning and disagree with it. It is not legal advice. Nobody has audited this kit
1370
+ against the regulation, and a review by a qualified lawyer is pending. Where that review says we
1371
+ read something wrong, this section gets corrected and re-dated.
1372
+
1373
+ ### Who is who
1374
+
1375
+ The app that ships a Wire-powered surface is the **deployer** of that surface. You choose the flow,
1376
+ the audience, the questions, and what the answers are used for.
1377
+
1378
+ Wire is the provider of a limited-risk component inside that: a card renderer, plus a backend that
1379
+ selects and fills cards from a fixed vocabulary you configure.
1380
+
1381
+ ### Not for high-risk use
1382
+
1383
+ **This system is not intended to be put into service as, or changed into, a high-risk AI system.**
1384
+
1385
+ Do not deploy Wire-powered surfaces in an Annex III high-risk area:
1386
+
1387
+ 1. Biometrics
1388
+ 2. Critical infrastructure
1389
+ 3. Education and vocational training
1390
+ 4. Employment, workers management and access to self-employment
1391
+ 5. Access to essential private and public services and benefits
1392
+ 6. Law enforcement
1393
+ 7. Migration, asylum and border control
1394
+ 8. Administration of justice and democratic processes
1395
+
1396
+ Obligations for Annex III systems apply from 2 December 2027, which is not a countdown to when this
1397
+ becomes usable there. It is not what this kit is built for, at any date.
1398
+
1399
+ Two prohibitions in Article 5(1) have been live since 2 February 2025 and were left untouched by the
1400
+ Omnibus. Do not use these surfaces for subliminal, manipulative or deceptive techniques that
1401
+ materially distort behaviour, and do not use them to exploit vulnerabilities due to age, disability,
1402
+ or a specific social or economic situation.
1403
+
1404
+ That second one deserves saying out loud, because an onboarding flow that adapts per user is an easy
1405
+ place to cross it without meaning to. A flow that adapts because it learned what a user responds to
1406
+ is one short step from a flow that adapts because it learned what a user cannot resist. Optimise for
1407
+ the user finishing something they came to do.
1408
+
1409
+ ### Article 50(1): telling people they are talking to an AI
1410
+
1411
+ Article 50(1) applies since 2 August 2026. There is no legacy grace period for it.
1412
+
1413
+ It requires that AI systems intended to interact directly with natural persons are designed so those
1414
+ persons are informed they are interacting with an AI system, unless that is obvious from the point of
1415
+ view of a reasonably well-informed, observant and circumspect natural person.
1416
+
1417
+ Our reading of the generated-UI case: generated onboarding UI is not a conversational agent. Cards,
1418
+ buttons, pickers and form fields do not present themselves as a person, and someone tapping through a
1419
+ signup flow is not being led to believe there is a human on the other end. A form is obvious.
1420
+
1421
+ Where that reading stops: if you build a chat-like or free-text-responding surface, one that answers
1422
+ a user in free prose the way a person would, that is the case Article 50(1) was written for. If you
1423
+ build that, **your app owns the disclosure**, not the kit.
1424
+
1425
+ We cannot place it for you. We do not control your screen, your copy, or your first-run experience.
1426
+ Put it where the user meets the surface, not buried in a settings page nobody opens.
1427
+
1428
+ The honest edge in between: `TextInputCard` takes free text, and the copy on the next card is
1429
+ generated from what the user typed. We read that as a form that adapts rather than a conversation,
1430
+ because the system does not answer as an interlocutor and the interaction stays inside the card
1431
+ vocabulary you configured. If that reading does not hold for the flow you built, disclose. Nothing in
1432
+ the kit stops you.
1433
+
1434
+ ### Article 50(2): marking generated content
1435
+
1436
+ Article 50(2) covers providers of AI systems that generate synthetic audio, image, video or text, and
1437
+ requires the outputs to be marked in a machine-readable format as artificially generated or
1438
+ manipulated. Its legacy grace period runs to 2 December 2026, and only for providers of those
1439
+ generators.
1440
+
1441
+ Our reading has two parts.
1442
+
1443
+ The first is that Wire does not provide the generative model. The card copy a user reads was produced
1444
+ by a third-party model. Provider-level marking of a model's output sits upstream, with the provider
1445
+ of that model.
1446
+
1447
+ The second is that for the layer we do provide, the carve-out written into the article is the one we
1448
+ rely on. It "shall not apply to the extent the AI systems perform an assistive function for standard
1449
+ editing or do not substantially alter the input data provided by the deployer or the semantics
1450
+ thereof." You supply the app description, the first question, the allowed components and the copy
1451
+ constraints. The system selects and fills cards inside that. We read it as assistive rather than as
1452
+ generating a work of synthetic content.
1453
+
1454
+ Where that reading stops: if you use this kit as the delivery surface for a general content
1455
+ generator, handing a model's free-form output to a user as content, our reading does not carry over
1456
+ to your app. Look at Article 50(2) for yourself in that case.
1457
+
1458
+ ### Article 50(3): emotion recognition
1459
+
1460
+ We do not read Article 50(3) as engaged by anything this kit does today.
1461
+
1462
+ Article 3(39) defines an emotion recognition system as one inferring emotions or intentions **on the
1463
+ basis of biometric data**, and recital 18 confines that to biometric signals like face, gesture and
1464
+ voice. This kit reads typed and tapped answers, and text is not biometric data. No camera, no
1465
+ microphone, no biometric processing anywhere in the flow.
1466
+
1467
+ ### Article 25: the specification, and why we are not standing behind a licence
1468
+
1469
+ Article 25(2) closes with an express carve-out. The provider flip does not apply "in cases where the
1470
+ initial provider has clearly specified that its AI system is not to be changed into a high-risk AI
1471
+ system."
1472
+
1473
+ The sentence in bold under **Not for high-risk use** is that specification. It is written
1474
+ deliberately, and it is why this section exists in the README rather than in a lawyer's drawer.
1475
+
1476
+ Article 25(4) ends by exempting third parties who make tools, services, processes or components
1477
+ (other than general-purpose AI models) publicly available "under a free and open-source licence" from
1478
+ the written-agreement duty. `@wireai/activation` is published on npm under the MIT licence, so on the
1479
+ face of it that sentence reaches it, though the source repository is private and that is a difference
1480
+ worth stating rather than glossing over.
1481
+
1482
+ We would rather not stand on that carve-out. Unlike the open-source SDK underneath this kit, we
1483
+ usually know who integrates this one and we have a direct channel to them. So if you integrate the
1484
+ kit and need information about it to meet your own obligations as a deployer, ask, and you get it in
1485
+ writing. That is a commitment about how we behave, not a claim about our legal position.
1486
+
1487
+ ### What this section is not
1488
+
1489
+ Nobody has reviewed or audited this kit against the regulation. This is not a statement that it meets
1490
+ a legal standard, and it is not a badge for a landing page.
1491
+
1492
+ It is what we think the regulation asks of a component like this one, what we did about it, and which
1493
+ parts sit on your side of the line rather than ours. Your app, your users, your assessment.
1494
+
1366
1495
  ## More from Code Meet AI
1367
1496
 
1368
1497
  **Open source:** [wireai-rn](https://github.com/chohra-med/wireai-rn) · [expo_boilerplate](https://github.com/chohra-med/expo_boilerplate) · [colorway-c-brand](https://github.com/chohra-med/colorway-c-brand) · [claude_design_skill](https://github.com/chohra-med/claude_design_skill)
@@ -1,6 +1,6 @@
1
1
  export { R as ReportAppEventOptions, r as reportAppEvent } from '../transport-j5gFfJhK.mjs';
2
- import { C as ClientEventTarget, W as WireUserContext, E as EventQueueOptions } from '../currentSession-DgJf0fRz.mjs';
3
- export { A as AUTO_DEVICE_ID_PREFIX, a as AnalyticsEvent, b as ClearUserContextOptions, c as ClientEvent, d as ClientEventType, e as ContextEnvelope, f as ContextEnvelopeInput, D as DeviceKeyStorage, g as EnvelopeSource, h as EventQueue, R as ResolveAutoDeviceKeyOptions, i as WIRE_ONBOARDING_EVENTS, j as WireOnboardingEventName, k as analyticsUserIdStorageKey, l as buildContextEnvelope, m as clearPiiFromContext, n as clearUserContext, o as createEventQueue, p as deviceIdStorageKey, q as ensureCurrentSessionId, r as getCurrentSessionId, s as looksLikeEmail, t as makeSessionId, u as reportClientEvent, v as reportClientEventAwait, w as reportClientEvents, x as reportClientEventsAwait, y as resetAutoDeviceKeys, z as resetCurrentSessionId, B as resetEventQueueKeys, F as resolveAutoDeviceKey, G as setCurrentSessionId, H as toAnalyticsEvent } from '../currentSession-DgJf0fRz.mjs';
2
+ import { C as ClientEventTarget, W as WireUserContext, E as EventQueueOptions } from '../currentSession-DngW-QoD.mjs';
3
+ export { A as AUTO_DEVICE_ID_PREFIX, a as AnalyticsEvent, b as ClearUserContextOptions, c as ClientEvent, d as ClientEventType, e as ContextEnvelope, f as ContextEnvelopeInput, D as DeviceKeyStorage, g as EnvelopeSource, h as EventQueue, R as ResolveAutoDeviceKeyOptions, i as WIRE_ONBOARDING_EVENTS, j as WireOnboardingEventName, k as analyticsUserIdStorageKey, l as buildContextEnvelope, m as clearPiiFromContext, n as clearUserContext, o as createEventQueue, p as deviceIdStorageKey, q as ensureCurrentSessionId, r as getCurrentSessionId, s as looksLikeEmail, t as makeSessionId, u as reportClientEvent, v as reportClientEventAwait, w as reportClientEvents, x as reportClientEventsAwait, y as resetAutoDeviceKeys, z as resetCurrentSessionId, B as resetEventQueueKeys, F as resolveAutoDeviceKey, G as setCurrentSessionId, H as toAnalyticsEvent } from '../currentSession-DngW-QoD.mjs';
4
4
  import { W as WireOnboardingStorage } from '../types-BpwiRpA8.mjs';
5
5
  import '../types-Cju-1_jT.mjs';
6
6
  import '../types-BKfpdZzX.mjs';
@@ -1,6 +1,6 @@
1
1
  export { R as ReportAppEventOptions, r as reportAppEvent } from '../transport-B_0SgCBe.js';
2
- import { C as ClientEventTarget, W as WireUserContext, E as EventQueueOptions } from '../currentSession-EOHU64QD.js';
3
- export { A as AUTO_DEVICE_ID_PREFIX, a as AnalyticsEvent, b as ClearUserContextOptions, c as ClientEvent, d as ClientEventType, e as ContextEnvelope, f as ContextEnvelopeInput, D as DeviceKeyStorage, g as EnvelopeSource, h as EventQueue, R as ResolveAutoDeviceKeyOptions, i as WIRE_ONBOARDING_EVENTS, j as WireOnboardingEventName, k as analyticsUserIdStorageKey, l as buildContextEnvelope, m as clearPiiFromContext, n as clearUserContext, o as createEventQueue, p as deviceIdStorageKey, q as ensureCurrentSessionId, r as getCurrentSessionId, s as looksLikeEmail, t as makeSessionId, u as reportClientEvent, v as reportClientEventAwait, w as reportClientEvents, x as reportClientEventsAwait, y as resetAutoDeviceKeys, z as resetCurrentSessionId, B as resetEventQueueKeys, F as resolveAutoDeviceKey, G as setCurrentSessionId, H as toAnalyticsEvent } from '../currentSession-EOHU64QD.js';
2
+ import { C as ClientEventTarget, W as WireUserContext, E as EventQueueOptions } from '../currentSession-C5976akx.js';
3
+ export { A as AUTO_DEVICE_ID_PREFIX, a as AnalyticsEvent, b as ClearUserContextOptions, c as ClientEvent, d as ClientEventType, e as ContextEnvelope, f as ContextEnvelopeInput, D as DeviceKeyStorage, g as EnvelopeSource, h as EventQueue, R as ResolveAutoDeviceKeyOptions, i as WIRE_ONBOARDING_EVENTS, j as WireOnboardingEventName, k as analyticsUserIdStorageKey, l as buildContextEnvelope, m as clearPiiFromContext, n as clearUserContext, o as createEventQueue, p as deviceIdStorageKey, q as ensureCurrentSessionId, r as getCurrentSessionId, s as looksLikeEmail, t as makeSessionId, u as reportClientEvent, v as reportClientEventAwait, w as reportClientEvents, x as reportClientEventsAwait, y as resetAutoDeviceKeys, z as resetCurrentSessionId, B as resetEventQueueKeys, F as resolveAutoDeviceKey, G as setCurrentSessionId, H as toAnalyticsEvent } from '../currentSession-C5976akx.js';
4
4
  import { W as WireOnboardingStorage } from '../types-BpwiRpA8.js';
5
5
  import '../types-h2BZvl1t.js';
6
6
  import '../types-BKfpdZzX.js';
@@ -471,6 +471,29 @@ type OnboardingResult = {
471
471
  answers: Record<string, unknown>;
472
472
  /** The raw message thread, for custom downstream parsing. */
473
473
  raw: Message[];
474
+ /**
475
+ * The backend's onboarding plan, when it sent one. Present ONLY on the AI path: a tenant running
476
+ * the static flow, or any run the server finished without a plan, leaves this `undefined` AND
477
+ * leaves the key off the result object entirely — so a host written before plans existed sees
478
+ * byte-identically what it always saw.
479
+ *
480
+ * ⚠️ The kit does NOT interpret this and does NOT validate it. It checks one structural fact (a
481
+ * plan is an object) and hands the payload straight through, unread, unlogged, and never attached
482
+ * to an analytics event. THE HOST MUST VALIDATE IT before applying it: the fields are
483
+ * backend-authored, they may gain new ones without a kit release, and what any of them mean is
484
+ * the host's decision, not the kit's.
485
+ */
486
+ plan?: unknown;
487
+ /**
488
+ * The experiment ARM the backend assigned this session, when it is running one. Absent when
489
+ * the tenant runs no experiment, which is the common case, and absent is NOT an error.
490
+ * The kit does NOT interpret it, does not log it, does not brand it and never attaches it
491
+ * to an analytics event. What an arm key MEANS is the host's decision.
492
+ *
493
+ * Like {@link plan}, the KEY is set only when an arm was actually seen, so a run with no
494
+ * experiment leaves the result byte-identical to what every host already reads.
495
+ */
496
+ variant?: string;
474
497
  };
475
498
  /**
476
499
  * Lifecycle events emitted as the flow runs, for host-side analytics. The kit owns
@@ -482,7 +505,11 @@ type OnboardingResult = {
482
505
  * - `resumed`: a persisted session was restored after an app kill (fires INSTEAD of
483
506
  * `started`, so host funnels don't double-count the same session). Also carries
484
507
  * `contextId`. Requires the `storage` prop.
485
- * - `turn`: a new assistant card arrived (`step` = 1-based index of cards seen).
508
+ * - `turn`: a new assistant card arrived (`step` = 1-based index of cards seen). Carries
509
+ * `variant`, the backend's experiment arm for this session, from the first card that
510
+ * declared one onwards — this is how the arm is available DURING the flow rather than
511
+ * only at completion. The key is absent whenever no arm has been seen, which is every
512
+ * turn of every tenant running no experiment.
486
513
  * - `error`: the backend errored or the first-card watchdog timed out.
487
514
  * - `retry`: a transient failure is being auto-retried (`attempt` = 1-based).
488
515
  * - `fallback`: retries are exhausted; the kit degraded to the static `fallbackFlow`
@@ -503,6 +530,7 @@ type OnboardingEvent = {
503
530
  type: "turn";
504
531
  step: number;
505
532
  component?: string;
533
+ variant?: string;
506
534
  } | {
507
535
  type: "error";
508
536
  reason: "backend" | "timeout";
@@ -777,6 +805,17 @@ type OnboardingProgress = {
777
805
  slot_id?: string;
778
806
  /** Whether the CURRENT screen may be skipped (backend-marked; default false → no Skip shown). */
779
807
  skippable?: boolean;
808
+ /**
809
+ * The EXPERIMENT ARM the backend assigned this session, when the tenant is running one. Rides the
810
+ * render envelope as a sibling of {@link step}/{@link total}/{@link key}/{@link slot_id}, and is
811
+ * OMITTED entirely for a tenant running no experiment — which is the common case, and is not an
812
+ * error. The assignment is sticky for the session, so a later card that omits it does not
813
+ * un-assign it.
814
+ *
815
+ * The kit does NOT interpret this. It is whitelisted, latched and handed to the host exactly as
816
+ * received; what an arm key MEANS is the host's decision. See {@link OnboardingResult.variant}.
817
+ */
818
+ variant?: string;
780
819
  };
781
820
 
782
821
  /**
@@ -471,6 +471,29 @@ type OnboardingResult = {
471
471
  answers: Record<string, unknown>;
472
472
  /** The raw message thread, for custom downstream parsing. */
473
473
  raw: Message[];
474
+ /**
475
+ * The backend's onboarding plan, when it sent one. Present ONLY on the AI path: a tenant running
476
+ * the static flow, or any run the server finished without a plan, leaves this `undefined` AND
477
+ * leaves the key off the result object entirely — so a host written before plans existed sees
478
+ * byte-identically what it always saw.
479
+ *
480
+ * ⚠️ The kit does NOT interpret this and does NOT validate it. It checks one structural fact (a
481
+ * plan is an object) and hands the payload straight through, unread, unlogged, and never attached
482
+ * to an analytics event. THE HOST MUST VALIDATE IT before applying it: the fields are
483
+ * backend-authored, they may gain new ones without a kit release, and what any of them mean is
484
+ * the host's decision, not the kit's.
485
+ */
486
+ plan?: unknown;
487
+ /**
488
+ * The experiment ARM the backend assigned this session, when it is running one. Absent when
489
+ * the tenant runs no experiment, which is the common case, and absent is NOT an error.
490
+ * The kit does NOT interpret it, does not log it, does not brand it and never attaches it
491
+ * to an analytics event. What an arm key MEANS is the host's decision.
492
+ *
493
+ * Like {@link plan}, the KEY is set only when an arm was actually seen, so a run with no
494
+ * experiment leaves the result byte-identical to what every host already reads.
495
+ */
496
+ variant?: string;
474
497
  };
475
498
  /**
476
499
  * Lifecycle events emitted as the flow runs, for host-side analytics. The kit owns
@@ -482,7 +505,11 @@ type OnboardingResult = {
482
505
  * - `resumed`: a persisted session was restored after an app kill (fires INSTEAD of
483
506
  * `started`, so host funnels don't double-count the same session). Also carries
484
507
  * `contextId`. Requires the `storage` prop.
485
- * - `turn`: a new assistant card arrived (`step` = 1-based index of cards seen).
508
+ * - `turn`: a new assistant card arrived (`step` = 1-based index of cards seen). Carries
509
+ * `variant`, the backend's experiment arm for this session, from the first card that
510
+ * declared one onwards — this is how the arm is available DURING the flow rather than
511
+ * only at completion. The key is absent whenever no arm has been seen, which is every
512
+ * turn of every tenant running no experiment.
486
513
  * - `error`: the backend errored or the first-card watchdog timed out.
487
514
  * - `retry`: a transient failure is being auto-retried (`attempt` = 1-based).
488
515
  * - `fallback`: retries are exhausted; the kit degraded to the static `fallbackFlow`
@@ -503,6 +530,7 @@ type OnboardingEvent = {
503
530
  type: "turn";
504
531
  step: number;
505
532
  component?: string;
533
+ variant?: string;
506
534
  } | {
507
535
  type: "error";
508
536
  reason: "backend" | "timeout";
@@ -777,6 +805,17 @@ type OnboardingProgress = {
777
805
  slot_id?: string;
778
806
  /** Whether the CURRENT screen may be skipped (backend-marked; default false → no Skip shown). */
779
807
  skippable?: boolean;
808
+ /**
809
+ * The EXPERIMENT ARM the backend assigned this session, when the tenant is running one. Rides the
810
+ * render envelope as a sibling of {@link step}/{@link total}/{@link key}/{@link slot_id}, and is
811
+ * OMITTED entirely for a tenant running no experiment — which is the common case, and is not an
812
+ * error. The assignment is sticky for the session, so a later card that omits it does not
813
+ * un-assign it.
814
+ *
815
+ * The kit does NOT interpret this. It is whitelisted, latched and handed to the host exactly as
816
+ * received; what an arm key MEANS is the host's decision. See {@link OnboardingResult.variant}.
817
+ */
818
+ variant?: string;
780
819
  };
781
820
 
782
821
  /**
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import React__default, { ReactNode } from 'react';
3
- import { I as WireOnboardingProps, J as WireOnboardingConfig, O as OnboardingResult, K as WirePermissionStatus, P as PermissionStage, L as WirePermissionOutcome, S as StepValidator, M as OnboardingEvent, N as OnboardingCopy, C as ClientEventTarget, Q as DeviceContext, T as PermissionScreenConfig, W as WireUserContext, U as PermissionPlacement, c as ClientEvent, g as EnvelopeSource } from './currentSession-DgJf0fRz.mjs';
4
- export { A as AUTO_DEVICE_ID_PREFIX, a as AnalyticsEvent, b as ClearUserContextOptions, d as ClientEventType, V as DEFAULT_PERMISSION_COPY, X as DeviceFormFactor, D as DeviceKeyStorage, Y as EXTRA_KEY_PREFIX, Z as GENERIC_PERMISSION_COPY, _ as IdentifyOnboardingBinding, $ as IdentifyOnboardingOptions, a0 as IdentityRecord, a1 as IdentitySource, a2 as IdentitySpace, a3 as NOTIFICATIONS_PERMISSION_COPY, a4 as OnboardingProgress, a5 as PermissionScreenCopy, R as ResolveAutoDeviceKeyOptions, a6 as ResolveUserContextOptions, a7 as ResolvedUserContext, a8 as USER_ID_MAX_LENGTH, i as WIRE_ONBOARDING_EVENTS, a9 as WIRE_PERMISSION_EVENTS, j as WireOnboardingEventName, aa as WirePermissionEventName, ab as WirePermissionKind, ac as activationJoinContext, k as analyticsUserIdStorageKey, m as clearPiiFromContext, n as clearUserContext, ad as collectDeviceContext, p as deviceIdStorageKey, q as ensureCurrentSessionId, r as getCurrentSessionId, ae as hashEmailFnv1a, af as hostIdentity, ag as hydrateAutoDeviceKey, ah as hydrateDeviceIdentity, ai as identifyOnboarding, aj as isWireScalar, s as looksLikeEmail, t as makeSessionId, ak as mintDeviceId, al as namespaceExtra, am as normalizePermissionStatus, an as permissionEventName, ao as permissionEventProps, u as reportClientEvent, v as reportClientEventAwait, w as reportClientEvents, x as reportClientEventsAwait, y as resetAutoDeviceKeys, z as resetCurrentSessionId, ap as resetIdentityProvenance, F as resolveAutoDeviceKey, aq as resolveIdentity, ar as resolvePermissionCopy, as as resolveUserContext, at as sanitizeUserId, G as setCurrentSessionId, H as toAnalyticsEvent } from './currentSession-DgJf0fRz.mjs';
3
+ import { I as WireOnboardingProps, J as WireOnboardingConfig, O as OnboardingResult, K as WirePermissionStatus, P as PermissionStage, L as WirePermissionOutcome, S as StepValidator, M as OnboardingEvent, N as OnboardingCopy, C as ClientEventTarget, Q as DeviceContext, T as PermissionScreenConfig, W as WireUserContext, U as PermissionPlacement, c as ClientEvent, g as EnvelopeSource } from './currentSession-DngW-QoD.mjs';
4
+ export { A as AUTO_DEVICE_ID_PREFIX, a as AnalyticsEvent, b as ClearUserContextOptions, d as ClientEventType, V as DEFAULT_PERMISSION_COPY, X as DeviceFormFactor, D as DeviceKeyStorage, Y as EXTRA_KEY_PREFIX, Z as GENERIC_PERMISSION_COPY, _ as IdentifyOnboardingBinding, $ as IdentifyOnboardingOptions, a0 as IdentityRecord, a1 as IdentitySource, a2 as IdentitySpace, a3 as NOTIFICATIONS_PERMISSION_COPY, a4 as OnboardingProgress, a5 as PermissionScreenCopy, R as ResolveAutoDeviceKeyOptions, a6 as ResolveUserContextOptions, a7 as ResolvedUserContext, a8 as USER_ID_MAX_LENGTH, i as WIRE_ONBOARDING_EVENTS, a9 as WIRE_PERMISSION_EVENTS, j as WireOnboardingEventName, aa as WirePermissionEventName, ab as WirePermissionKind, ac as activationJoinContext, k as analyticsUserIdStorageKey, m as clearPiiFromContext, n as clearUserContext, ad as collectDeviceContext, p as deviceIdStorageKey, q as ensureCurrentSessionId, r as getCurrentSessionId, ae as hashEmailFnv1a, af as hostIdentity, ag as hydrateAutoDeviceKey, ah as hydrateDeviceIdentity, ai as identifyOnboarding, aj as isWireScalar, s as looksLikeEmail, t as makeSessionId, ak as mintDeviceId, al as namespaceExtra, am as normalizePermissionStatus, an as permissionEventName, ao as permissionEventProps, u as reportClientEvent, v as reportClientEventAwait, w as reportClientEvents, x as reportClientEventsAwait, y as resetAutoDeviceKeys, z as resetCurrentSessionId, ap as resetIdentityProvenance, F as resolveAutoDeviceKey, aq as resolveIdentity, ar as resolvePermissionCopy, as as resolveUserContext, at as sanitizeUserId, G as setCurrentSessionId, H as toAnalyticsEvent } from './currentSession-DngW-QoD.mjs';
5
5
  import { O as OnboardingTheme } from './types-BKfpdZzX.mjs';
6
6
  export { a as OnboardingButtonStyle, b as OnboardingColors, c as OnboardingFonts, d as OnboardingRadius, e as OnboardingSpacing } from './types-BKfpdZzX.mjs';
7
7
  export { C as CenteredModal, a as CenteredModalHandle, b as CenteredModalProps } from './CenteredModal-C3qQBHsA.mjs';
@@ -1286,6 +1286,8 @@ type PartialProgress = {
1286
1286
  /** The stable per-slot identity, when the backend sends one. See `OnboardingProgress.slot_id`. */
1287
1287
  slot_id?: string;
1288
1288
  skippable?: boolean;
1289
+ /** The experiment arm assigned to this session, when the tenant runs one. See `OnboardingProgress.variant`. */
1290
+ variant?: string;
1289
1291
  };
1290
1292
  declare const readProgress: (response?: WireAIResponse) => PartialProgress;
1291
1293
 
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import React__default, { ReactNode } from 'react';
3
- import { I as WireOnboardingProps, J as WireOnboardingConfig, O as OnboardingResult, K as WirePermissionStatus, P as PermissionStage, L as WirePermissionOutcome, S as StepValidator, M as OnboardingEvent, N as OnboardingCopy, C as ClientEventTarget, Q as DeviceContext, T as PermissionScreenConfig, W as WireUserContext, U as PermissionPlacement, c as ClientEvent, g as EnvelopeSource } from './currentSession-EOHU64QD.js';
4
- export { A as AUTO_DEVICE_ID_PREFIX, a as AnalyticsEvent, b as ClearUserContextOptions, d as ClientEventType, V as DEFAULT_PERMISSION_COPY, X as DeviceFormFactor, D as DeviceKeyStorage, Y as EXTRA_KEY_PREFIX, Z as GENERIC_PERMISSION_COPY, _ as IdentifyOnboardingBinding, $ as IdentifyOnboardingOptions, a0 as IdentityRecord, a1 as IdentitySource, a2 as IdentitySpace, a3 as NOTIFICATIONS_PERMISSION_COPY, a4 as OnboardingProgress, a5 as PermissionScreenCopy, R as ResolveAutoDeviceKeyOptions, a6 as ResolveUserContextOptions, a7 as ResolvedUserContext, a8 as USER_ID_MAX_LENGTH, i as WIRE_ONBOARDING_EVENTS, a9 as WIRE_PERMISSION_EVENTS, j as WireOnboardingEventName, aa as WirePermissionEventName, ab as WirePermissionKind, ac as activationJoinContext, k as analyticsUserIdStorageKey, m as clearPiiFromContext, n as clearUserContext, ad as collectDeviceContext, p as deviceIdStorageKey, q as ensureCurrentSessionId, r as getCurrentSessionId, ae as hashEmailFnv1a, af as hostIdentity, ag as hydrateAutoDeviceKey, ah as hydrateDeviceIdentity, ai as identifyOnboarding, aj as isWireScalar, s as looksLikeEmail, t as makeSessionId, ak as mintDeviceId, al as namespaceExtra, am as normalizePermissionStatus, an as permissionEventName, ao as permissionEventProps, u as reportClientEvent, v as reportClientEventAwait, w as reportClientEvents, x as reportClientEventsAwait, y as resetAutoDeviceKeys, z as resetCurrentSessionId, ap as resetIdentityProvenance, F as resolveAutoDeviceKey, aq as resolveIdentity, ar as resolvePermissionCopy, as as resolveUserContext, at as sanitizeUserId, G as setCurrentSessionId, H as toAnalyticsEvent } from './currentSession-EOHU64QD.js';
3
+ import { I as WireOnboardingProps, J as WireOnboardingConfig, O as OnboardingResult, K as WirePermissionStatus, P as PermissionStage, L as WirePermissionOutcome, S as StepValidator, M as OnboardingEvent, N as OnboardingCopy, C as ClientEventTarget, Q as DeviceContext, T as PermissionScreenConfig, W as WireUserContext, U as PermissionPlacement, c as ClientEvent, g as EnvelopeSource } from './currentSession-C5976akx.js';
4
+ export { A as AUTO_DEVICE_ID_PREFIX, a as AnalyticsEvent, b as ClearUserContextOptions, d as ClientEventType, V as DEFAULT_PERMISSION_COPY, X as DeviceFormFactor, D as DeviceKeyStorage, Y as EXTRA_KEY_PREFIX, Z as GENERIC_PERMISSION_COPY, _ as IdentifyOnboardingBinding, $ as IdentifyOnboardingOptions, a0 as IdentityRecord, a1 as IdentitySource, a2 as IdentitySpace, a3 as NOTIFICATIONS_PERMISSION_COPY, a4 as OnboardingProgress, a5 as PermissionScreenCopy, R as ResolveAutoDeviceKeyOptions, a6 as ResolveUserContextOptions, a7 as ResolvedUserContext, a8 as USER_ID_MAX_LENGTH, i as WIRE_ONBOARDING_EVENTS, a9 as WIRE_PERMISSION_EVENTS, j as WireOnboardingEventName, aa as WirePermissionEventName, ab as WirePermissionKind, ac as activationJoinContext, k as analyticsUserIdStorageKey, m as clearPiiFromContext, n as clearUserContext, ad as collectDeviceContext, p as deviceIdStorageKey, q as ensureCurrentSessionId, r as getCurrentSessionId, ae as hashEmailFnv1a, af as hostIdentity, ag as hydrateAutoDeviceKey, ah as hydrateDeviceIdentity, ai as identifyOnboarding, aj as isWireScalar, s as looksLikeEmail, t as makeSessionId, ak as mintDeviceId, al as namespaceExtra, am as normalizePermissionStatus, an as permissionEventName, ao as permissionEventProps, u as reportClientEvent, v as reportClientEventAwait, w as reportClientEvents, x as reportClientEventsAwait, y as resetAutoDeviceKeys, z as resetCurrentSessionId, ap as resetIdentityProvenance, F as resolveAutoDeviceKey, aq as resolveIdentity, ar as resolvePermissionCopy, as as resolveUserContext, at as sanitizeUserId, G as setCurrentSessionId, H as toAnalyticsEvent } from './currentSession-C5976akx.js';
5
5
  import { O as OnboardingTheme } from './types-BKfpdZzX.js';
6
6
  export { a as OnboardingButtonStyle, b as OnboardingColors, c as OnboardingFonts, d as OnboardingRadius, e as OnboardingSpacing } from './types-BKfpdZzX.js';
7
7
  export { C as CenteredModal, a as CenteredModalHandle, b as CenteredModalProps } from './CenteredModal-Cdgns6--.js';
@@ -1286,6 +1286,8 @@ type PartialProgress = {
1286
1286
  /** The stable per-slot identity, when the backend sends one. See `OnboardingProgress.slot_id`. */
1287
1287
  slot_id?: string;
1288
1288
  skippable?: boolean;
1289
+ /** The experiment arm assigned to this session, when the tenant runs one. See `OnboardingProgress.variant`. */
1290
+ variant?: string;
1289
1291
  };
1290
1292
  declare const readProgress: (response?: WireAIResponse) => PartialProgress;
1291
1293