@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.
- package/AGENTS.md +73 -0
- package/CHANGELOG.md +572 -0
- package/INTEGRATION_PROMPT.md +55 -0
- package/LICENSE +21 -0
- package/README.md +923 -0
- package/dist/CenteredModal-C3qQBHsA.d.mts +38 -0
- package/dist/CenteredModal-Cdgns6--.d.ts +38 -0
- package/dist/coachmarks/index.d.mts +352 -0
- package/dist/coachmarks/index.d.ts +352 -0
- package/dist/coachmarks/index.js +944 -0
- package/dist/coachmarks/index.js.map +1 -0
- package/dist/coachmarks/index.mjs +918 -0
- package/dist/coachmarks/index.mjs.map +1 -0
- package/dist/decision-CFvGY6nP.d.ts +228 -0
- package/dist/decision-Cl8OFYzu.d.mts +228 -0
- package/dist/index.d.mts +1390 -0
- package/dist/index.d.ts +1390 -0
- package/dist/index.js +3260 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +3186 -0
- package/dist/index.mjs.map +1 -0
- package/dist/questionnaire/index.d.mts +249 -0
- package/dist/questionnaire/index.d.ts +249 -0
- package/dist/questionnaire/index.js +922 -0
- package/dist/questionnaire/index.js.map +1 -0
- package/dist/questionnaire/index.mjs +906 -0
- package/dist/questionnaire/index.mjs.map +1 -0
- package/dist/reviews/index.d.mts +213 -0
- package/dist/reviews/index.d.ts +213 -0
- package/dist/reviews/index.js +1001 -0
- package/dist/reviews/index.js.map +1 -0
- package/dist/reviews/index.mjs +971 -0
- package/dist/reviews/index.mjs.map +1 -0
- package/dist/showcase/index.d.mts +119 -0
- package/dist/showcase/index.d.ts +119 -0
- package/dist/showcase/index.js +660 -0
- package/dist/showcase/index.js.map +1 -0
- package/dist/showcase/index.mjs +651 -0
- package/dist/showcase/index.mjs.map +1 -0
- package/dist/types-BKfpdZzX.d.mts +84 -0
- package/dist/types-BKfpdZzX.d.ts +84 -0
- package/dist/types-CMuOexw0.d.mts +116 -0
- package/dist/types-CMuOexw0.d.ts +116 -0
- package/dist/types-GL_hQ0TN.d.mts +46 -0
- package/dist/types-GL_hQ0TN.d.ts +46 -0
- package/llms.txt +36 -0
- package/metro/index.d.ts +32 -0
- package/metro/index.js +83 -0
- package/package.json +171 -0
- package/src/DemoOnboarding.tsx +133 -0
- package/src/OnboardingFlow.tsx +504 -0
- package/src/WireOnboarding.tsx +248 -0
- package/src/analytics/analyticsEvent.ts +69 -0
- package/src/analytics/reportClientEvent.ts +119 -0
- package/src/analytics/sendPreview.ts +73 -0
- package/src/attribution/attribution.ts +46 -0
- package/src/cards/ChipSelectCard.tsx +265 -0
- package/src/cards/InterstitialCard.tsx +299 -0
- package/src/cards/NumberStepperCard.tsx +121 -0
- package/src/cards/SelectionCard.tsx +270 -0
- package/src/cards/StatusCard.tsx +170 -0
- package/src/cards/TextInputCard.tsx +147 -0
- package/src/cards/index.ts +23 -0
- package/src/coachmarks/CoachmarkOverlayHost.tsx +44 -0
- package/src/coachmarks/CoachmarkProvider.tsx +108 -0
- package/src/coachmarks/GestureHint.tsx +331 -0
- package/src/coachmarks/SpotlightOverlay.tsx +254 -0
- package/src/coachmarks/coachmarkAnchorRegistry.ts +70 -0
- package/src/coachmarks/coachmarkOverlayStore.ts +63 -0
- package/src/coachmarks/index.ts +62 -0
- package/src/coachmarks/runtime.ts +103 -0
- package/src/coachmarks/selectTourSteps.ts +45 -0
- package/src/coachmarks/types.ts +58 -0
- package/src/coachmarks/useCoachmarkAnchor.ts +49 -0
- package/src/coachmarks/useCoachmarkTour.ts +200 -0
- package/src/components/AnimatedSparkle.tsx +101 -0
- package/src/components/Button.tsx +129 -0
- package/src/components/CardHandoff.tsx +212 -0
- package/src/components/CardLayout.tsx +103 -0
- package/src/components/CenteredModal.tsx +184 -0
- package/src/components/CompletionView.tsx +186 -0
- package/src/components/DoneBlock.tsx +37 -0
- package/src/components/ErrorBlock.tsx +42 -0
- package/src/components/Illustration.tsx +36 -0
- package/src/components/LoadingBlock.tsx +182 -0
- package/src/components/LoadingScreen.tsx +35 -0
- package/src/components/OnboardingScaffold.tsx +105 -0
- package/src/components/StepProgress.tsx +85 -0
- package/src/components/loaderChrome.ts +28 -0
- package/src/config/onboardingFlag.ts +39 -0
- package/src/config/wireConfigFromEnv.ts +52 -0
- package/src/device/deviceContext.ts +158 -0
- package/src/features/WireFeaturesProvider.tsx +81 -0
- package/src/features/cache.ts +92 -0
- package/src/features/defaults.ts +67 -0
- package/src/features/fetchWireFeatures.ts +86 -0
- package/src/features/index.ts +39 -0
- package/src/features/types.ts +61 -0
- package/src/features/useWireFeatures.ts +57 -0
- package/src/identity/userIdentity.ts +100 -0
- package/src/illustrations/defaultIllustrations.tsx +99 -0
- package/src/index.ts +159 -0
- package/src/motion/centeredModalMotion.ts +45 -0
- package/src/motion/motionSpec.ts +133 -0
- package/src/motion/useReducedMotion.ts +43 -0
- package/src/questionnaire/QuestionnaireGate.tsx +235 -0
- package/src/questionnaire/decision.ts +63 -0
- package/src/questionnaire/index.ts +54 -0
- package/src/questionnaire/runtime.ts +20 -0
- package/src/questionnaire/transport.ts +83 -0
- package/src/questionnaire/types.ts +182 -0
- package/src/questionnaire/useQuestionnaireGate.ts +159 -0
- package/src/reviews/ReviewGate.tsx +316 -0
- package/src/reviews/ReviewModal.tsx +13 -0
- package/src/reviews/decision.ts +120 -0
- package/src/reviews/equality.ts +54 -0
- package/src/reviews/expo-store-review.d.ts +16 -0
- package/src/reviews/feedbackForm.ts +41 -0
- package/src/reviews/index.ts +78 -0
- package/src/reviews/presentation.ts +29 -0
- package/src/reviews/runtime.ts +45 -0
- package/src/reviews/storeReview.ts +62 -0
- package/src/reviews/transport.ts +89 -0
- package/src/reviews/types.ts +172 -0
- package/src/reviews/useReviewGate.ts +137 -0
- package/src/session/persistedSession.ts +143 -0
- package/src/session-analytics/index.ts +18 -0
- package/src/session-analytics/reportSessionStart.ts +144 -0
- package/src/session-analytics/useSessionStart.ts +114 -0
- package/src/showcase/FeatureShowcase.tsx +233 -0
- package/src/showcase/index.ts +19 -0
- package/src/showcase/selectShowcaseSlides.ts +45 -0
- package/src/showcase/showcaseColors.ts +91 -0
- package/src/showcase/types.ts +74 -0
- package/src/theme/ThemeContext.tsx +38 -0
- package/src/theme/defaultTheme.ts +70 -0
- package/src/theme/themeFromBrand.ts +93 -0
- package/src/theme/types.ts +88 -0
- package/src/theme/typography.ts +46 -0
- package/src/types.ts +215 -0
- package/src/utils/deriveAnswers.ts +43 -0
- package/src/utils/readProgress.ts +33 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `wireai-onboarding`. Consumed from source (unpublished),
|
|
4
|
+
so versions are informational.
|
|
5
|
+
|
|
6
|
+
## [0.7.0] — Unreleased
|
|
7
|
+
|
|
8
|
+
### Added: session mapping — know when a user opens the app again
|
|
9
|
+
|
|
10
|
+
- New `session-analytics/` module. `reportSessionStart(opts)` posts ONE standard
|
|
11
|
+
`app.session_started` event per app-open to `POST /v1/events`, carrying the host's opaque
|
|
12
|
+
`userId` (optional; pre-auth opens are device-only), a stable non-PII `deviceKey`, the host's
|
|
13
|
+
`sessionCount`, a derived `returning` flag (`session_count > 1`), and `appVersion`/`platform`.
|
|
14
|
+
Fire-and-forget (never throws, never awaits) with a once-per-open guard so a re-render can never
|
|
15
|
+
double-fire. The event is stored as `event_type='app_event'`, `question_key='app.session_started'`
|
|
16
|
+
(the app.* namespace the reviews wave shipped), so it needs NO server change: the same event
|
|
17
|
+
stream the console and decision engines already read now answers "show the questionnaire on this
|
|
18
|
+
user's Nth session" (`min_sessions:N`, or a trigger `{event:"app.session_started", min_count:N}`)
|
|
19
|
+
and becomes per-user retention analytics.
|
|
20
|
+
- The per-open `session_id` is a fresh `makeSessionId()` each open, NOT the onboarding A2A
|
|
21
|
+
contextId (onboarding runs once; session-start fires on every open). Grouping over time is by
|
|
22
|
+
`deviceKey`/`userId`, never by session_id. `deviceKey` links a device's pre-auth opens to the
|
|
23
|
+
user once `userId` arrives (here or via `identifyOnboarding`).
|
|
24
|
+
- Convenience hook `useSessionStart(config, {userId, sessionCount, deviceKey})` fires on mount and
|
|
25
|
+
on foreground-after-30-min-background (a real new open, not a quick app-switch), collecting the
|
|
26
|
+
device snapshot for you. Optional: a host with its own session counter (Myelino/Morrow) can call
|
|
27
|
+
the plain `reportSessionStart` directly instead. Both paths are first-class.
|
|
28
|
+
- Exports added to the core barrel: `reportSessionStart`, `useSessionStart`, `SESSION_STARTED_EVENT`,
|
|
29
|
+
`BACKGROUND_SESSION_MS`, and the `ReportSessionStartOptions` / `SessionStartConfig` /
|
|
30
|
+
`UseSessionStartOptions` types. New README section "Session mapping".
|
|
31
|
+
|
|
32
|
+
## [0.6.0] — 2026-07-11
|
|
33
|
+
|
|
34
|
+
### Fixed: the between-turns loader now centers in the full screen
|
|
35
|
+
|
|
36
|
+
- The "AI is thinking" loader rendered a little low: the between-turns `LoadingBlock` fills the
|
|
37
|
+
`OnboardingScaffold` body, which sits below the header progress bar, so it centered in the body
|
|
38
|
+
rather than the screen (the device finding "the loading between questions animation is not
|
|
39
|
+
centralized"). The scaffold now publishes its top-chrome height through a new `loaderChrome`
|
|
40
|
+
context and `LoadingBlock` shifts its centered content up by half that distance, landing at the
|
|
41
|
+
true screen center. It is a static transform on the loader alone, so the card underneath and the
|
|
42
|
+
card-to-loader handoff are untouched (no reflow, no jump; opacity/transform, native driver).
|
|
43
|
+
- The standalone loader slots (cold-start, resumed-session restoring, transient error/timeout) used
|
|
44
|
+
to return a bare `LoadingBlock` whose flex only filled whatever the host gave it (no safe area,
|
|
45
|
+
top-anchored if unbounded). New exported `LoadingScreen` wraps them in a full-area `SafeAreaView`
|
|
46
|
+
so every loader slot centers the same way. `WireOnboarding` restoring and `OnboardingFlow`
|
|
47
|
+
cold-start/error now use it. The Scripted-onboarding playground demo exercises the fixed
|
|
48
|
+
between-turns layout unchanged (same `OnboardingScaffold` + `CardHandoff` + `LoadingBlock`).
|
|
49
|
+
|
|
50
|
+
### Added: `CenteredModal`, the shared centered-popup shell (reviews API unchanged)
|
|
51
|
+
|
|
52
|
+
- The content-agnostic centered-popup shell was extracted, unchanged, out of the reviews module
|
|
53
|
+
into `components/CenteredModal` (`CenteredModal` / `CenteredModalHandle` / `CenteredModalProps`),
|
|
54
|
+
exported from the core barrel. It is the same transparent RN `Modal` + dimmed backdrop +
|
|
55
|
+
centered card + spring/fade entrance on RN `Animated` (native driver, zero new deps), with
|
|
56
|
+
backdrop-tap / Android-back / imperative `close()` dismissal that animates out then fires
|
|
57
|
+
`onDismiss`. Its pure entrance spec moved to `motion/centeredModalMotion`.
|
|
58
|
+
- `reviews/ReviewModal` is now a thin back-compat re-export of `CenteredModal`, so `ReviewModal`,
|
|
59
|
+
`ReviewModalHandle`, and `ReviewModalProps` still import from `wireai-onboarding/reviews` and
|
|
60
|
+
resolve to the exact same runtime object. `reviews/presentation` re-exports the spec under its
|
|
61
|
+
original `REVIEW_MODAL_ANIM` / `entranceFrame` names. The reviews public API and its canary are
|
|
62
|
+
unchanged; a new `centeredModal` canary covers the shared shell. The questionnaire module can
|
|
63
|
+
now reuse the shell without depending on reviews/.
|
|
64
|
+
|
|
65
|
+
### Changed: the 1-4 review feedback phase is now TWO optional inputs
|
|
66
|
+
|
|
67
|
+
- `ReviewGate` feedback phase (the 1-4 star path) now renders two optional inputs instead of
|
|
68
|
+
one required box: the existing multiline box is relabelled "why this rating" (the opinion),
|
|
69
|
+
and a smaller "what can you suggest" input is added below it. The rating is the only mandatory
|
|
70
|
+
action now, so Send is no longer disabled on empty text: it works with the opinion box, the
|
|
71
|
+
suggestion box, both, or neither. Warm thank-you + Skip are unchanged, and the fire-once
|
|
72
|
+
`onShown` / `onEvent` ref pattern and every analytics moment are untouched.
|
|
73
|
+
|
|
74
|
+
### Added: `suggestion` carried to the server
|
|
75
|
+
|
|
76
|
+
- `ReviewSubmission` gains an optional `suggestion` field and `buildReviewSubmission` accepts a
|
|
77
|
+
`suggestion` input, trimmed and included in the POST body only when non-empty (mirroring the
|
|
78
|
+
`feedback_text` / `contact` handling). The server ignores the extra field until its own change
|
|
79
|
+
lands, so this is additive and safe to ship first.
|
|
80
|
+
- The playground Review Gate demo surfaces both inputs so the two-input feedback phase can be
|
|
81
|
+
tapped through by hand.
|
|
82
|
+
|
|
83
|
+
### Added: the `questionnaire` feature switch
|
|
84
|
+
|
|
85
|
+
- `WireFeatures` gained a `questionnaire: { enabled }` switch, mirroring the server's
|
|
86
|
+
`features.questionnaire`. It fails open to on (an absent or malformed key resolves to
|
|
87
|
+
`{ enabled: true }`, like `review`), is carried in the frozen `defaultWireFeatures`, parsed via
|
|
88
|
+
the shared `readEnabled` tolerant reader, and compared in `featuresEqual`. Dark-by-default for
|
|
89
|
+
the questionnaire comes from the server not returning an enabled switch, not from this default.
|
|
90
|
+
The questionnaire module gates on this flag.
|
|
91
|
+
|
|
92
|
+
### Added: the `questionnaire` module (skippable pre-onboarding popup)
|
|
93
|
+
|
|
94
|
+
- New subpath `wireai-onboarding/questionnaire` (mirrors `reviews/`): a `QuestionnaireGate`
|
|
95
|
+
popup over the shared `CenteredModal` shell with three inputs, one MANDATORY "what do you
|
|
96
|
+
think about the app" box plus two OPTIONAL boxes ("help us improve" + "suggestions"). Send is
|
|
97
|
+
disabled ONLY until the opinion box has text; the two optional boxes never gate it, and the
|
|
98
|
+
popup is ALWAYS skippable ("Not now" plays the exit, backdrop tap and Android back dismiss).
|
|
99
|
+
On send it fire-and-forgets the contract body to `POST /v1/questionnaires/{id}/responses` and
|
|
100
|
+
shows a warm thank-you.
|
|
101
|
+
- `useQuestionnaireGate` owns WHEN the gate shows, mirroring `useReviewGate` exactly: local
|
|
102
|
+
firing rules OR a server-provided `decision` (the AI seam, `decideQuestionnaire`), gated by the
|
|
103
|
+
`questionnaire` master switch (`useResolvedFeatures(...).questionnaire.enabled`, fail-open) and
|
|
104
|
+
shown at most once per user per questionnaire `id` via the shared coachmark seen-gate, so a
|
|
105
|
+
server bug can never re-spam the popup. Inline `config`/`decision` props are value-stabilized so
|
|
106
|
+
a verdict-keyed host effect never loops (frequent_rules #11).
|
|
107
|
+
- `submitQuestionnaireResponse` POSTs the contract body (`{ session_id, answers: { opinion,
|
|
108
|
+
improve, suggestions }, meta: { user_id, device_key, session_count } }`) fire-and-forget, and a
|
|
109
|
+
best-effort `fetchQuestionnaireDecision` reads `GET /v1/questionnaires/decision` with hard
|
|
110
|
+
fail-open (unreachable / non-2xx / 404 resolve to null, so the host simply never shows the
|
|
111
|
+
gate). Pure helpers `buildQuestionnaireSubmission` (opinion required, empties omitted) and the
|
|
112
|
+
reused `evaluateGate` / `resolveRules` keep the logic verifiable in isolation.
|
|
113
|
+
- `package.json` gains `exports["./questionnaire"]` (react-native to source, import/require to
|
|
114
|
+
dist), and tsup builds the new subpath. Zero new runtime deps (RN `Animated` only). A new
|
|
115
|
+
`test/canary/questionnaire.test.tsx` proves the hook does not loop on inline props and that
|
|
116
|
+
Send is gated on the opinion box only. A playground "8 Questionnaire" demo screen surfaces the
|
|
117
|
+
trigger, the skip path, the mandatory-first-field validation, and the real once-gate.
|
|
118
|
+
|
|
119
|
+
## [0.5.0] — 2026-07-11
|
|
120
|
+
|
|
121
|
+
### Added: per-module feature controls (dashboard kill switches, hard fail-open)
|
|
122
|
+
|
|
123
|
+
- New core module `src/features/`: `fetchWireFeatures(config)` reads the per-module switches
|
|
124
|
+
from `GET /v1/features` (tenant key, `Cache-Control: private, max-age=300`), with a last-known
|
|
125
|
+
cache persisted through the optional `storage`. THE CONTRACT is fail-open, never fail-dark:
|
|
126
|
+
unreachable, 401, 5xx, timeout, or malformed JSON all resolve to the last cached value, else the
|
|
127
|
+
all-on defaults (`defaultWireFeatures` = every module on, onboarding `ai`). No failure path ever
|
|
128
|
+
throws into the host. A control-plane outage can only be more permissive than the dashboard
|
|
129
|
+
intends. It can never dark a tenant's activation.
|
|
130
|
+
- New hook `useWireFeatures(config?)` (value-stabilized inputs per frequent_rules #11) and an
|
|
131
|
+
optional `WireFeaturesProvider` so one fetch serves every module. Zero setup is a valid setup:
|
|
132
|
+
omit the wiring and every surface stays on (defaults), zero behavior change, zero extra fetches.
|
|
133
|
+
- Gated the three client surfaces with graceful semantics:
|
|
134
|
+
- `CoachmarkProvider` / the overlay host: `coachmarks.enabled === false` makes tour and overlay
|
|
135
|
+
`show()` requests silent no-ops. The once-per-tour gates are NOT consumed, so re-enabling
|
|
136
|
+
replays correctly.
|
|
137
|
+
- `FeatureShowcase`: `showcase.enabled === false` renders nothing and calls `onDone()` from an
|
|
138
|
+
effect so the host flow continues (cannot strand a navigation flow); the seen gate is not
|
|
139
|
+
written, so re-enabling replays.
|
|
140
|
+
- `useReviewGate` / `ReviewGate`: `review.enabled === false` means the gate never fires,
|
|
141
|
+
composing as a master switch over the existing local-rules + server-decision seam.
|
|
142
|
+
- Onboarding needs NO kit gating: the server owns `ai` | `static` | `off` over the same A2A
|
|
143
|
+
contract (`off` already returns a terminal card the kit reads as completion). See the README.
|
|
144
|
+
- Wiring is OPTIONAL at every surface: an explicit `features` prop, else the `WireFeaturesProvider`
|
|
145
|
+
context, else a lazy `featuresConfig` fetch, else pure defaults.
|
|
146
|
+
- Tests: 26 added (fetch / cache / fail-open matrix covering 401, 5xx, timeout, malformed JSON,
|
|
147
|
+
cache hit, stale-while-error, no-creds; parser tolerance; the coachmarks-disabled no-op; a
|
|
148
|
+
`WireFeaturesProvider` render canary; and gating-semantics for showcase + reviews). Full suite
|
|
149
|
+
93 to 119, green.
|
|
150
|
+
- New public exports from the core barrel: `fetchWireFeatures`, `useWireFeatures`,
|
|
151
|
+
`WireFeaturesProvider`, `useWireFeaturesContext`, `useResolvedFeatures`, `defaultWireFeatures`,
|
|
152
|
+
`parseWireFeatures`, `featuresEqual`, the cache helpers, and the `WireFeatures` / `OnboardingMode`
|
|
153
|
+
types. Coachmarks barrel adds `setCoachmarksEnabled` / `areCoachmarksEnabled`.
|
|
154
|
+
|
|
155
|
+
## [0.4.0] — 2026-07-10
|
|
156
|
+
|
|
157
|
+
### Added: render-canary test harness (runtime coverage for the gates)
|
|
158
|
+
- `npm test` now mounts every publicly exported component headlessly
|
|
159
|
+
(react-test-renderer, dev-only) under deliberately unstable inline props and
|
|
160
|
+
fails the suite if any component exceeds its render budget. This closes the
|
|
161
|
+
structural gap that let the ReviewGate remount loop ship past every static
|
|
162
|
+
gate: bundling and static analysis green is not runtime safe. 20 canary tests
|
|
163
|
+
across reviews, cards, showcase, coachmarks, and the flow shell, plus an
|
|
164
|
+
interaction sanity layer. The tester gate definition and frequent_rules #11
|
|
165
|
+
now require a canary for every new or changed component.
|
|
166
|
+
|
|
167
|
+
<!-- Retitled from 0.3.2: this section now also ships the card-interactions motion
|
|
168
|
+
system (new public exports: CardHandoff, useReducedMotion, motionSpec; new
|
|
169
|
+
optional InterstitialCard `items` prop) — additive feature work = minor bump. -->
|
|
170
|
+
|
|
171
|
+
### Added: card-interactions motion system (Wire Activation design)
|
|
172
|
+
- **The whole questionnaire now moves per the locked design canvas** ("Wire
|
|
173
|
+
Activation - Card Interactions"); the extracted, testable spec lives in
|
|
174
|
+
`design-refs/EXTRACTED-SPEC.md` and every value is a constant in the new
|
|
175
|
+
`src/motion/motionSpec.ts` (pure, unit-tested — components import, never inline).
|
|
176
|
+
- `SelectionCard`: rows tick on press (scale 0.975 spring), the selected
|
|
177
|
+
`primarySoft` fill + `primary` border crossfade in over 180ms ease-wire on a
|
|
178
|
+
native-driver overlay, and a 24px check dot pops in with a spring. Rows gain
|
|
179
|
+
radio/checkbox accessibility roles + states. The selected label now keeps the
|
|
180
|
+
text color (accent discipline: accent on border/dot/fill only).
|
|
181
|
+
- `ChipSelectCard`: chips press-tick (scale 0.94), the full-accent selected pill
|
|
182
|
+
crossfades in over 160ms with an `onPrimary` check pop; deselect reverses.
|
|
183
|
+
- `InterstitialCard`: art and title fade-up in sequence (500ms ease-wire, 100ms
|
|
184
|
+
stagger). NEW optional `items: string[]` prop (additive): 2-3 short "work being
|
|
185
|
+
done" lines that tick on one by one (90ms stagger, check pops 200ms after its
|
|
186
|
+
row), each meant to reference a real answer. Falls back to `body` unchanged.
|
|
187
|
+
- `StatusCard`: status glyph springs in (scale 0.4→1), copy fades up 80ms behind.
|
|
188
|
+
- `CompletionView` (the terminal payoff): badge spring pop + ONE soft accent
|
|
189
|
+
burst (scale 0.4→1.5, opacity 0.5→0, 500ms, fires once — celebratory without
|
|
190
|
+
confetti) + copy fade-up staggered 80ms. Settles under a second.
|
|
191
|
+
- NEW `CardHandoff` (exported): the card-to-card transition — the answered card
|
|
192
|
+
exits left (180ms) while the next enters from the right (220ms, 80ms overlap),
|
|
193
|
+
both ease-wire `cubic-bezier(0.16,1,0.3,1)`; loaders hand off with a 120ms
|
|
194
|
+
fade. Supersedes the internal enter-only `AnimatedCard` (which was never
|
|
195
|
+
exported). `OnboardingFlow` now routes loader and card states through it, so
|
|
196
|
+
the "thinking" state fades into the first card instead of hard-cutting.
|
|
197
|
+
- `LoadingBlock`: the "no spinner" loader — the mark breathes (1600ms), an
|
|
198
|
+
accent dot orbits a dashed ring (2400ms), and skeleton lines appear only past
|
|
199
|
+
300ms so a fast (prefetched) turn never flashes placeholder chrome.
|
|
200
|
+
`AnimatedSparkle` gains a `variant="pulse"` (default "spin" unchanged).
|
|
201
|
+
- `Button`: press answers the tap — scale 0.975 spring in/out (design row 20).
|
|
202
|
+
- `StepProgress`: fill now eases with the design curve (400ms ease-wire; was
|
|
203
|
+
450ms default easing). Still the one sanctioned JS-driver width animation.
|
|
204
|
+
- **Reduce motion is now ENFORCED, closing the documented gap** in
|
|
205
|
+
`ai_rules/rules/performance.md` §3: new shared hook `useReducedMotion`
|
|
206
|
+
(exported) reads `AccessibilityInfo.isReduceMotionEnabled` + the
|
|
207
|
+
`reduceMotionChanged` listener; every animation in the kit gates on it
|
|
208
|
+
(entrances become a ~120ms fade to the final frame, state changes snap, loops
|
|
209
|
+
go static, press feedback becomes an opacity dip). `ReviewModal`,
|
|
210
|
+
`AnimatedSparkle`, `CompletionView` and every card are covered; the rule file
|
|
211
|
+
is updated from "gap" to enforced with falsifiable greps.
|
|
212
|
+
- Public API: purely additive (`CardHandoff`, `useReducedMotion`, `motionSpec`,
|
|
213
|
+
InterstitialCard `items`). Existing integrators render correctly with zero
|
|
214
|
+
changes.
|
|
215
|
+
- Dev/QA: `motionSpec.setMotionDurationScale(x)` — a global duration multiplier
|
|
216
|
+
(2 = slow-mo) for judging motion; springs don't scale; production defaults to
|
|
217
|
+
1x. Driven by the playground's Tweak panel.
|
|
218
|
+
|
|
219
|
+
### Added: playground design-review surface
|
|
220
|
+
- **Design system storybook**: an 11-story "Design system" group on the
|
|
221
|
+
playground home gallery (Tokens specimen sheet, Buttons, Chips, Option rows,
|
|
222
|
+
Progress bar, AI-thinking/loading with the sub-300ms vs skeleton comparison,
|
|
223
|
+
Interstitial processing, Status/Completion replay, the CardHandoff transition
|
|
224
|
+
flipper, Review modal, Showcase slide) — every story shows its exact
|
|
225
|
+
props/tokens via the collapsible code block.
|
|
226
|
+
- **Tweak panel**: live global overrides (accent hex + presets, radius scale,
|
|
227
|
+
spacing scale, animation-speed 0.5x/1x/2x) applied across every story and
|
|
228
|
+
demo through the shared theme context; one-tap reset.
|
|
229
|
+
- **Scripted onboarding**: a full-screen 5-question run (chips, two selects,
|
|
230
|
+
interstitial with answer-reflecting `items`, final select, CompletionView)
|
|
231
|
+
through the REAL shell (OnboardingScaffold + CardHandoff + LoadingBlock) with
|
|
232
|
+
scripted 700ms thinking beats — the offline stand-in for a tenant flow.
|
|
233
|
+
|
|
234
|
+
### Fixed: ReviewGate render loop under unstable inline props
|
|
235
|
+
- **`useReviewGate` and `ReviewGate` are now hardened against inline props.** The
|
|
236
|
+
first on-device run of the centered review popup hit a Maximum-update-depth
|
|
237
|
+
crash: the host remounted the gate on every analytics event (a `key` derived
|
|
238
|
+
from an event log), and each fresh mount re-fired `review_prompt_shown`. The
|
|
239
|
+
kit now tolerates both failure ingredients: `config` and `decision` inputs are
|
|
240
|
+
value-stabilized (a shallow-equal object no longer recomputes the verdict or
|
|
241
|
+
remints a decision), the controller and `markShown`/`markResolved` are
|
|
242
|
+
referentially stable, and the shown event fires exactly once per mount through
|
|
243
|
+
latest-ref callbacks immune to inline `onEvent`/`onShown` recreation. The
|
|
244
|
+
once-per-user gate semantics are unchanged.
|
|
245
|
+
- New pure exports from `wireai-onboarding/reviews`: `shallowEqual` and
|
|
246
|
+
`sameDecision` (the value-equality helpers behind the stabilization), covered
|
|
247
|
+
by 10 new tests in `equality.test.ts`.
|
|
248
|
+
|
|
249
|
+
### Fixed: pre-onboarding showcase first slide flooded with the app accent
|
|
250
|
+
- **The showcase's large image/hero panel is now a calm surface, not an accent
|
|
251
|
+
flood.** 0.3.1 fixed the invisible CTA by mapping `background.primary` to the
|
|
252
|
+
brand `accent`. But in the underlying `@blazejkustra/react-native-onboarding`,
|
|
253
|
+
`background.primary` is ONE key that drives THREE surfaces at once: the primary
|
|
254
|
+
button (`buttons/PrimaryButton.tsx`), the intro highlight word
|
|
255
|
+
(`OnboardingIntroPanel.tsx`), and the full-bleed image hero panel
|
|
256
|
+
(`OnboardingImageContainer.tsx`'s `colorBg`). So the button fix also painted the
|
|
257
|
+
whole hero panel in the accent, which dominated the first slide and "hid" the
|
|
258
|
+
app (reported in Myelino on 0.3.1). The package exposes no per-surface color key
|
|
259
|
+
to split them, but it does accept a `background?: () => ReactNode` render prop
|
|
260
|
+
that REPLACES that panel fill outright. `FeatureShowcase` now passes one: a calm
|
|
261
|
+
theme `surface` base with a faint accent tint (~10% opacity) on top. Result: the
|
|
262
|
+
primary button and intro highlight keep the full accent (they read
|
|
263
|
+
`background.primary` directly), while the hero panel reads as a subtle branded
|
|
264
|
+
card. No app change needed.
|
|
265
|
+
- New pure export `showcasePanelBackground(theme, accent)` (plus `SHOWCASE_PANEL_TINT_OPACITY`,
|
|
266
|
+
`ShowcasePanelBackground`) holds the split, unit-tested in
|
|
267
|
+
`showcaseColors.test.ts` (button = accent, panel base = surface, not accent, tint in
|
|
268
|
+
the calm 8 to 12% band). The 0.3.1 button-color regression tests are unchanged and
|
|
269
|
+
still pass.
|
|
270
|
+
|
|
271
|
+
### Changed: in-app review feedback form (no identity ask, taller box, form CTA)
|
|
272
|
+
- **The 1 to 4 star feedback form no longer asks for identity.** The optional
|
|
273
|
+
name/email "contact" field was removed. The host already identifies the user via
|
|
274
|
+
`sessionId` + `meta` (e.g. `user_id`, `device_key`), which is all the POST body
|
|
275
|
+
carries alongside the rating and free text. The form never asks the user to type
|
|
276
|
+
who they are. (`ReviewSubmission.contact` / `buildReviewSubmission`'s `contact`
|
|
277
|
+
param stay as server-contract plumbing but are no longer populated by the kit.)
|
|
278
|
+
- **The free-text box is now a proper multiline field, ~4 lines tall (capped at
|
|
279
|
+
~5).** Height derives from the theme's body line-height via the new pure
|
|
280
|
+
`feedbackInputHeights(fonts, verticalPadding)` (plus `FEEDBACK_MIN_LINES` /
|
|
281
|
+
`FEEDBACK_MAX_LINES`), so it scales with the app's font config; `multiline` +
|
|
282
|
+
`textAlignVertical="top"`, hairline border + `radius.md` + muted placeholder from
|
|
283
|
+
theme tokens. Unit-tested in `feedbackForm.test.ts`.
|
|
284
|
+
- **The submit button now reads as a form CTA.** It uses the kit's themed `Button`
|
|
285
|
+
(accent background, `onPrimary` label, `button.radius` / `button.paddingX`: the
|
|
286
|
+
accent-not-background discipline) and stretches full-width via a new opt-in
|
|
287
|
+
`fullWidth` prop on `Button` (default false, so no other call site changes).
|
|
288
|
+
- All review events/analytics are unchanged. New `decision.test.ts` documents the
|
|
289
|
+
no-identity guarantee of the POST body.
|
|
290
|
+
|
|
291
|
+
## [0.3.1] — Unreleased
|
|
292
|
+
|
|
293
|
+
### Changed: the in-app review prompt is now a centered popup by default
|
|
294
|
+
- **`ReviewGate` renders as a centered modal** (dimmed backdrop, card in the middle of the
|
|
295
|
+
screen, spring/fade entrance) instead of the bare host-positioned card, which rendered
|
|
296
|
+
bottom-anchored and half-hidden on device (reported in Myelino on 0.3.0). The entrance
|
|
297
|
+
uses RN's built-in `Animated` on the native driver (the same idiom as `CompletionView` /
|
|
298
|
+
`AnimatedCard`), so the reviews module stays dependency-free (no reanimated). The card
|
|
299
|
+
surface, radius, spacing, and text all come from the active theme, so it is theme-aware in
|
|
300
|
+
light and dark. A backdrop tap or Android back dismisses it, counting as the same resolve
|
|
301
|
+
path `onResolved` already tracks; it never auto-closes on resolve, so the user still sees
|
|
302
|
+
the thank-you payoff. Adapted from the mobile-launcher boilerplate's `animated-modal`
|
|
303
|
+
(transparent RN `Modal` + centered container + backdrop-tap dismiss + content
|
|
304
|
+
stop-propagation + springy zoom-in), reimplemented on RN core primitives.
|
|
305
|
+
- **Additive API, no app change needed.** New optional `presentation?: "modal" | "inline"`
|
|
306
|
+
prop on `ReviewGate`, defaulting to `"modal"`. Apps like Myelino's `HomeReviewGate` pick up
|
|
307
|
+
the centered popup automatically when they bump to 0.3.1, with zero code changes. Pass
|
|
308
|
+
`presentation="inline"` to keep the legacy bare card. New exports: `ReviewModal`,
|
|
309
|
+
`ReviewModalHandle`, `ReviewModalProps`, `ReviewPresentation`, `resolvePresentation`,
|
|
310
|
+
`DEFAULT_PRESENTATION`, `REVIEW_MODAL_ANIM`, `entranceFrame`.
|
|
311
|
+
- Extracted the presentation default + entrance math into a pure, unit-tested
|
|
312
|
+
`src/reviews/presentation.ts` (`resolvePresentation`, `entranceFrame`), with tests
|
|
313
|
+
asserting the modal is the default and the scale/fade/backdrop interpolation and clamping.
|
|
314
|
+
|
|
315
|
+
### Fixed — pre-onboarding showcase button rendered in the wrong color
|
|
316
|
+
- **`FeatureShowcase` primary button now uses the brand accent, not the screen
|
|
317
|
+
background.** The underlying `@blazejkustra/react-native-onboarding` package paints
|
|
318
|
+
its primary button (plus the intro highlight word and the image hero panel) from
|
|
319
|
+
`background.primary` — its BRAND/action color, despite the "primary" name. The kit
|
|
320
|
+
was mapping `background.primary` to the theme's `background`, so every consuming app
|
|
321
|
+
rendered the "Next/Get Started" CTA in its own screen background color: white-on-white
|
|
322
|
+
in light mode, dark-on-dark in dark mode (reported in Morrow Self + Myelino, both on
|
|
323
|
+
0.3.0). The CTA now maps to the theme's `primary` (or the `accentColor` override), and
|
|
324
|
+
the screen/panel background maps to `background`. No app change is required to pick up
|
|
325
|
+
the fix — apps already pass their theme/`accentColor` correctly.
|
|
326
|
+
- Extracted the theme→package color mapping into a pure, unit-tested
|
|
327
|
+
`showcaseColorsFromTheme(theme, accent)` (new `src/showcase/showcaseColors.ts`) with a
|
|
328
|
+
regression test asserting the primary button is the accent and never the background.
|
|
329
|
+
|
|
330
|
+
## [0.3.0] — Unreleased
|
|
331
|
+
|
|
332
|
+
> Pending release. `0.2.1` is already published on npm (coachmarks + showcase, no
|
|
333
|
+
> identity API), so this identity surface — alongside the unpublished `/reviews`
|
|
334
|
+
> module and `selectShowcaseSlides` — ships in `0.3.0`.
|
|
335
|
+
|
|
336
|
+
### Added — first-class user identity (`userId`) with late binding
|
|
337
|
+
- **`userId` prop** on `<WireOnboarding>`: your own opaque, pseudonymous user id (NOT
|
|
338
|
+
an email/name/phone) so onboarding sessions reconcile to real users later. Trimmed and
|
|
339
|
+
capped at 128 chars; old servers ignore the field (backward compatible). Binds at three
|
|
340
|
+
moments: known at mount (rides the A2A session-start metadata), the user registers
|
|
341
|
+
mid-session (the kit emits an `identify` client event that attaches the id to the live
|
|
342
|
+
session), or after the flow (the `identifyOnboarding` helper below).
|
|
343
|
+
- **`identifyOnboarding(opts)`**: post-flow / post-registration binding. Resolves the
|
|
344
|
+
session from an explicit `contextId` or the persisted `storage`, then posts an `identify`
|
|
345
|
+
event to `POST /v1/events`. Fire-and-forget, never throws; resolves `true` when it
|
|
346
|
+
dispatched, `false` when it could not (no user id, no server url, no resolvable session).
|
|
347
|
+
- **`IdentifyOnboardingOptions`** type, **`sanitizeUserId(raw)`** (trim + 128-char cap,
|
|
348
|
+
returns `undefined` for blank/non-string), and **`USER_ID_MAX_LENGTH`** (128) exported.
|
|
349
|
+
- **`peekPersistedSession(storage, key)`**: read-only accessor that returns the persisted
|
|
350
|
+
`{ id, ts }` WITHOUT minting a fresh one (backs `identifyOnboarding`'s storage lookup).
|
|
351
|
+
- **`contextId` on the `started` / `resumed` `onEvent`**: the A2A session id is now surfaced
|
|
352
|
+
on those events so a host can capture it and bind a user AFTER completion (completion
|
|
353
|
+
clears the persisted session, so the captured id is the reliable post-flow handle).
|
|
354
|
+
Additive and non-breaking.
|
|
355
|
+
|
|
356
|
+
## [0.2.0] — 2026-07-09
|
|
357
|
+
|
|
358
|
+
### Added — coachmarks + feature showcase (two new subpath modules)
|
|
359
|
+
- **`wireai-onboarding/coachmarks`** — a performance-first guided-tour engine lifted
|
|
360
|
+
from a production app and generalized for the kit. Ships `CoachmarkProvider` (root
|
|
361
|
+
provider that injects a **synchronous** `CoachmarkStorage` gate + renders the overlay
|
|
362
|
+
host above the tab bar), `useCoachmarkAnchor` (measure-at-step anchor registry),
|
|
363
|
+
`useCoachmarkTour` (ordered queue with in-kit gating on `wire_coachmark_<tourId>_seen`,
|
|
364
|
+
cancellable anchor resolve, ref-held callbacks, one-overlay-at-a-time), the
|
|
365
|
+
`SpotlightOverlay` (animated `expo-blur` + pulsing ring + tooltip), a dependency-free
|
|
366
|
+
self-drawn `GestureHint` (UI-thread `react-native-reanimated`, Reduce-Motion fallback),
|
|
367
|
+
a global `isTestingCoachmark` QA-replay flag (`setCoachmarkTesting`), and the Phase-3
|
|
368
|
+
`selectTourSteps(catalog, selection?)` AI-selection seam. Ring/tooltip color derives
|
|
369
|
+
from the onboarding theme with an `accentColor` override.
|
|
370
|
+
- **`wireai-onboarding/showcase`** — `FeatureShowcase`, a themed wrapper over
|
|
371
|
+
`@blazejkustra/react-native-onboarding` for the pre-onboarding app intro. Maps a
|
|
372
|
+
declarative `ShowcaseConfig` to the package API, overlays the perf-tuned `GestureHint`
|
|
373
|
+
per slide, and gates once on `wire_showcase_<id>_seen` (testing flag bypasses).
|
|
374
|
+
|
|
375
|
+
### Packaging
|
|
376
|
+
- **Subpath exports** keep the main barrel dependency-free — `coachmarks`/`showcase` are
|
|
377
|
+
NOT re-exported from `src/index.ts`. Their optional peers
|
|
378
|
+
(`react-native-reanimated >=3`, `expo-blur >=13`, `@blazejkustra/react-native-onboarding >=1`)
|
|
379
|
+
are declared in `peerDependenciesMeta` as optional and load only when the subpath is used.
|
|
380
|
+
- tsup multi-entry build → `dist/coachmarks` + `dist/showcase` (ESM + CJS + `.d.ts`).
|
|
381
|
+
|
|
382
|
+
## [0.1.12] — 2026-07-08
|
|
383
|
+
|
|
384
|
+
### Fixed
|
|
385
|
+
- `withTimeout` now clears its race timer on settle — a won read no longer leaves a
|
|
386
|
+
≤1.5 s no-op timer holding the closure alive (`session/persistedSession.ts`).
|
|
387
|
+
|
|
388
|
+
### Docs
|
|
389
|
+
- Documented the dev-only StrictMode caveat on first-mint hydration: a spurious
|
|
390
|
+
`resumed: true` can surface in dev builds; production is unaffected and it self-heals
|
|
391
|
+
(`WireOnboarding.tsx`).
|
|
392
|
+
|
|
393
|
+
## [0.1.11] — 2026-07-08
|
|
394
|
+
|
|
395
|
+
### Added — session-id persistence (fixes the phantom-drop funnel inflation)
|
|
396
|
+
- New optional **`storage` prop** (AsyncStorage-compatible `getItem/setItem/removeItem`;
|
|
397
|
+
pass `@react-native-async-storage/async-storage` as-is or a small MMKV wrapper). When
|
|
398
|
+
set, the kit persists `{ id, ts }` of its session id — the correlation seed the backend
|
|
399
|
+
adopts as the A2A `contextId` — so an app **kill** mid-onboarding resumes the SAME
|
|
400
|
+
backend session on relaunch instead of minting a new one. Previously every relaunch
|
|
401
|
+
counted a second `session_started` and left the first session as a false "drop",
|
|
402
|
+
inflating the analytics funnel's `started` denominator. **No new dependency** — the
|
|
403
|
+
host injects the storage (the kit's first host-supplied-capability port since
|
|
404
|
+
`illustrations`). Persists the kit's own correlation seed ONLY; answer persistence
|
|
405
|
+
stays a host concern via `onComplete`.
|
|
406
|
+
- New `sessionTtlMs` prop (default 3 600 000 ms = the backend's session TTL) and
|
|
407
|
+
`persistKey` prop (default key `wireai:session:<appId>`).
|
|
408
|
+
- Hydration is a **loader-gate**: the provider subtree mounts only after the stored id
|
|
409
|
+
resolves (one timeout-capped storage read; a hung adapter degrades to a fresh mint,
|
|
410
|
+
never a stuck screen). New `restoringTitle`/`restoringHint` copy strings.
|
|
411
|
+
- On resume the kickoff sends the existing skip sentinel instead of `startMessage`
|
|
412
|
+
(whose literal text the server would record as the pending question's ANSWER); the
|
|
413
|
+
server logs a `screen_skipped` and advances — so a resumed session skips at most the
|
|
414
|
+
one question that was on screen at kill time. A session the server no longer knows
|
|
415
|
+
degrades cleanly to a fresh flow under the same id (still one funnel session).
|
|
416
|
+
- The persisted id is **cleared on completion** and deliberately kept on drop/degrade
|
|
417
|
+
(a dropped session stays resumable within the TTL).
|
|
418
|
+
|
|
419
|
+
### Changed — `OnboardingEvent` union grew (compile-time visible)
|
|
420
|
+
- New `{ type: "resumed" }` event, fired INSTEAD of `started` when a persisted session
|
|
421
|
+
was restored — host funnels must not count both as starts. `toAnalyticsEvent` maps it
|
|
422
|
+
to the new canonical `wire_onboarding_resumed`. Exhaustive `switch`es over
|
|
423
|
+
`OnboardingEvent` will now fail to compile until they handle `resumed` (intentional).
|
|
424
|
+
- Note for dashboards: resume-kickoffs add `screen_skipped` events, so the skip rate
|
|
425
|
+
now includes resume-skips.
|
|
426
|
+
|
|
427
|
+
## [0.1.10] — 2026-07-07
|
|
428
|
+
|
|
429
|
+
### Fixed — keyboard no longer covers the pinned CTA
|
|
430
|
+
- `CardLayout` now wraps its column in a `KeyboardAvoidingView` (`behavior="padding"`), so
|
|
431
|
+
the bottom-pinned Continue/Submit button rides above the soft keyboard on text questions
|
|
432
|
+
(`TextInputCard`). No new dependency — RN core only. On hosts whose window already resizes
|
|
433
|
+
for the keyboard the computed overlap is ~0, so the padding is a no-op.
|
|
434
|
+
- The card `ScrollView` gets `keyboardDismissMode="on-drag"` (scrolling dismisses the keyboard).
|
|
435
|
+
|
|
436
|
+
### Added — clearer text questions
|
|
437
|
+
- `TextInputCard` gains an optional `hint` prop, rendered as the subtitle line under the
|
|
438
|
+
question (one sentence of context: what the answer personalizes). The `placeholder` schema
|
|
439
|
+
description now demands a concrete "e.g. ..." example answer so the model reliably sends one.
|
|
440
|
+
Pairs with the backend prompt update (server `app/wire.py` + `app/prompt.py`, same date).
|
|
441
|
+
|
|
442
|
+
### Fixed — package.json cleanup
|
|
443
|
+
- Removed the stray `dependencies` block that self-depended on `wireai-onboarding@0.1.8` and
|
|
444
|
+
duplicated the `wireai-rn` peer as a hard dependency.
|
|
445
|
+
|
|
446
|
+
## [0.1.3] — 2026-06-27
|
|
447
|
+
|
|
448
|
+
### Changed — kit is the single fallback reporter
|
|
449
|
+
- The whole-flow degrade self-report now posts **`client_fallback`** (was `llm_fallback`),
|
|
450
|
+
so it lands on the backend's idempotent `client_fallback` ingest, which back-fills a
|
|
451
|
+
`session_started` for a session it never saw. Result: a device that drops straight to its
|
|
452
|
+
static manifest still shows as a counted fallback session, correlated to the real funnel
|
|
453
|
+
session when the SDK forwards `metadata.sessionId` (wireai-rn ≥ 0.2.1). `ClientEventType`
|
|
454
|
+
is now `screen_skipped | dropped | client_fallback`. **Hosts must NOT also report their own
|
|
455
|
+
fallback** — the kit owns this signal (previously apps direct-fetched it; remove that on upgrade).
|
|
456
|
+
|
|
457
|
+
## [0.1.8] — 2026-07-06
|
|
458
|
+
|
|
459
|
+
### Added — full-screen card layout redesign
|
|
460
|
+
- New `CardLayout` / `AnimatedCard` / `OnboardingScaffold`: every card gets a full-screen
|
|
461
|
+
layout with a pinned CTA, safe-area handling, and a per-step enter transition (all six
|
|
462
|
+
cards + the completion view route through it).
|
|
463
|
+
- `TextInputCard` renders a tall multi-line box by default for open-ended answers (new
|
|
464
|
+
optional `multiline`/`lines` props; set `multiline:false` for a single-line field like a name).
|
|
465
|
+
|
|
466
|
+
### Changed — new required peer dependency
|
|
467
|
+
- Adds **`react-native-safe-area-context` (>=4.0.0)** as a required peer (used by
|
|
468
|
+
`OnboardingScaffold` for insets). The kit is no longer strictly pure-JS; the host must wrap
|
|
469
|
+
its root in a `SafeAreaProvider` (standard in every Expo/RN app, and both current consumers
|
|
470
|
+
already do).
|
|
471
|
+
|
|
472
|
+
### Security
|
|
473
|
+
- `InterstitialCard.imageUrl` is now validated as an `https://` URL (previously any string
|
|
474
|
+
was passed to `<Image>`); a non-https or malformed value is rejected and degrades to the
|
|
475
|
+
named-illustration fallback.
|
|
476
|
+
|
|
477
|
+
### Added — prefetch on select (fixes "the first AI question is always the slowest") (2026-07-05)
|
|
478
|
+
- **Choice cards now warm the next turn while the user is still picking.** Q1 is a deterministic
|
|
479
|
+
base card (instant); Q2 is the first LLM turn and neither backend cache protects it (the
|
|
480
|
+
flow-cache misses a novel answer combo, the provider prompt-cache is cold on the first call),
|
|
481
|
+
so it was always the perceptibly-slowest screen. `SelectionCard` and `ChipSelectCard` now emit
|
|
482
|
+
their live selection via a new optional `onSelectionChange` prop; `<WireOnboarding>` injects a
|
|
483
|
+
handler that fires a trailing-edge debounced (300ms) `message/preview` keyed on the EXACT text
|
|
484
|
+
the Continue tap will send (`I selected: ${JSON.stringify(value)}`). The backend (with
|
|
485
|
+
`WIREAI_PREFETCH_ENABLED`) generates Q2 during the pick→Continue window, so the real tap hits a
|
|
486
|
+
warm cache and returns instantly. Fire-and-forget: if the user changes the selection after the
|
|
487
|
+
last preview, the real turn just generates normally (never worse than before). Requires the
|
|
488
|
+
backend prefetch endpoint (already shipped) enabled.
|
|
489
|
+
|
|
490
|
+
### Added — reusable integration substrate (2026-06-27)
|
|
491
|
+
- **`isOnboardingEnabled({ remote })`** — one standard gate (transport present + env flag + optional remote-config boolean the app passes in). Replaces per-app `isWireBackendConfigured`.
|
|
492
|
+
- **`WIRE_ONBOARDING_EVENTS` + `toAnalyticsEvent(event)`** — canonical `wire_onboarding_*` funnel names so every app logs the same events through its own logger.
|
|
493
|
+
- **`attributionMetadata(a)`** — shape install/ad attribution into `config.metadata` (forwarded into every A2A request).
|
|
494
|
+
- **`reportClientEvent` / `reportClientEvents` / `makeSessionId`** — fire-and-forget client reporting of device-only funnel events (`dropped`) to `POST /v1/events`; `<WireOnboarding>` emits `dropped` on unmount-without-complete. (`screen_skipped` is server-derived; not double-sent.) NOTE: full session correlation requires `wireai-rn@>=0.2.1`, which forwards `metadata.sessionId` so the server adopts it as the A2A `contextId` (0.2.0 dropped metadata at runtime; fixed in 0.2.1).
|
|
495
|
+
|
|
496
|
+
### Changed — licensing & distribution (2026-06-27)
|
|
497
|
+
- **Open-sourced**: `license` → MIT (was UNLICENSED), `private` → false, added `repository`/`homepage`/`bugs`/`keywords`/`publishConfig`. Primary install channel is now **npm** (`npm install wireai-onboarding`). README/AGENTS install steps updated off `git+ssh`.
|
|
498
|
+
|
|
499
|
+
### Added — distribution & glue
|
|
500
|
+
- **Build output** — `tsup` now compiles `src/index.ts` → `dist/` (ESM `index.mjs`
|
|
501
|
+
+ CJS `index.js` + `.d.ts`/`.d.mts`), es2019, peers
|
|
502
|
+
(`react`/`react-native`/`wireai-rn`/`zod`) externalized. `package.json` gains an
|
|
503
|
+
`"exports"` map; top-level `main`/`module`/`types` point at `dist` for tooling,
|
|
504
|
+
while a `"react-native"` export condition keeps RN/Metro consumers resolving to
|
|
505
|
+
`src/index.ts`. External apps can now `npm install` a tarball / `git+ssh` build;
|
|
506
|
+
the monorepo apps still consume from source. Scripts: `build`, `prepack`,
|
|
507
|
+
`pack:tarball`. (Still `"private": true` — closed-source.)
|
|
508
|
+
- **`wireai-onboarding/metro` helper** — `withWireOnboarding(config, options)`
|
|
509
|
+
folds the per-app ~25-line Metro block (pin single-instance deps + map the
|
|
510
|
+
specifier + watch source) into one non-destructive call. `options.source` switches
|
|
511
|
+
to source consumption; `options.appRoot` defaults to `process.cwd()`.
|
|
512
|
+
- **`themeFromBrand({ primary, … })`** — derives a full `Partial<OnboardingTheme>`
|
|
513
|
+
(tinted soft wash + on-primary + optional background/font) from a brand color over
|
|
514
|
+
the neutral default, via `mergeTheme`.
|
|
515
|
+
- **`wireConfigFromEnv(overrides?)`** — builds a `WireOnboardingConfig` from
|
|
516
|
+
`EXPO_PUBLIC_WIREAI_API_KEY` / `EXPO_PUBLIC_WIREAI_SERVER_URL` (+ appId), returning
|
|
517
|
+
`null` when keys are missing so apps stop re-authoring env gating.
|
|
518
|
+
- **`defaultIllustrations`** — a dependency-free fallback registry (`momentum`,
|
|
519
|
+
`before-after`) of themed `<View>`/`<Text>` glyphs (no SVG/Lottie), so an app can
|
|
520
|
+
render `InterstitialCard` with zero illustration code. Spread under your own art to
|
|
521
|
+
override by name.
|
|
522
|
+
|
|
523
|
+
### Added
|
|
524
|
+
- **`InterstitialCard`** — a mid-flow momentum/value screen (Duolingo/Cal-AI):
|
|
525
|
+
illustration + a line that reflects the user's answers back + a Continue tap.
|
|
526
|
+
Registered in `onboardingComponents`. (Requires the matching server-side entry
|
|
527
|
+
in `KNOWN_COMPONENTS` + the tenant's `allowed_components`.) The backend can now
|
|
528
|
+
carry **multiple** images per app (managed in the dashboard); the AI picks one by
|
|
529
|
+
name and the server resolves it to `imageUrl`, so the card renders it with no
|
|
530
|
+
app-side registry node needed.
|
|
531
|
+
- **`approxScreens`** prop on `<WireOnboarding>` — when the host knows roughly how
|
|
532
|
+
many screens the flow runs, the progress bar paces as `step / approxScreens`
|
|
533
|
+
(capped, still no visible number) instead of the asymptotic default. A
|
|
534
|
+
backend-supplied `progress.total` (the screen budget, returned on every turn
|
|
535
|
+
incl. init) takes precedence, so apps usually don't need to pass this at all.
|
|
536
|
+
- **Illustration registry** — `IllustrationProvider` / `useIllustration` + the
|
|
537
|
+
`illustrations` prop on `<WireOnboarding>` (`name → ReactNode`). Apps bring brand
|
|
538
|
+
SVG/Lottie/Image; the kit stays dependency-free. Falls back to a backend
|
|
539
|
+
`imageUrl` via RN `<Image>` when no registry match.
|
|
540
|
+
- **`onError`** prop — fires on backend error/timeout so the host can own recovery
|
|
541
|
+
(e.g. fall back to a static onboarding). Without it, the kit shows an inline retry.
|
|
542
|
+
- **`onEvent`** prop — `started` / `turn` / `error` lifecycle events for host-side
|
|
543
|
+
analytics (the kit owns the thread loop).
|
|
544
|
+
- **`copy`** prop — localize the kit's built-in English loader/completion strings.
|
|
545
|
+
- `README.md` documenting the architecture, props, illustrations, and the
|
|
546
|
+
consume-from-source (Metro + tsconfig) setup.
|
|
547
|
+
|
|
548
|
+
### Notes
|
|
549
|
+
- `DoneBlock` is no longer used internally (the terminal screen is `CompletionView`);
|
|
550
|
+
the export is kept for back-compat and marked `@deprecated`.
|
|
551
|
+
- The countless asymptotic `StepProgress`, the sparkle `LoadingBlock`, and the
|
|
552
|
+
animated-checkmark `CompletionView` (CTA-gated `onComplete`) are unchanged.
|
|
553
|
+
|
|
554
|
+
## [0.1.2]
|
|
555
|
+
|
|
556
|
+
- **Static env reads** — `wireConfigFromEnv` / `isOnboardingEnabled` read each `EXPO_PUBLIC_*` via STATIC member access. Expo`s inline-env-vars only inlines static `process.env.EXPO_PUBLIC_*`; aliased/computed access was `undefined` on device (fine in Jest). This was the root cause onboarding did not show on a real device.
|
|
557
|
+
- **Gate on transport presence** — removed the `EXPO_PUBLIC_AI_ONBOARDING_ENABLED` flag. `isOnboardingEnabled` keys off apiKey+serverUrl presence, with an optional `remote` kill-switch. The `envFlagVar` option is gone.
|
|
558
|
+
- **Fallback reported to backend** — on degrade to the static fallback, the kit posts `llm_fallback` to `/v1/events` (tagged via `meta`), so the dashboard fallback-rate counts whole-flow degrades, not just single-turn ones.
|
|
559
|
+
|
|
560
|
+
## [0.1.1]
|
|
561
|
+
|
|
562
|
+
- **exports**: the `"."` `react-native` condition now resolves TYPES to the shipped
|
|
563
|
+
`dist/index.d.ts` (consumers drop the tsconfig workaround that pointed
|
|
564
|
+
`wireai-onboarding` at `dist`), while Metro still loads `./src/index.ts` at runtime.
|
|
565
|
+
- **zod** moved from `devDependencies` to `peerDependencies` (`>=3`) — the consuming app
|
|
566
|
+
provides zod; the kit only references it for types.
|
|
567
|
+
|
|
568
|
+
## [0.1.0]
|
|
569
|
+
|
|
570
|
+
- Initial kit: `<WireOnboarding>` host over `wireai-rn`, theme system, themed cards
|
|
571
|
+
(`TextInputCard`, `SelectionCard`, `ChipSelectCard`, `NumberStepperCard`,
|
|
572
|
+
`StatusCard`), per-step validators, scaffold + progress + loading/error/completion.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Claude integration prompt — wireai-onboarding
|
|
2
|
+
|
|
3
|
+
Copy everything in the block below and paste it into Claude Code (or any coding agent) **from the
|
|
4
|
+
root of your React Native / Expo app**. Fill in the two values at the top first.
|
|
5
|
+
|
|
6
|
+
> Fastest path instead of this prompt: run the **`wire-rn-integration`** Claude skill — same job,
|
|
7
|
+
> fully automated. Use this prompt when you don't have that skill installed.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
You are integrating the `wireai-onboarding` package into THIS React Native / Expo app — a drop-in
|
|
13
|
+
AI onboarding flow. Do it end to end, match my app's existing conventions, and leave it
|
|
14
|
+
type-checking green. Read the package's README.md, llms.txt, and AGENTS.md (in node_modules/wireai-onboarding
|
|
15
|
+
after install) before writing code.
|
|
16
|
+
|
|
17
|
+
MY VALUES:
|
|
18
|
+
- WIREAI_SERVER_URL: <paste the backend URL, e.g. https://...fly.dev>
|
|
19
|
+
- WIREAI_API_KEY: <paste the wai_… app key from the getwireai console / register_<app>.py>
|
|
20
|
+
- APP_ID: <paste my app id, e.g. my-app>
|
|
21
|
+
|
|
22
|
+
STEPS (do them in order, stop and ask if a convention is ambiguous):
|
|
23
|
+
1. Detect my conventions: navigation lib + where the signup/first-run flow lives, my env-var
|
|
24
|
+
typing, my profile-update / persistence path, my theme tokens (colors/fonts), my path aliases.
|
|
25
|
+
2. Install: `wireai-onboarding` and `wireai-rn` (react + react-native are already peers). Use my
|
|
26
|
+
package manager (yarn/npm/pnpm — detect it).
|
|
27
|
+
3. Metro: set `module.exports = withWireOnboarding(getDefaultConfig(__dirname))` from
|
|
28
|
+
`wireai-onboarding/metro` in metro.config.js (preserve my existing config). This pins one copy
|
|
29
|
+
of react/react-native/wireai-rn/zod — do NOT skip it.
|
|
30
|
+
4. Env: add EXPO_PUBLIC_WIREAI_API_KEY, EXPO_PUBLIC_WIREAI_SERVER_URL, EXPO_PUBLIC_WIREAI_APP_ID
|
|
31
|
+
to my env files and my env typing.
|
|
32
|
+
5. Theme: build a theme from my brand with `themeFromBrand({ primary: "<my brand color>" })` or a
|
|
33
|
+
Partial<OnboardingTheme>; match my dark/light.
|
|
34
|
+
6. Screen: scaffold an onboarding screen that renders
|
|
35
|
+
`<WireOnboarding config={wireConfigFromEnv({ appId: APP_ID })} theme={...} illustrations={...}
|
|
36
|
+
onComplete={persist} fallbackFlow={<MyExistingStaticOnboarding/>} onEvent={logEvent} />`.
|
|
37
|
+
- Gate entry with `isOnboardingEnabled({ remote })` (wire my remote-config boolean if I have one).
|
|
38
|
+
- `persist`: write ALL of `result.answers` (use `deriveAnswers(result.raw)` to fill gaps) through
|
|
39
|
+
my normal profile-update path. Do NOT re-ask anything a dedicated screen already owns.
|
|
40
|
+
- `fallbackFlow` MUST be my existing static onboarding so a backend error/timeout degrades, never
|
|
41
|
+
dead-ends.
|
|
42
|
+
- Forward install attribution if I have it: `wireConfigFromEnv({ appId, metadata: attributionMetadata({...}) })`.
|
|
43
|
+
- Analytics: log via `toAnalyticsEvent(e)` → my logger, using the canonical `wire_onboarding_*` names.
|
|
44
|
+
7. Navigation: mount the screen in my first-run/signup flow; branch to the static flow when the gate
|
|
45
|
+
is off or config is missing.
|
|
46
|
+
8. Verify: type-check (and lint the changed files); test the flag-off + backend-error paths.
|
|
47
|
+
|
|
48
|
+
Report back: the files you changed (path:line), the typecheck result, and anything you couldn't
|
|
49
|
+
infer about my conventions.
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
After it runs: set the two secrets in your env, run `expo start -c` once (clears the Metro cache
|
|
55
|
+
after the config change), and you're live.
|