@wireai/activation 0.14.3 → 0.16.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 +96 -21
- package/CHANGELOG.md +780 -0
- package/INTEGRATION_PROMPT.md +61 -23
- package/README.md +110 -31
- package/dist/analytics/index.d.mts +35 -13
- package/dist/analytics/index.d.ts +35 -13
- package/dist/analytics/index.js +288 -127
- package/dist/analytics/index.js.map +1 -1
- package/dist/analytics/index.mjs +288 -127
- package/dist/analytics/index.mjs.map +1 -1
- package/dist/coachmarks/index.d.mts +15 -1
- package/dist/coachmarks/index.d.ts +15 -1
- package/dist/coachmarks/index.js +120 -22
- package/dist/coachmarks/index.js.map +1 -1
- package/dist/coachmarks/index.mjs +120 -22
- package/dist/coachmarks/index.mjs.map +1 -1
- package/dist/{currentSession-CUvTOchb.d.mts → currentSession-Bz7G6lno.d.mts} +35 -39
- package/dist/{currentSession-CW_5Mq4O.d.ts → currentSession-z-CZ55ad.d.ts} +35 -39
- 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 +5 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.js +125 -36
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +125 -36
- package/dist/index.mjs.map +1 -1
- package/dist/questionnaire/index.d.mts +3 -16
- package/dist/questionnaire/index.d.ts +3 -16
- package/dist/questionnaire/index.js +202 -46
- package/dist/questionnaire/index.js.map +1 -1
- package/dist/questionnaire/index.mjs +203 -47
- 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 +269 -52
- package/dist/reviews/index.js.map +1 -1
- package/dist/reviews/index.mjs +270 -53
- 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 +106 -20
- package/dist/showcase/index.js.map +1 -1
- package/dist/showcase/index.mjs +107 -21
- 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/llms.txt +9 -9
- package/package.json +6 -9
- package/src/WireOnboarding.tsx +4 -1
- package/src/analytics/currentSession.ts +141 -4
- package/src/analytics/index.ts +6 -1
- package/src/analytics/reportClientEvent.ts +19 -10
- package/src/analytics/useAnalytics.ts +74 -15
- package/src/analytics/wireDoctor.ts +152 -7
- package/src/coachmarks/CoachmarkProvider.tsx +26 -5
- package/src/coachmarks/runtime.ts +156 -2
- package/src/coachmarks/types.ts +7 -2
- package/src/coachmarks/useCoachmarkTour.ts +51 -1
- package/src/context/deviceId.ts +72 -6
- package/src/features/WireFeaturesProvider.tsx +72 -12
- package/src/features/fetchWireFeatures.ts +49 -11
- package/src/features/useWireFeatures.ts +39 -3
- package/src/identity/identityRecord.ts +15 -2
- package/src/questionnaire/QuestionnaireGate.tsx +40 -1
- package/src/questionnaire/transport.ts +22 -8
- package/src/questionnaire/useQuestionnaireGate.ts +58 -7
- package/src/reviews/ReviewGate.tsx +125 -37
- package/src/reviews/decision.ts +11 -1
- package/src/reviews/idempotency.ts +109 -0
- package/src/reviews/index.ts +4 -2
- package/src/reviews/runtime.ts +44 -13
- package/src/reviews/transport.ts +39 -20
- package/src/reviews/types.ts +7 -0
- package/src/reviews/useReviewGate.ts +57 -7
- package/src/session-analytics/lifecycle.ts +16 -0
- package/src/session-analytics/useLifecycleEvents.ts +30 -2
- package/src/session-analytics/useSessionStart.ts +22 -2
- package/src/showcase/FeatureShowcase.tsx +50 -3
- package/src/types.ts +15 -8
- package/src/utils/submitResult.ts +17 -9
- package/src/utils/withDeadline.ts +70 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import React__default, { ReactNode } from 'react';
|
|
3
3
|
import { a as WireFeatures, b as WireFeaturesConfig } from '../types-BpwiRpA8.mjs';
|
|
4
|
-
import { C as CoachmarkStorage, a as CoachmarkStep, G as GestureKind, T as TargetRect, P as Placement } from '../types-
|
|
4
|
+
import { C as CoachmarkStorage, a as CoachmarkStep, G as GestureKind, T as TargetRect, P as Placement } from '../types-D_0B0yay.mjs';
|
|
5
5
|
import { View } from 'react-native';
|
|
6
6
|
|
|
7
7
|
interface CoachmarkProviderProps {
|
|
@@ -29,6 +29,11 @@ interface CoachmarkProviderProps {
|
|
|
29
29
|
* pre-resolved `features`, OR a `featuresConfig` (serverUrl + apiKey) to lazily fetch once, OR
|
|
30
30
|
* mount a `WireFeaturesProvider` above (context is read automatically). Omit all three and
|
|
31
31
|
* coachmarks stay on (fail-open) — zero behavior change for hosts that never adopt flags.
|
|
32
|
+
*
|
|
33
|
+
* While a fetch is still in flight, a NEW tour does not arm (so a tenant who switched the module
|
|
34
|
+
* off gets no first impression), and a tour already in flight is never torn down. Nothing is
|
|
35
|
+
* spent by that wait: no once-gate is consumed, so an ON answer plays the tour in full. A failed
|
|
36
|
+
* fetch is an answer — fail-open is unchanged.
|
|
32
37
|
*/
|
|
33
38
|
features?: WireFeatures;
|
|
34
39
|
/** Lazy-fetch config for the flags, used when `features` is absent and no provider is above. */
|
|
@@ -130,6 +135,15 @@ interface UseCoachmarkTourOptions {
|
|
|
130
135
|
* `show()` is a no-op while the flag is off, so a tour that carried on would paint nothing, could
|
|
131
136
|
* not be tapped to advance, and would never reach a terminal state.
|
|
132
137
|
*
|
|
138
|
+
* ARM vs TERMINAL — the third gate, and why it is not the second one. Until the tenant's flags
|
|
139
|
+
* land, that kill switch reads ON because nobody has asked yet, so a tenant who turned coachmarks
|
|
140
|
+
* OFF still got the first tour of every cold start. The wait CANNOT be expressed by writing the
|
|
141
|
+
* kill switch false, precisely because that flag is terminal: it would end the tour of every
|
|
142
|
+
* ENABLED tenant on every cold start, a worse defect than the one being fixed. So an UNANSWERED
|
|
143
|
+
* verdict (`areCoachmarksResolved`, written by CoachmarkProvider from the features' `settled`)
|
|
144
|
+
* blocks only the ARM below — a tour already in flight is never touched by it — and costs nothing
|
|
145
|
+
* while it waits: no timer, no overlay, no once-gate spent.
|
|
146
|
+
*
|
|
133
147
|
* IMPORTANT: `steps` MUST be a stable (memoized) array. If a new array identity
|
|
134
148
|
* is passed on every render the drive effect re-runs and re-shows the current
|
|
135
149
|
* step (wasteful anchor re-resolves / overlay churn), and analytics can
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import React__default, { ReactNode } from 'react';
|
|
3
3
|
import { a as WireFeatures, b as WireFeaturesConfig } from '../types-BpwiRpA8.js';
|
|
4
|
-
import { C as CoachmarkStorage, a as CoachmarkStep, G as GestureKind, T as TargetRect, P as Placement } from '../types-
|
|
4
|
+
import { C as CoachmarkStorage, a as CoachmarkStep, G as GestureKind, T as TargetRect, P as Placement } from '../types-D_0B0yay.js';
|
|
5
5
|
import { View } from 'react-native';
|
|
6
6
|
|
|
7
7
|
interface CoachmarkProviderProps {
|
|
@@ -29,6 +29,11 @@ interface CoachmarkProviderProps {
|
|
|
29
29
|
* pre-resolved `features`, OR a `featuresConfig` (serverUrl + apiKey) to lazily fetch once, OR
|
|
30
30
|
* mount a `WireFeaturesProvider` above (context is read automatically). Omit all three and
|
|
31
31
|
* coachmarks stay on (fail-open) — zero behavior change for hosts that never adopt flags.
|
|
32
|
+
*
|
|
33
|
+
* While a fetch is still in flight, a NEW tour does not arm (so a tenant who switched the module
|
|
34
|
+
* off gets no first impression), and a tour already in flight is never torn down. Nothing is
|
|
35
|
+
* spent by that wait: no once-gate is consumed, so an ON answer plays the tour in full. A failed
|
|
36
|
+
* fetch is an answer — fail-open is unchanged.
|
|
32
37
|
*/
|
|
33
38
|
features?: WireFeatures;
|
|
34
39
|
/** Lazy-fetch config for the flags, used when `features` is absent and no provider is above. */
|
|
@@ -130,6 +135,15 @@ interface UseCoachmarkTourOptions {
|
|
|
130
135
|
* `show()` is a no-op while the flag is off, so a tour that carried on would paint nothing, could
|
|
131
136
|
* not be tapped to advance, and would never reach a terminal state.
|
|
132
137
|
*
|
|
138
|
+
* ARM vs TERMINAL — the third gate, and why it is not the second one. Until the tenant's flags
|
|
139
|
+
* land, that kill switch reads ON because nobody has asked yet, so a tenant who turned coachmarks
|
|
140
|
+
* OFF still got the first tour of every cold start. The wait CANNOT be expressed by writing the
|
|
141
|
+
* kill switch false, precisely because that flag is terminal: it would end the tour of every
|
|
142
|
+
* ENABLED tenant on every cold start, a worse defect than the one being fixed. So an UNANSWERED
|
|
143
|
+
* verdict (`areCoachmarksResolved`, written by CoachmarkProvider from the features' `settled`)
|
|
144
|
+
* blocks only the ARM below — a tour already in flight is never touched by it — and costs nothing
|
|
145
|
+
* while it waits: no timer, no overlay, no once-gate spent.
|
|
146
|
+
*
|
|
133
147
|
* IMPORTANT: `steps` MUST be a stable (memoized) array. If a new array identity
|
|
134
148
|
* is passed on every render the drive effect re-runs and re-shows the current
|
|
135
149
|
* step (wasteful anchor re-resolves / overlay churn), and analytics can
|
package/dist/coachmarks/index.js
CHANGED
|
@@ -95,17 +95,31 @@ var failOpen = async (storage, key) => {
|
|
|
95
95
|
const cached3 = await readCachedFeatures(storage, key);
|
|
96
96
|
return (_a = cached3 == null ? void 0 : cached3.features) != null ? _a : defaultWireFeatures;
|
|
97
97
|
};
|
|
98
|
-
var
|
|
98
|
+
var NO_ANSWER = /* @__PURE__ */ Symbol("wire-features-no-answer");
|
|
99
|
+
var fetchFeaturesJson = async (url, apiKey, timeoutMs) => {
|
|
99
100
|
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
let timer;
|
|
102
|
+
const expired = new Promise((resolve) => {
|
|
103
|
+
timer = setTimeout(() => {
|
|
104
|
+
controller == null ? void 0 : controller.abort();
|
|
105
|
+
resolve(NO_ANSWER);
|
|
106
|
+
}, timeoutMs);
|
|
107
|
+
});
|
|
108
|
+
const exchange = (async () => {
|
|
109
|
+
const res = await fetch(url, {
|
|
103
110
|
method: "GET",
|
|
104
111
|
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
105
112
|
signal: controller == null ? void 0 : controller.signal
|
|
106
113
|
});
|
|
114
|
+
if (!res || !res.ok) return NO_ANSWER;
|
|
115
|
+
return await res.json();
|
|
116
|
+
})();
|
|
117
|
+
try {
|
|
118
|
+
return await Promise.race([exchange, expired]);
|
|
107
119
|
} finally {
|
|
108
120
|
clearTimeout(timer);
|
|
121
|
+
void exchange.catch(() => {
|
|
122
|
+
});
|
|
109
123
|
}
|
|
110
124
|
};
|
|
111
125
|
var fetchWireFeatures = async (config) => {
|
|
@@ -115,9 +129,8 @@ var fetchWireFeatures = async (config) => {
|
|
|
115
129
|
const url = `${config.serverUrl.replace(/\/$/, "")}/v1/features`;
|
|
116
130
|
const timeoutMs = config.timeoutMs && config.timeoutMs > 0 ? config.timeoutMs : DEFAULT_TIMEOUT_MS;
|
|
117
131
|
try {
|
|
118
|
-
const
|
|
119
|
-
if (
|
|
120
|
-
const json = await res.json();
|
|
132
|
+
const json = await fetchFeaturesJson(url, config.apiKey, timeoutMs);
|
|
133
|
+
if (json === NO_ANSWER) return failOpen(storage, key);
|
|
121
134
|
const features = parseWireFeatures(json);
|
|
122
135
|
if (storage) writeCachedFeatures(storage, key, features);
|
|
123
136
|
return features;
|
|
@@ -133,39 +146,51 @@ var useStableValue = (value, isEqual) => {
|
|
|
133
146
|
return ref.current;
|
|
134
147
|
};
|
|
135
148
|
var sameFetchKey = (a, b) => (a == null ? void 0 : a.serverUrl) === (b == null ? void 0 : b.serverUrl) && (a == null ? void 0 : a.apiKey) === (b == null ? void 0 : b.apiKey) && (a == null ? void 0 : a.appId) === (b == null ? void 0 : b.appId);
|
|
136
|
-
var
|
|
149
|
+
var useWireFeaturesState = (config) => {
|
|
137
150
|
const stableConfig = useStableValue(config, sameFetchKey);
|
|
138
151
|
const [flags, setFlags] = React3.useState(defaultWireFeatures);
|
|
139
152
|
const canFetch = !!(stableConfig == null ? void 0 : stableConfig.serverUrl) && !!(stableConfig == null ? void 0 : stableConfig.apiKey);
|
|
153
|
+
const [settled, setSettled] = React3.useState(!canFetch);
|
|
140
154
|
React3.useEffect(() => {
|
|
141
155
|
if (!canFetch) {
|
|
142
156
|
setFlags((prev) => featuresEqual(prev, defaultWireFeatures) ? prev : defaultWireFeatures);
|
|
157
|
+
setSettled(true);
|
|
143
158
|
return;
|
|
144
159
|
}
|
|
145
160
|
let alive = true;
|
|
146
161
|
void fetchWireFeatures(stableConfig).then((next) => {
|
|
147
162
|
if (!alive) return;
|
|
148
163
|
setFlags((prev) => featuresEqual(prev, next) ? prev : next);
|
|
164
|
+
setSettled(true);
|
|
149
165
|
});
|
|
150
166
|
return () => {
|
|
151
167
|
alive = false;
|
|
152
168
|
};
|
|
153
169
|
}, [canFetch, stableConfig]);
|
|
154
|
-
return flags;
|
|
170
|
+
return React3.useMemo(() => ({ flags, settled }), [flags, settled]);
|
|
155
171
|
};
|
|
156
172
|
var CONTEXT_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.context");
|
|
157
|
-
var
|
|
158
|
-
if (!
|
|
159
|
-
|
|
173
|
+
var featuresContextGlobal = globalThis;
|
|
174
|
+
if (!featuresContextGlobal[CONTEXT_SYMBOL]) {
|
|
175
|
+
featuresContextGlobal[CONTEXT_SYMBOL] = React3.createContext(null);
|
|
176
|
+
}
|
|
177
|
+
var WireFeaturesContext = featuresContextGlobal[CONTEXT_SYMBOL];
|
|
178
|
+
var SETTLED_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.settled.context");
|
|
179
|
+
var settledContextGlobal = globalThis;
|
|
180
|
+
if (!settledContextGlobal[SETTLED_SYMBOL]) {
|
|
181
|
+
settledContextGlobal[SETTLED_SYMBOL] = React3.createContext(true);
|
|
160
182
|
}
|
|
161
|
-
var
|
|
183
|
+
var WireFeaturesSettledContext = settledContextGlobal[SETTLED_SYMBOL];
|
|
162
184
|
var useWireFeaturesContext = () => React3.useContext(WireFeaturesContext);
|
|
163
|
-
var
|
|
185
|
+
var useResolvedFeaturesState = (options) => {
|
|
164
186
|
var _a, _b, _c;
|
|
165
187
|
const ctx = useWireFeaturesContext();
|
|
188
|
+
const ctxSettled = React3.useContext(WireFeaturesSettledContext);
|
|
166
189
|
const shouldFetch = !(options == null ? void 0 : options.flags) && ctx == null;
|
|
167
|
-
const fetched =
|
|
168
|
-
|
|
190
|
+
const fetched = useWireFeaturesState(shouldFetch ? options == null ? void 0 : options.config : void 0);
|
|
191
|
+
const flags = (_c = (_b = (_a = options == null ? void 0 : options.flags) != null ? _a : ctx) != null ? _b : fetched.flags) != null ? _c : defaultWireFeatures;
|
|
192
|
+
const settled = (options == null ? void 0 : options.flags) ? true : ctx != null ? ctxSettled : fetched.settled;
|
|
193
|
+
return React3.useMemo(() => ({ flags, settled }), [flags, settled]);
|
|
169
194
|
};
|
|
170
195
|
|
|
171
196
|
// src/coachmarks/runtime.ts
|
|
@@ -201,6 +226,17 @@ var setCoachmarksEnabled = (value) => {
|
|
|
201
226
|
notifyCoachmarksEnabled();
|
|
202
227
|
};
|
|
203
228
|
var areCoachmarksEnabled = () => coachmarkRuntime().coachmarksEnabled;
|
|
229
|
+
var setCoachmarksResolved = (value) => {
|
|
230
|
+
var _a;
|
|
231
|
+
const runtime = coachmarkRuntime();
|
|
232
|
+
if (((_a = runtime.coachmarksResolved) != null ? _a : true) === value) return;
|
|
233
|
+
runtime.coachmarksResolved = value;
|
|
234
|
+
notifyCoachmarksEnabled();
|
|
235
|
+
};
|
|
236
|
+
var areCoachmarksResolved = () => {
|
|
237
|
+
var _a;
|
|
238
|
+
return (_a = coachmarkRuntime().coachmarksResolved) != null ? _a : true;
|
|
239
|
+
};
|
|
204
240
|
var subscribeCoachmarksEnabled = (listener) => {
|
|
205
241
|
const listeners2 = enabledListeners();
|
|
206
242
|
listeners2.add(listener);
|
|
@@ -219,9 +255,54 @@ var isCoachmarkTesting = () => coachmarkRuntime().testing;
|
|
|
219
255
|
var coachmarkGateKey = (tourId) => `wire_coachmark_${tourId}_seen`;
|
|
220
256
|
var showcaseGateKey = (showcaseId) => `wire_showcase_${showcaseId}_seen`;
|
|
221
257
|
var SEEN_VALUE = "1";
|
|
258
|
+
var GATE_PROBE_KEY = "wire_gate_probe";
|
|
259
|
+
var ASYNC_GATE_STORAGE_WARNED_SLOT = /* @__PURE__ */ Symbol.for(
|
|
260
|
+
"@wireai/activation:asyncGateStorageWarned"
|
|
261
|
+
);
|
|
262
|
+
var asyncGateWarnGlobal = globalThis;
|
|
263
|
+
var closedGateStorage = Object.freeze({
|
|
264
|
+
getItem: () => SEEN_VALUE,
|
|
265
|
+
setItem: () => {
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
var syncGateAdapters = /* @__PURE__ */ new WeakSet();
|
|
269
|
+
var asyncGateAdapters = /* @__PURE__ */ new WeakSet();
|
|
270
|
+
var warnAsyncGateStorage = () => {
|
|
271
|
+
if (asyncGateWarnGlobal[ASYNC_GATE_STORAGE_WARNED_SLOT]) return;
|
|
272
|
+
if (typeof __DEV__ === "undefined" || !__DEV__) return;
|
|
273
|
+
if (typeof console === "undefined" || !console.warn) return;
|
|
274
|
+
asyncGateWarnGlobal[ASYNC_GATE_STORAGE_WARNED_SLOT] = true;
|
|
275
|
+
console.warn(
|
|
276
|
+
"[wireai] the gate storage's getItem returned a Promise \u2014 an async adapter (e.g. AsyncStorage) cannot drive the once-per-user gates, so coachmark tours, showcases and the review/questionnaire gates are all DISABLED (fail closed: nothing shows, nothing fires) until a synchronous adapter is provided. Pass a sync adapter (e.g. MMKV: `{ getItem: (k) => mmkv.getString(k) ?? null, setItem: (k, v) => mmkv.set(k, v) }`) to CoachmarkProvider or the gate's `storage` prop."
|
|
277
|
+
);
|
|
278
|
+
};
|
|
279
|
+
var validateGateStorage = (storage) => {
|
|
280
|
+
if (!storage) return null;
|
|
281
|
+
if (syncGateAdapters.has(storage)) return storage;
|
|
282
|
+
if (asyncGateAdapters.has(storage)) return closedGateStorage;
|
|
283
|
+
let probed;
|
|
284
|
+
try {
|
|
285
|
+
probed = storage.getItem(GATE_PROBE_KEY);
|
|
286
|
+
} catch {
|
|
287
|
+
syncGateAdapters.add(storage);
|
|
288
|
+
return storage;
|
|
289
|
+
}
|
|
290
|
+
if (probed != null && typeof probed.then === "function") {
|
|
291
|
+
asyncGateAdapters.add(storage);
|
|
292
|
+
try {
|
|
293
|
+
probed.then(void 0, () => {
|
|
294
|
+
});
|
|
295
|
+
} catch {
|
|
296
|
+
}
|
|
297
|
+
warnAsyncGateStorage();
|
|
298
|
+
return closedGateStorage;
|
|
299
|
+
}
|
|
300
|
+
syncGateAdapters.add(storage);
|
|
301
|
+
return storage;
|
|
302
|
+
};
|
|
222
303
|
var hasSeenGate = (key, storageOverride, testingOverride) => {
|
|
223
304
|
if (testingOverride != null ? testingOverride : isCoachmarkTesting()) return false;
|
|
224
|
-
const storage = storageOverride != null ? storageOverride : getCoachmarkStorage();
|
|
305
|
+
const storage = validateGateStorage(storageOverride != null ? storageOverride : getCoachmarkStorage());
|
|
225
306
|
if (!storage) return false;
|
|
226
307
|
try {
|
|
227
308
|
return storage.getItem(key) === SEEN_VALUE;
|
|
@@ -231,7 +312,7 @@ var hasSeenGate = (key, storageOverride, testingOverride) => {
|
|
|
231
312
|
};
|
|
232
313
|
var markSeenGate = (key, storageOverride, testingOverride) => {
|
|
233
314
|
if (testingOverride != null ? testingOverride : isCoachmarkTesting()) return;
|
|
234
|
-
const storage = storageOverride != null ? storageOverride : getCoachmarkStorage();
|
|
315
|
+
const storage = validateGateStorage(storageOverride != null ? storageOverride : getCoachmarkStorage());
|
|
235
316
|
if (!storage) return;
|
|
236
317
|
try {
|
|
237
318
|
storage.setItem(key, SEEN_VALUE);
|
|
@@ -968,22 +1049,28 @@ var CoachmarkProvider = ({
|
|
|
968
1049
|
featuresConfig,
|
|
969
1050
|
children
|
|
970
1051
|
}) => {
|
|
971
|
-
const
|
|
972
|
-
|
|
1052
|
+
const { flags, settled } = useResolvedFeaturesState({
|
|
1053
|
+
flags: features,
|
|
1054
|
+
config: featuresConfig
|
|
1055
|
+
});
|
|
1056
|
+
const coachmarksOn = settled ? flags.coachmarks.enabled : true;
|
|
973
1057
|
setCoachmarkStorage(storage);
|
|
974
1058
|
setCoachmarkTesting(isTestingCoachmark);
|
|
1059
|
+
setCoachmarksResolved(settled);
|
|
975
1060
|
setCoachmarksEnabled(coachmarksOn);
|
|
976
1061
|
React3.useEffect(() => {
|
|
977
1062
|
setCoachmarkStorage(storage);
|
|
978
1063
|
setCoachmarkTesting(isTestingCoachmark);
|
|
1064
|
+
setCoachmarksResolved(settled);
|
|
979
1065
|
setCoachmarksEnabled(coachmarksOn);
|
|
980
1066
|
return () => {
|
|
981
1067
|
if (getCoachmarkStorage() === storage) {
|
|
982
1068
|
setCoachmarkStorage(null);
|
|
983
1069
|
}
|
|
984
1070
|
setCoachmarksEnabled(true);
|
|
1071
|
+
setCoachmarksResolved(true);
|
|
985
1072
|
};
|
|
986
|
-
}, [storage, isTestingCoachmark, coachmarksOn]);
|
|
1073
|
+
}, [storage, isTestingCoachmark, coachmarksOn, settled]);
|
|
987
1074
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
988
1075
|
children,
|
|
989
1076
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -1086,6 +1173,11 @@ var useCoachmarkTour = (steps, {
|
|
|
1086
1173
|
areCoachmarksEnabled,
|
|
1087
1174
|
areCoachmarksEnabled
|
|
1088
1175
|
);
|
|
1176
|
+
const coachmarksResolved = React3.useSyncExternalStore(
|
|
1177
|
+
subscribeCoachmarksEnabled,
|
|
1178
|
+
areCoachmarksResolved,
|
|
1179
|
+
areCoachmarksResolved
|
|
1180
|
+
);
|
|
1089
1181
|
const exit = React3.useCallback(
|
|
1090
1182
|
(writeGate) => {
|
|
1091
1183
|
if (finishedRef.current) return;
|
|
@@ -1100,14 +1192,16 @@ var useCoachmarkTour = (steps, {
|
|
|
1100
1192
|
const finish = React3.useCallback(() => exit(true), [exit]);
|
|
1101
1193
|
React3.useEffect(() => {
|
|
1102
1194
|
if (!enabled || startedRef.current) return void 0;
|
|
1195
|
+
if (steps.length === 0) return void 0;
|
|
1103
1196
|
if (!coachmarksOn) return void 0;
|
|
1197
|
+
if (!coachmarksResolved) return void 0;
|
|
1104
1198
|
if (showOnce && hasSeenGate(coachmarkGateKey(tourId))) return void 0;
|
|
1105
1199
|
const timer = setTimeout(() => {
|
|
1106
1200
|
startedRef.current = true;
|
|
1107
1201
|
setActiveIndex(0);
|
|
1108
1202
|
}, startDelayMs);
|
|
1109
1203
|
return () => clearTimeout(timer);
|
|
1110
|
-
}, [enabled, coachmarksOn, startDelayMs, showOnce, tourId]);
|
|
1204
|
+
}, [enabled, coachmarksOn, coachmarksResolved, startDelayMs, showOnce, tourId, steps.length]);
|
|
1111
1205
|
React3.useEffect(() => {
|
|
1112
1206
|
if (activeIndex === null) return void 0;
|
|
1113
1207
|
if (!coachmarksOn) {
|
|
@@ -1118,6 +1212,10 @@ var useCoachmarkTour = (steps, {
|
|
|
1118
1212
|
if (startedRef.current && !finishedRef.current) coachmarkOverlay.hide();
|
|
1119
1213
|
return void 0;
|
|
1120
1214
|
}
|
|
1215
|
+
if (steps.length === 0) {
|
|
1216
|
+
exit(false);
|
|
1217
|
+
return void 0;
|
|
1218
|
+
}
|
|
1121
1219
|
if (activeIndex >= steps.length) {
|
|
1122
1220
|
finish();
|
|
1123
1221
|
return void 0;
|