@wireai/activation 0.14.2 → 0.15.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 +1 -1
- package/CHANGELOG.md +144 -0
- package/README.md +23 -9
- package/dist/analytics/index.d.mts +5 -5
- package/dist/analytics/index.d.ts +5 -5
- package/dist/analytics/index.js.map +1 -1
- package/dist/analytics/index.mjs.map +1 -1
- package/dist/coachmarks/index.d.mts +13 -5
- package/dist/coachmarks/index.d.ts +13 -5
- package/dist/coachmarks/index.js +252 -58
- package/dist/coachmarks/index.js.map +1 -1
- package/dist/coachmarks/index.mjs +204 -9
- package/dist/coachmarks/index.mjs.map +1 -1
- package/dist/{currentSession-CW_5Mq4O.d.ts → currentSession-CFSRZ2wg.d.ts} +10 -4
- package/dist/{currentSession-CUvTOchb.d.mts → currentSession-orZy5p1e.d.mts} +10 -4
- package/dist/{decision-Bgo17oH7.d.mts → decision-3vWLuBlO.d.ts} +11 -2
- package/dist/{decision-Bkh_LigV.d.ts → decision-yBj2AyPW.d.mts} +11 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/questionnaire/index.d.mts +3 -3
- package/dist/questionnaire/index.d.ts +3 -3
- package/dist/questionnaire/index.js +48 -3
- package/dist/questionnaire/index.js.map +1 -1
- package/dist/questionnaire/index.mjs +48 -3
- package/dist/questionnaire/index.mjs.map +1 -1
- package/dist/reviews/index.d.mts +10 -6
- package/dist/reviews/index.d.ts +10 -6
- package/dist/reviews/index.js +167 -18
- package/dist/reviews/index.js.map +1 -1
- package/dist/reviews/index.mjs +167 -18
- package/dist/reviews/index.mjs.map +1 -1
- package/dist/showcase/index.d.mts +1 -1
- package/dist/showcase/index.d.ts +1 -1
- package/dist/showcase/index.js +282 -48
- package/dist/showcase/index.js.map +1 -1
- package/dist/showcase/index.mjs +248 -12
- package/dist/showcase/index.mjs.map +1 -1
- package/dist/{transport-j5gFfJhK.d.mts → transport-s5QxA-ci.d.mts} +18 -13
- package/dist/{transport-B_0SgCBe.d.ts → transport-xqqSFqxs.d.ts} +18 -13
- package/dist/{types-Cju-1_jT.d.mts → types-Byx306Kv.d.mts} +25 -10
- package/dist/{types-BcmagF6K.d.mts → types-D_0B0yay.d.mts} +7 -2
- package/dist/{types-BcmagF6K.d.ts → types-D_0B0yay.d.ts} +7 -2
- package/dist/{types-h2BZvl1t.d.ts → types-tdATL5z0.d.ts} +25 -10
- package/metro/index.js +16 -5
- package/package.json +1 -1
- package/src/WireOnboarding.tsx +4 -1
- package/src/coachmarks/GestureHint.tsx +16 -7
- package/src/coachmarks/SpotlightOverlay.tsx +19 -13
- package/src/coachmarks/index.ts +7 -2
- package/src/coachmarks/reanimated.ts +342 -0
- package/src/coachmarks/runtime.ts +103 -2
- package/src/coachmarks/types.ts +7 -2
- package/src/context/deviceId.ts +32 -0
- package/src/reviews/ReviewGate.tsx +86 -37
- package/src/reviews/decision.ts +11 -1
- package/src/reviews/idempotency.ts +71 -0
- package/src/reviews/index.ts +4 -2
- package/src/reviews/runtime.ts +5 -3
- package/src/reviews/transport.ts +17 -12
- package/src/reviews/types.ts +7 -0
- package/src/showcase/FeatureShowcase.tsx +25 -9
- package/src/showcase/blazejOnboarding.ts +136 -0
- package/src/showcase/index.ts +8 -4
- package/src/types.ts +10 -4
- package/src/utils/submitResult.ts +17 -9
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { k as ReviewTarget, S as SubmitResult, g as ReviewDecisionResponse, j as ReviewSubmission } from './types-
|
|
1
|
+
import { k as ReviewTarget, S as SubmitResult, g as ReviewDecisionResponse, j as ReviewSubmission } from './types-tdATL5z0.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* What became of a review POST — the SHARED three-outcome verdict, re-exported under the name the
|
|
@@ -25,23 +25,28 @@ type ReviewSubmitResult = SubmitResult;
|
|
|
25
25
|
*
|
|
26
26
|
* It was, for exactly one unpublished release, and the boolean was the bug. `false` meant both
|
|
27
27
|
* "nothing reached the server" and "the server answered non-2xx", and the one caller that reads
|
|
28
|
-
* this (`ReviewGate.postOnce`) treats `false` as "still owed" and re-posts.
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
28
|
+
* this (`ReviewGate.postOnce`) treats `false` as "still owed" and re-posts. At the time there was
|
|
29
|
+
* no idempotency key on the wire at all, so a 502 returned AFTER the insert commits meant the
|
|
30
|
+
* re-post wrote a SECOND row, double-counting `count` and corrupting `avg` — the precise corruption
|
|
31
|
+
* the one-row latch exists to prevent. A response of any status proves the server was reached, and
|
|
32
|
+
* that is a different question from whether it liked the row. So the two are different values, and
|
|
33
|
+
* they stay different: the split is what lets the caller decide, and the retry-on-`rejected`
|
|
34
|
+
* question is a recovery-budget decision the key does not answer on its own.
|
|
34
35
|
*
|
|
35
36
|
* ── THE RESIDUAL, STATED HONESTLY ────────────────────────────────────────────────────────────
|
|
36
37
|
*
|
|
37
38
|
* `unsent` is not proof the server never got the row. A connection dropped after the request was
|
|
38
39
|
* written — or after the row committed — surfaces as a thrown/rejected `fetch` here, exactly like
|
|
39
|
-
* an offline device.
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
40
|
+
* an offline device. That used to make the `unsent` re-post SAFER rather than SAFE, and it is what
|
|
41
|
+
* the client-minted idempotency key now covers: `ReviewSubmission.idempotency_key` reaches
|
|
42
|
+
* `CreateReviewRequest`, and the server upserts the derived row rather than inserting a second one.
|
|
43
|
+
*
|
|
44
|
+
* IT IS A CAPABILITY, NOT A GUARANTEE, and the condition is not ours to enforce from in here. The
|
|
45
|
+
* server honours the key only when the post also carries a unit it can scope it to —
|
|
46
|
+
* `meta.device_key`, else `session_id` — and ignores it outright otherwise. `ReviewGate` fills the
|
|
47
|
+
* device key in for exactly this reason, but this function posts whatever body it is handed, so a
|
|
48
|
+
* caller that assembles its own body without a unit is back in the old world where a lost detractor
|
|
49
|
+
* is a gap in the data and a duplicated one is a lie in the data.
|
|
45
50
|
*/
|
|
46
51
|
declare const submitReview: (target: ReviewTarget | undefined, review: ReviewSubmission) => Promise<ReviewSubmitResult>;
|
|
47
52
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { O as OnboardingTheme } from './types-BKfpdZzX.mjs';
|
|
2
|
-
import { C as CoachmarkStorage } from './types-
|
|
2
|
+
import { C as CoachmarkStorage } from './types-D_0B0yay.mjs';
|
|
3
3
|
import { a as WireFeatures, b as WireFeaturesConfig } from './types-BpwiRpA8.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -25,20 +25,28 @@ import { a as WireFeatures, b as WireFeaturesConfig } from './types-BpwiRpA8.mjs
|
|
|
25
25
|
*
|
|
26
26
|
* `submitReview` was a boolean for exactly one unpublished release, and the boolean WAS the bug.
|
|
27
27
|
* `false` meant both "nothing reached the server" and "the server answered non-2xx", and the caller
|
|
28
|
-
* treats `false` as "still owed" and re-posts.
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
28
|
+
* treats `false` as "still owed" and re-posts. At the time neither body carried a client id, so
|
|
29
|
+
* there was NO idempotency key on the wire on either surface: a 502 returned AFTER the insert
|
|
30
|
+
* commits meant the re-post wrote a SECOND row. A response of any status proves the server was
|
|
31
|
+
* reached, and that is a different question from whether it liked the payload — which is why the
|
|
32
|
+
* three values stay, on both surfaces, whatever the wire gains.
|
|
33
33
|
*
|
|
34
34
|
* ── THE RESIDUAL, STATED HONESTLY ─────────────────────────────────────────────────────────────
|
|
35
35
|
*
|
|
36
36
|
* `unsent` is not proof the server never got it. A connection dropped after the request was written
|
|
37
37
|
* — or after the row committed — surfaces as a thrown/rejected `fetch`, exactly like an offline
|
|
38
|
-
* device.
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
38
|
+
* device. Closing that window needs a client-minted idempotency key the server upserts on, and AS
|
|
39
|
+
* OF 2026-08-19 THE TWO SURFACES DIFFER — do not read this paragraph as one fact about both:
|
|
40
|
+
*
|
|
41
|
+
* • REVIEWS — closed. `CreateReviewRequest` takes an optional `idempotency_key` and the server
|
|
42
|
+
* upserts a derived row on it, so a `ReviewGate` re-post merges instead of duplicating. It is a
|
|
43
|
+
* capability, not a guarantee: the server honours the key only when the post also carries a
|
|
44
|
+
* unit to scope it to (`meta.device_key`, else `session_id`), so a caller that strips both is
|
|
45
|
+
* back to the old behaviour.
|
|
46
|
+
* • QUESTIONNAIRES — still open, and still exactly as this paragraph used to read for both. The
|
|
47
|
+
* response body carries no client id and the server has no idempotency path for it at all, so
|
|
48
|
+
* retrying only on `unsent` there is SAFER, not SAFE. Prefer losing a row over inventing one: a
|
|
49
|
+
* lost detractor is a gap in the data, a duplicated one is a lie in the data.
|
|
42
50
|
*/
|
|
43
51
|
type SubmitResult = "accepted" | "rejected" | "unsent";
|
|
44
52
|
|
|
@@ -119,6 +127,13 @@ interface ReviewSubmission {
|
|
|
119
127
|
contact?: string;
|
|
120
128
|
session_id?: string;
|
|
121
129
|
source?: string;
|
|
130
|
+
/**
|
|
131
|
+
* OPTIONAL client-minted key for the two-phase submit: two posts carrying the same key upsert ONE
|
|
132
|
+
* row instead of creating two. The server honours it ONLY when the post also carries a resolvable
|
|
133
|
+
* user — `meta.device_key`, else `session_id` — and silently ignores it otherwise, so it always
|
|
134
|
+
* travels with one of those. One key per gate impression, stable across that impression's posts.
|
|
135
|
+
*/
|
|
136
|
+
idempotency_key?: string;
|
|
122
137
|
meta?: Record<string, unknown>;
|
|
123
138
|
}
|
|
124
139
|
/**
|
|
@@ -18,8 +18,13 @@ interface TargetRect {
|
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
20
|
* The host-injected gate storage. SYNCHRONOUS on purpose — a coachmark's
|
|
21
|
-
* "seen?" gate must resolve during render
|
|
22
|
-
*
|
|
21
|
+
* "seen?" gate must resolve during render. This is ENFORCED, fail closed: a
|
|
22
|
+
* probe on first read catches an adapter whose `getItem` returns a thenable
|
|
23
|
+
* (AsyncStorage handed straight in) and substitutes a closed adapter — every
|
|
24
|
+
* gate reads "seen", nothing shows, nothing fires, one dev warning names the
|
|
25
|
+
* fix (see `validateGateStorage` in ./runtime). A raw async adapter would
|
|
26
|
+
* otherwise report every gate unseen forever: tours replay each open, and a
|
|
27
|
+
* review gate with `minSessions` 0/1 re-fires each open. MMKV's
|
|
23
28
|
* `getString`/`set` fit as-is; an in-memory `Map` wrapper works for tests. This
|
|
24
29
|
* is deliberately separate from the async `WireOnboardingStorage` used for
|
|
25
30
|
* session persistence.
|
|
@@ -18,8 +18,13 @@ interface TargetRect {
|
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
20
|
* The host-injected gate storage. SYNCHRONOUS on purpose — a coachmark's
|
|
21
|
-
* "seen?" gate must resolve during render
|
|
22
|
-
*
|
|
21
|
+
* "seen?" gate must resolve during render. This is ENFORCED, fail closed: a
|
|
22
|
+
* probe on first read catches an adapter whose `getItem` returns a thenable
|
|
23
|
+
* (AsyncStorage handed straight in) and substitutes a closed adapter — every
|
|
24
|
+
* gate reads "seen", nothing shows, nothing fires, one dev warning names the
|
|
25
|
+
* fix (see `validateGateStorage` in ./runtime). A raw async adapter would
|
|
26
|
+
* otherwise report every gate unseen forever: tours replay each open, and a
|
|
27
|
+
* review gate with `minSessions` 0/1 re-fires each open. MMKV's
|
|
23
28
|
* `getString`/`set` fit as-is; an in-memory `Map` wrapper works for tests. This
|
|
24
29
|
* is deliberately separate from the async `WireOnboardingStorage` used for
|
|
25
30
|
* session persistence.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { O as OnboardingTheme } from './types-BKfpdZzX.js';
|
|
2
|
-
import { C as CoachmarkStorage } from './types-
|
|
2
|
+
import { C as CoachmarkStorage } from './types-D_0B0yay.js';
|
|
3
3
|
import { a as WireFeatures, b as WireFeaturesConfig } from './types-BpwiRpA8.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -25,20 +25,28 @@ import { a as WireFeatures, b as WireFeaturesConfig } from './types-BpwiRpA8.js'
|
|
|
25
25
|
*
|
|
26
26
|
* `submitReview` was a boolean for exactly one unpublished release, and the boolean WAS the bug.
|
|
27
27
|
* `false` meant both "nothing reached the server" and "the server answered non-2xx", and the caller
|
|
28
|
-
* treats `false` as "still owed" and re-posts.
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
28
|
+
* treats `false` as "still owed" and re-posts. At the time neither body carried a client id, so
|
|
29
|
+
* there was NO idempotency key on the wire on either surface: a 502 returned AFTER the insert
|
|
30
|
+
* commits meant the re-post wrote a SECOND row. A response of any status proves the server was
|
|
31
|
+
* reached, and that is a different question from whether it liked the payload — which is why the
|
|
32
|
+
* three values stay, on both surfaces, whatever the wire gains.
|
|
33
33
|
*
|
|
34
34
|
* ── THE RESIDUAL, STATED HONESTLY ─────────────────────────────────────────────────────────────
|
|
35
35
|
*
|
|
36
36
|
* `unsent` is not proof the server never got it. A connection dropped after the request was written
|
|
37
37
|
* — or after the row committed — surfaces as a thrown/rejected `fetch`, exactly like an offline
|
|
38
|
-
* device.
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
38
|
+
* device. Closing that window needs a client-minted idempotency key the server upserts on, and AS
|
|
39
|
+
* OF 2026-08-19 THE TWO SURFACES DIFFER — do not read this paragraph as one fact about both:
|
|
40
|
+
*
|
|
41
|
+
* • REVIEWS — closed. `CreateReviewRequest` takes an optional `idempotency_key` and the server
|
|
42
|
+
* upserts a derived row on it, so a `ReviewGate` re-post merges instead of duplicating. It is a
|
|
43
|
+
* capability, not a guarantee: the server honours the key only when the post also carries a
|
|
44
|
+
* unit to scope it to (`meta.device_key`, else `session_id`), so a caller that strips both is
|
|
45
|
+
* back to the old behaviour.
|
|
46
|
+
* • QUESTIONNAIRES — still open, and still exactly as this paragraph used to read for both. The
|
|
47
|
+
* response body carries no client id and the server has no idempotency path for it at all, so
|
|
48
|
+
* retrying only on `unsent` there is SAFER, not SAFE. Prefer losing a row over inventing one: a
|
|
49
|
+
* lost detractor is a gap in the data, a duplicated one is a lie in the data.
|
|
42
50
|
*/
|
|
43
51
|
type SubmitResult = "accepted" | "rejected" | "unsent";
|
|
44
52
|
|
|
@@ -119,6 +127,13 @@ interface ReviewSubmission {
|
|
|
119
127
|
contact?: string;
|
|
120
128
|
session_id?: string;
|
|
121
129
|
source?: string;
|
|
130
|
+
/**
|
|
131
|
+
* OPTIONAL client-minted key for the two-phase submit: two posts carrying the same key upsert ONE
|
|
132
|
+
* row instead of creating two. The server honours it ONLY when the post also carries a resolvable
|
|
133
|
+
* user — `meta.device_key`, else `session_id` — and silently ignores it otherwise, so it always
|
|
134
|
+
* travels with one of those. One key per gate impression, stable across that impression's posts.
|
|
135
|
+
*/
|
|
136
|
+
idempotency_key?: string;
|
|
122
137
|
meta?: Record<string, unknown>;
|
|
123
138
|
}
|
|
124
139
|
/**
|
package/metro/index.js
CHANGED
|
@@ -71,11 +71,16 @@ const SINGLE_INSTANCE_DEPS = ['react', 'react-native', 'wireai-rn', 'zod'];
|
|
|
71
71
|
* only thing holding it up.
|
|
72
72
|
*
|
|
73
73
|
* ONLY modules read through a guarded require/import belong here. A module the kit imports
|
|
74
|
-
* STATICALLY at module scope
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
74
|
+
* STATICALLY at module scope must NOT be stubbed: an empty module would turn a loud build-time
|
|
75
|
+
* "Unable to resolve module" into a silent `undefined` component that crashes at render, which is
|
|
76
|
+
* strictly worse. That exclusion used to name `react-native-reanimated` and
|
|
77
|
+
* `@blazejkustra/react-native-onboarding` — as of 0.14.3 both are guarded (`coachmarks/reanimated.ts`,
|
|
78
|
+
* `showcase/blazejOnboarding.ts`) and so they belong on the list, not in the exclusion. The kit now
|
|
79
|
+
* has NO statically-imported optional peer: every module it can live without is reached through a
|
|
80
|
+
* guard, and everything it imports statically (react, react-native, react-native-safe-area-context,
|
|
81
|
+
* wireai-rn, zod) is a REQUIRED peer whose absence must stay a hard build error.
|
|
82
|
+
* `test/canary/metroResolution.test.tsx` keeps this list in sync with the source by asking Metro
|
|
83
|
+
* which deps it marks optional.
|
|
79
84
|
*/
|
|
80
85
|
const OPTIONAL_MODULES = [
|
|
81
86
|
// src/icons/expoIcons.ts — icon families; absent → WireIcon renders null.
|
|
@@ -91,6 +96,12 @@ const OPTIONAL_MODULES = [
|
|
|
91
96
|
'expo-haptics',
|
|
92
97
|
// src/coachmarks/expoBlur.ts — the coachmark spotlight frost; absent → plain dimmed scrim.
|
|
93
98
|
'expo-blur',
|
|
99
|
+
// src/coachmarks/reanimated.ts — the coachmark/showcase animation engine; absent → the overlay
|
|
100
|
+
// and the gesture glyph render statically, at their resting frame.
|
|
101
|
+
'react-native-reanimated',
|
|
102
|
+
// src/showcase/blazejOnboarding.ts — the FeatureShowcase slide pager; absent → the showcase
|
|
103
|
+
// renders null and calls onDone, so the host's flow still advances.
|
|
104
|
+
'@blazejkustra/react-native-onboarding',
|
|
94
105
|
];
|
|
95
106
|
|
|
96
107
|
function withWireOnboarding(config, options) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wireai/activation",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Premium, fully-themable drop-in AI onboarding kit for React Native / Expo, on top of the open-source wireai-rn SDK.",
|
|
6
6
|
"author": "Malik Chohra <malik@getwireai.com>",
|
package/src/WireOnboarding.tsx
CHANGED
|
@@ -135,7 +135,10 @@ export const WireOnboarding: React.FC<WireOnboardingProps> = ({
|
|
|
135
135
|
// per-install `device_key` the analytics surfaces auto-mint and persist — so the default wiring
|
|
136
136
|
// produces a joined funnel instead of a silent zero. Three conditions, all necessary:
|
|
137
137
|
//
|
|
138
|
-
// • `missingJoinKey` — a host-supplied key ALWAYS wins, verbatim, and is never touched.
|
|
138
|
+
// • `missingJoinKey` — a host-supplied key ALWAYS wins, verbatim, and is never touched. What
|
|
139
|
+
// COUNTS as supplied is `resolveIdentity`'s predicate above: a string with at least one
|
|
140
|
+
// non-whitespace character. A non-string or an all-whitespace `device_key` yields no record,
|
|
141
|
+
// so it reads as absent here and the auto key is merged over it.
|
|
139
142
|
// • `autoJoinKey !== false` — the documented opt-out for a host that genuinely wants an
|
|
140
143
|
// unlinked onboarding session; opting out restores the pre-0.12.2 behavior exactly.
|
|
141
144
|
// • `storage` that actually WORKS — without persistence the auto id is process-scoped, so every
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import React, { useEffect, useMemo, type ReactNode } from "react";
|
|
2
2
|
import { StyleSheet, View } from "react-native";
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
Animated,
|
|
4
6
|
useAnimatedStyle,
|
|
5
7
|
useReducedMotion,
|
|
6
8
|
useSharedValue,
|
|
@@ -8,15 +10,22 @@ import Animated, {
|
|
|
8
10
|
withRepeat,
|
|
9
11
|
withSequence,
|
|
10
12
|
withTiming,
|
|
11
|
-
} from "
|
|
12
|
-
|
|
13
|
+
} from "./reanimated";
|
|
13
14
|
import type { GestureKind } from "./types";
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
|
-
* Abstract gesture pictogram shown beside a coachmark tooltip.
|
|
17
|
-
* every animation runs on the UI thread
|
|
18
|
-
* setState, so it costs nothing on the JS
|
|
19
|
-
* dropping the motion loop and rendering the
|
|
17
|
+
* Abstract gesture pictogram shown beside a coachmark tooltip. With the OPTIONAL
|
|
18
|
+
* `react-native-reanimated` peer installed every animation runs on the UI thread
|
|
19
|
+
* (shared values + worklets), never setState, so it costs nothing on the JS
|
|
20
|
+
* thread. Honors Reduce Motion by dropping the motion loop and rendering the
|
|
21
|
+
* glyph static.
|
|
22
|
+
*
|
|
23
|
+
* The peer is reached through the guarded lazy require in `reanimated.ts`, never
|
|
24
|
+
* a static import, so `@wireai/activation/coachmarks` (and `/showcase`, which
|
|
25
|
+
* reaches this component through FeatureShowcase) builds on a host that never
|
|
26
|
+
* installed it. Absent, the glyph renders STATICALLY at its resting frame — the
|
|
27
|
+
* same capsules, ring, dot and motion track, at full opacity and no travel, with
|
|
28
|
+
* one render instead of a UI-thread loop. The pictogram never disappears.
|
|
20
29
|
*
|
|
21
30
|
* NO drawn-hand anatomy. Swipes render the standard two-finger swipe glyph: two
|
|
22
31
|
* rounded vertical capsules (an abstract index + middle finger, slightly
|
|
@@ -6,7 +6,12 @@ import {
|
|
|
6
6
|
useWindowDimensions,
|
|
7
7
|
View,
|
|
8
8
|
} from "react-native";
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
import { useOnboardingTheme } from "../theme/ThemeContext";
|
|
11
|
+
import { resolveBlurView } from "./expoBlur";
|
|
12
|
+
import { GestureHint } from "./GestureHint";
|
|
13
|
+
import {
|
|
14
|
+
Animated,
|
|
10
15
|
FadeIn,
|
|
11
16
|
FadeOut,
|
|
12
17
|
useAnimatedProps,
|
|
@@ -16,11 +21,7 @@ import Animated, {
|
|
|
16
21
|
withRepeat,
|
|
17
22
|
withSequence,
|
|
18
23
|
withTiming,
|
|
19
|
-
} from "
|
|
20
|
-
|
|
21
|
-
import { useOnboardingTheme } from "../theme/ThemeContext";
|
|
22
|
-
import { resolveBlurView } from "./expoBlur";
|
|
23
|
-
import { GestureHint } from "./GestureHint";
|
|
24
|
+
} from "./reanimated";
|
|
24
25
|
import type { GestureKind, Placement, TargetRect } from "./types";
|
|
25
26
|
|
|
26
27
|
const BLUR_INTENSITY = 26;
|
|
@@ -55,14 +56,19 @@ export interface SpotlightOverlayProps {
|
|
|
55
56
|
* root (above the tab bar) via CoachmarkOverlayHost.
|
|
56
57
|
*
|
|
57
58
|
* Performance: one BlurView (mounted only while a step is visible), the ring
|
|
58
|
-
* glow is a single
|
|
59
|
-
* Reduce Motion drops the pulse.
|
|
59
|
+
* glow is a single animated view, and with `react-native-reanimated` installed
|
|
60
|
+
* every animation runs on the UI thread. Reduce Motion drops the pulse.
|
|
60
61
|
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
62
|
+
* BOTH native peers are OPTIONAL, and each is resolved through a guarded lazy
|
|
63
|
+
* require, so the subpath never carries a static top-level import of either:
|
|
64
|
+
* • `expo-blur` (`expoBlur.ts`) — absent, the frost drops to an equivalent
|
|
65
|
+
* dimmed scrim. The animated component is built once, on first use.
|
|
66
|
+
* • `react-native-reanimated` (`reanimated.ts`) — absent, the overlay renders
|
|
67
|
+
* STATICALLY at its resting frame: the ring sits at scale 1 (no pulse), the
|
|
68
|
+
* blur at full BLUR_INTENSITY (no fade-in), and the whole overlay appears
|
|
69
|
+
* and leaves without the fade. Nothing moves; everything renders.
|
|
70
|
+
* A missing decoration must never blank a tour, so the ring, tooltip, copy and
|
|
71
|
+
* gestures are identical in all four combinations.
|
|
66
72
|
*/
|
|
67
73
|
const SpotlightOverlayComponent: React.FC<SpotlightOverlayProps> = ({
|
|
68
74
|
message,
|
package/src/coachmarks/index.ts
CHANGED
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
* @wireai/activation/coachmarks — the performance-first guided-tour engine.
|
|
3
3
|
*
|
|
4
4
|
* Subpath entry, kept OUT of the main barrel so the core kit stays dependency-
|
|
5
|
-
* free
|
|
6
|
-
* `expo-blur
|
|
5
|
+
* free. Importing this pulls in NO native peer: `react-native-reanimated` and
|
|
6
|
+
* `expo-blur` are both OPTIONAL and both reached through guarded lazy requires
|
|
7
|
+
* (`reanimated.ts`, `expoBlur.ts`). Installed, you get UI-thread animation and a
|
|
8
|
+
* frosted spotlight; absent, the tour still renders — statically, and with a
|
|
9
|
+
* plain dimmed scrim instead of the frost. (This doc used to say the subpath
|
|
10
|
+
* "pulls in" both peers, which was true until 0.14.2 / 0.14.3 removed the static
|
|
11
|
+
* imports.) The app declares WHERE things anchor (useCoachmarkAnchor) and
|
|
7
12
|
* WHICH tour plays (useCoachmarkTour); the kit owns the animation, measuring,
|
|
8
13
|
* blur, ring, gesture hand, and one-overlay-at-a-time queue.
|
|
9
14
|
*
|