@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.
Files changed (85) hide show
  1. package/AGENTS.md +96 -21
  2. package/CHANGELOG.md +780 -0
  3. package/INTEGRATION_PROMPT.md +61 -23
  4. package/README.md +110 -31
  5. package/dist/analytics/index.d.mts +35 -13
  6. package/dist/analytics/index.d.ts +35 -13
  7. package/dist/analytics/index.js +288 -127
  8. package/dist/analytics/index.js.map +1 -1
  9. package/dist/analytics/index.mjs +288 -127
  10. package/dist/analytics/index.mjs.map +1 -1
  11. package/dist/coachmarks/index.d.mts +15 -1
  12. package/dist/coachmarks/index.d.ts +15 -1
  13. package/dist/coachmarks/index.js +120 -22
  14. package/dist/coachmarks/index.js.map +1 -1
  15. package/dist/coachmarks/index.mjs +120 -22
  16. package/dist/coachmarks/index.mjs.map +1 -1
  17. package/dist/{currentSession-CUvTOchb.d.mts → currentSession-Bz7G6lno.d.mts} +35 -39
  18. package/dist/{currentSession-CW_5Mq4O.d.ts → currentSession-z-CZ55ad.d.ts} +35 -39
  19. package/dist/{decision-Bgo17oH7.d.mts → decision-3vWLuBlO.d.ts} +11 -2
  20. package/dist/{decision-Bkh_LigV.d.ts → decision-yBj2AyPW.d.mts} +11 -2
  21. package/dist/index.d.mts +5 -2
  22. package/dist/index.d.ts +5 -2
  23. package/dist/index.js +125 -36
  24. package/dist/index.js.map +1 -1
  25. package/dist/index.mjs +125 -36
  26. package/dist/index.mjs.map +1 -1
  27. package/dist/questionnaire/index.d.mts +3 -16
  28. package/dist/questionnaire/index.d.ts +3 -16
  29. package/dist/questionnaire/index.js +202 -46
  30. package/dist/questionnaire/index.js.map +1 -1
  31. package/dist/questionnaire/index.mjs +203 -47
  32. package/dist/questionnaire/index.mjs.map +1 -1
  33. package/dist/reviews/index.d.mts +10 -6
  34. package/dist/reviews/index.d.ts +10 -6
  35. package/dist/reviews/index.js +269 -52
  36. package/dist/reviews/index.js.map +1 -1
  37. package/dist/reviews/index.mjs +270 -53
  38. package/dist/reviews/index.mjs.map +1 -1
  39. package/dist/showcase/index.d.mts +1 -1
  40. package/dist/showcase/index.d.ts +1 -1
  41. package/dist/showcase/index.js +106 -20
  42. package/dist/showcase/index.js.map +1 -1
  43. package/dist/showcase/index.mjs +107 -21
  44. package/dist/showcase/index.mjs.map +1 -1
  45. package/dist/{transport-j5gFfJhK.d.mts → transport-s5QxA-ci.d.mts} +18 -13
  46. package/dist/{transport-B_0SgCBe.d.ts → transport-xqqSFqxs.d.ts} +18 -13
  47. package/dist/{types-Cju-1_jT.d.mts → types-Byx306Kv.d.mts} +25 -10
  48. package/dist/{types-BcmagF6K.d.mts → types-D_0B0yay.d.mts} +7 -2
  49. package/dist/{types-BcmagF6K.d.ts → types-D_0B0yay.d.ts} +7 -2
  50. package/dist/{types-h2BZvl1t.d.ts → types-tdATL5z0.d.ts} +25 -10
  51. package/llms.txt +9 -9
  52. package/package.json +6 -9
  53. package/src/WireOnboarding.tsx +4 -1
  54. package/src/analytics/currentSession.ts +141 -4
  55. package/src/analytics/index.ts +6 -1
  56. package/src/analytics/reportClientEvent.ts +19 -10
  57. package/src/analytics/useAnalytics.ts +74 -15
  58. package/src/analytics/wireDoctor.ts +152 -7
  59. package/src/coachmarks/CoachmarkProvider.tsx +26 -5
  60. package/src/coachmarks/runtime.ts +156 -2
  61. package/src/coachmarks/types.ts +7 -2
  62. package/src/coachmarks/useCoachmarkTour.ts +51 -1
  63. package/src/context/deviceId.ts +72 -6
  64. package/src/features/WireFeaturesProvider.tsx +72 -12
  65. package/src/features/fetchWireFeatures.ts +49 -11
  66. package/src/features/useWireFeatures.ts +39 -3
  67. package/src/identity/identityRecord.ts +15 -2
  68. package/src/questionnaire/QuestionnaireGate.tsx +40 -1
  69. package/src/questionnaire/transport.ts +22 -8
  70. package/src/questionnaire/useQuestionnaireGate.ts +58 -7
  71. package/src/reviews/ReviewGate.tsx +125 -37
  72. package/src/reviews/decision.ts +11 -1
  73. package/src/reviews/idempotency.ts +109 -0
  74. package/src/reviews/index.ts +4 -2
  75. package/src/reviews/runtime.ts +44 -13
  76. package/src/reviews/transport.ts +39 -20
  77. package/src/reviews/types.ts +7 -0
  78. package/src/reviews/useReviewGate.ts +57 -7
  79. package/src/session-analytics/lifecycle.ts +16 -0
  80. package/src/session-analytics/useLifecycleEvents.ts +30 -2
  81. package/src/session-analytics/useSessionStart.ts +22 -2
  82. package/src/showcase/FeatureShowcase.tsx +50 -3
  83. package/src/types.ts +15 -8
  84. package/src/utils/submitResult.ts +17 -9
  85. package/src/utils/withDeadline.ts +70 -0
@@ -1,4 +1,4 @@
1
- import React8, { createContext, forwardRef, useRef, useState, useEffect, useCallback, useImperativeHandle, useMemo, useContext } from 'react';
1
+ import React8, { createContext, forwardRef, useRef, useState, useEffect, useCallback, useImperativeHandle, useMemo, useContext, useSyncExternalStore } from 'react';
2
2
  import { StyleSheet, Animated, Easing, BackHandler, Modal, Pressable, Text, AccessibilityInfo, View, TextInput } from 'react-native';
3
3
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
4
  import { SafeAreaView } from 'react-native-safe-area-context';
@@ -837,6 +837,28 @@ var toAnswers = (drafts) => {
837
837
  };
838
838
  };
839
839
 
840
+ // src/utils/withDeadline.ts
841
+ var DEADLINE_EXPIRED = /* @__PURE__ */ Symbol("wire-deadline-expired");
842
+ var DEFAULT_DEADLINE_MS = 15e3;
843
+ var withDeadline = async (exchange, timeoutMs = DEFAULT_DEADLINE_MS) => {
844
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
845
+ let timer;
846
+ const expired = new Promise((resolve) => {
847
+ timer = setTimeout(() => {
848
+ controller == null ? void 0 : controller.abort();
849
+ resolve(DEADLINE_EXPIRED);
850
+ }, timeoutMs);
851
+ });
852
+ const running = exchange(controller == null ? void 0 : controller.signal);
853
+ try {
854
+ return await Promise.race([running, expired]);
855
+ } finally {
856
+ clearTimeout(timer);
857
+ void running.catch(() => {
858
+ });
859
+ }
860
+ };
861
+
840
862
  // src/questionnaire/transport.ts
841
863
  var submitQuestionnaireResponse = async (target, id, submission) => {
842
864
  if (!(target == null ? void 0 : target.serverUrl) || !id) return "unsent";
@@ -845,11 +867,15 @@ var submitQuestionnaireResponse = async (target, id, submission) => {
845
867
  const url = `${base}/v1/questionnaires/${encodeURIComponent(id)}/responses`;
846
868
  const headers = { "Content-Type": "application/json" };
847
869
  if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
848
- const res = await fetch(url, {
849
- method: "POST",
850
- headers,
851
- body: JSON.stringify(submission)
852
- });
870
+ const res = await withDeadline(
871
+ (signal) => fetch(url, {
872
+ method: "POST",
873
+ headers,
874
+ body: JSON.stringify(submission),
875
+ signal
876
+ })
877
+ );
878
+ if (res === DEADLINE_EXPIRED) return "unsent";
853
879
  if (!res) return "unsent";
854
880
  return res.ok ? "accepted" : "rejected";
855
881
  } catch {
@@ -867,9 +893,12 @@ var fetchQuestionnaireDecision = async (target, options = {}) => {
867
893
  const url = `${base}/v1/questionnaires/decision${qs ? `?${qs}` : ""}`;
868
894
  const headers = {};
869
895
  if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
870
- const res = await fetch(url, { headers });
871
- if (!res || !res.ok) return null;
872
- const json = await res.json();
896
+ const json = await withDeadline(async (signal) => {
897
+ const res = await fetch(url, { headers, signal });
898
+ if (!res || !res.ok) return null;
899
+ return await res.json();
900
+ });
901
+ if (json === DEADLINE_EXPIRED) return null;
873
902
  if (!json || typeof json.fire !== "boolean") return null;
874
903
  return json;
875
904
  } catch {
@@ -948,6 +977,10 @@ var _QuestionnaireGate = ({
948
977
  }, [id, onResolved]);
949
978
  const postedRef = useRef(false);
950
979
  const unackedRef = useRef(null);
980
+ const recoverOnSettleRef = useRef(false);
981
+ const recoveredRef = useRef(false);
982
+ const postOnceRef = useRef(() => {
983
+ });
951
984
  const postOnce = useCallback(
952
985
  (body) => {
953
986
  if (postedRef.current) return;
@@ -956,6 +989,10 @@ var _QuestionnaireGate = ({
956
989
  void submitQuestionnaireResponse(target, id, body).then((result) => {
957
990
  if (result === "unsent") {
958
991
  postedRef.current = false;
992
+ if (recoverOnSettleRef.current && !recoveredRef.current) {
993
+ recoveredRef.current = true;
994
+ postOnceRef.current(body);
995
+ }
959
996
  return;
960
997
  }
961
998
  unackedRef.current = null;
@@ -963,11 +1000,16 @@ var _QuestionnaireGate = ({
963
1000
  },
964
1001
  [target, id]
965
1002
  );
1003
+ postOnceRef.current = postOnce;
966
1004
  const recoverRef = useRef(() => {
967
1005
  });
968
1006
  recoverRef.current = () => {
969
1007
  const undelivered = unackedRef.current;
970
- if (undelivered && !postedRef.current) postOnce(undelivered);
1008
+ if (undelivered && !postedRef.current) {
1009
+ postOnce(undelivered);
1010
+ return;
1011
+ }
1012
+ if (postedRef.current && undelivered) recoverOnSettleRef.current = true;
971
1013
  };
972
1014
  React8.useEffect(() => () => recoverRef.current(), []);
973
1015
  const finish = useCallback(
@@ -1116,6 +1158,39 @@ var styles7 = StyleSheet.create({
1116
1158
  backGlyph: { includeFontPadding: false }
1117
1159
  });
1118
1160
 
1161
+ // src/analytics/reportClientEvent.ts
1162
+ var makeSessionId = () => `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
1163
+
1164
+ // src/analytics/currentSession.ts
1165
+ var CURRENT_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
1166
+ "@wireai/activation:currentSessionId"
1167
+ );
1168
+ var globalSlot = globalThis;
1169
+ var SESSION_ID_LISTENERS_SLOT = /* @__PURE__ */ Symbol.for(
1170
+ "@wireai/activation:currentSessionIdListeners"
1171
+ );
1172
+ var listenerSlot = globalThis;
1173
+ var listeners = () => {
1174
+ const existing = listenerSlot[SESSION_ID_LISTENERS_SLOT];
1175
+ if (existing) return existing;
1176
+ const created = /* @__PURE__ */ new Set();
1177
+ listenerSlot[SESSION_ID_LISTENERS_SLOT] = created;
1178
+ return created;
1179
+ };
1180
+ var subscribeCurrentSessionId = (listener) => {
1181
+ const set = listeners();
1182
+ set.add(listener);
1183
+ return () => {
1184
+ set.delete(listener);
1185
+ };
1186
+ };
1187
+ var getCurrentSessionId = () => globalSlot[CURRENT_SESSION_ID_SLOT];
1188
+ var MINTED_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
1189
+ "@wireai/activation:mintedSessionId"
1190
+ );
1191
+ var mintedSlot = globalThis;
1192
+ var isMintedSessionId = (id) => id !== void 0 && id === mintedSlot[MINTED_SESSION_ID_SLOT];
1193
+
1119
1194
  // src/coachmarks/runtime.ts
1120
1195
  var COACHMARK_RUNTIME_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:coachmarkRuntime");
1121
1196
  var runtimeGlobal = globalThis;
@@ -1129,9 +1204,54 @@ var coachmarkRuntime = () => {
1129
1204
  var getCoachmarkStorage = () => coachmarkRuntime().storage;
1130
1205
  var isCoachmarkTesting = () => coachmarkRuntime().testing;
1131
1206
  var SEEN_VALUE = "1";
1207
+ var GATE_PROBE_KEY = "wire_gate_probe";
1208
+ var ASYNC_GATE_STORAGE_WARNED_SLOT = /* @__PURE__ */ Symbol.for(
1209
+ "@wireai/activation:asyncGateStorageWarned"
1210
+ );
1211
+ var asyncGateWarnGlobal = globalThis;
1212
+ var closedGateStorage = Object.freeze({
1213
+ getItem: () => SEEN_VALUE,
1214
+ setItem: () => {
1215
+ }
1216
+ });
1217
+ var syncGateAdapters = /* @__PURE__ */ new WeakSet();
1218
+ var asyncGateAdapters = /* @__PURE__ */ new WeakSet();
1219
+ var warnAsyncGateStorage = () => {
1220
+ if (asyncGateWarnGlobal[ASYNC_GATE_STORAGE_WARNED_SLOT]) return;
1221
+ if (typeof __DEV__ === "undefined" || !__DEV__) return;
1222
+ if (typeof console === "undefined" || !console.warn) return;
1223
+ asyncGateWarnGlobal[ASYNC_GATE_STORAGE_WARNED_SLOT] = true;
1224
+ console.warn(
1225
+ "[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."
1226
+ );
1227
+ };
1228
+ var validateGateStorage = (storage) => {
1229
+ if (!storage) return null;
1230
+ if (syncGateAdapters.has(storage)) return storage;
1231
+ if (asyncGateAdapters.has(storage)) return closedGateStorage;
1232
+ let probed;
1233
+ try {
1234
+ probed = storage.getItem(GATE_PROBE_KEY);
1235
+ } catch {
1236
+ syncGateAdapters.add(storage);
1237
+ return storage;
1238
+ }
1239
+ if (probed != null && typeof probed.then === "function") {
1240
+ asyncGateAdapters.add(storage);
1241
+ try {
1242
+ probed.then(void 0, () => {
1243
+ });
1244
+ } catch {
1245
+ }
1246
+ warnAsyncGateStorage();
1247
+ return closedGateStorage;
1248
+ }
1249
+ syncGateAdapters.add(storage);
1250
+ return storage;
1251
+ };
1132
1252
  var hasSeenGate = (key, storageOverride, testingOverride) => {
1133
1253
  if (testingOverride != null ? testingOverride : isCoachmarkTesting()) return false;
1134
- const storage = storageOverride != null ? storageOverride : getCoachmarkStorage();
1254
+ const storage = validateGateStorage(storageOverride != null ? storageOverride : getCoachmarkStorage());
1135
1255
  if (!storage) return false;
1136
1256
  try {
1137
1257
  return storage.getItem(key) === SEEN_VALUE;
@@ -1141,7 +1261,7 @@ var hasSeenGate = (key, storageOverride, testingOverride) => {
1141
1261
  };
1142
1262
  var markSeenGate = (key, storageOverride, testingOverride) => {
1143
1263
  if (testingOverride != null ? testingOverride : isCoachmarkTesting()) return;
1144
- const storage = storageOverride != null ? storageOverride : getCoachmarkStorage();
1264
+ const storage = validateGateStorage(storageOverride != null ? storageOverride : getCoachmarkStorage());
1145
1265
  if (!storage) return;
1146
1266
  try {
1147
1267
  storage.setItem(key, SEEN_VALUE);
@@ -1229,17 +1349,31 @@ var failOpen = async (storage, key) => {
1229
1349
  const cached = await readCachedFeatures(storage, key);
1230
1350
  return (_a = cached == null ? void 0 : cached.features) != null ? _a : defaultWireFeatures;
1231
1351
  };
1232
- var fetchWithTimeout = async (url, apiKey, timeoutMs) => {
1352
+ var NO_ANSWER = /* @__PURE__ */ Symbol("wire-features-no-answer");
1353
+ var fetchFeaturesJson = async (url, apiKey, timeoutMs) => {
1233
1354
  const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
1234
- const timer = setTimeout(() => controller == null ? void 0 : controller.abort(), timeoutMs);
1235
- try {
1236
- return await fetch(url, {
1355
+ let timer;
1356
+ const expired = new Promise((resolve) => {
1357
+ timer = setTimeout(() => {
1358
+ controller == null ? void 0 : controller.abort();
1359
+ resolve(NO_ANSWER);
1360
+ }, timeoutMs);
1361
+ });
1362
+ const exchange = (async () => {
1363
+ const res = await fetch(url, {
1237
1364
  method: "GET",
1238
1365
  headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
1239
1366
  signal: controller == null ? void 0 : controller.signal
1240
1367
  });
1368
+ if (!res || !res.ok) return NO_ANSWER;
1369
+ return await res.json();
1370
+ })();
1371
+ try {
1372
+ return await Promise.race([exchange, expired]);
1241
1373
  } finally {
1242
1374
  clearTimeout(timer);
1375
+ void exchange.catch(() => {
1376
+ });
1243
1377
  }
1244
1378
  };
1245
1379
  var fetchWireFeatures = async (config) => {
@@ -1249,9 +1383,8 @@ var fetchWireFeatures = async (config) => {
1249
1383
  const url = `${config.serverUrl.replace(/\/$/, "")}/v1/features`;
1250
1384
  const timeoutMs = config.timeoutMs && config.timeoutMs > 0 ? config.timeoutMs : DEFAULT_TIMEOUT_MS;
1251
1385
  try {
1252
- const res = await fetchWithTimeout(url, config.apiKey, timeoutMs);
1253
- if (!res || !res.ok) return failOpen(storage, key);
1254
- const json = await res.json();
1386
+ const json = await fetchFeaturesJson(url, config.apiKey, timeoutMs);
1387
+ if (json === NO_ANSWER) return failOpen(storage, key);
1255
1388
  const features = parseWireFeatures(json);
1256
1389
  if (storage) writeCachedFeatures(storage, key, features);
1257
1390
  return features;
@@ -1267,39 +1400,51 @@ var useStableValue = (value, isEqual) => {
1267
1400
  return ref.current;
1268
1401
  };
1269
1402
  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);
1270
- var useWireFeatures = (config) => {
1403
+ var useWireFeaturesState = (config) => {
1271
1404
  const stableConfig = useStableValue(config, sameFetchKey);
1272
1405
  const [flags, setFlags] = useState(defaultWireFeatures);
1273
1406
  const canFetch = !!(stableConfig == null ? void 0 : stableConfig.serverUrl) && !!(stableConfig == null ? void 0 : stableConfig.apiKey);
1407
+ const [settled, setSettled] = useState(!canFetch);
1274
1408
  useEffect(() => {
1275
1409
  if (!canFetch) {
1276
1410
  setFlags((prev) => featuresEqual(prev, defaultWireFeatures) ? prev : defaultWireFeatures);
1411
+ setSettled(true);
1277
1412
  return;
1278
1413
  }
1279
1414
  let alive = true;
1280
1415
  void fetchWireFeatures(stableConfig).then((next) => {
1281
1416
  if (!alive) return;
1282
1417
  setFlags((prev) => featuresEqual(prev, next) ? prev : next);
1418
+ setSettled(true);
1283
1419
  });
1284
1420
  return () => {
1285
1421
  alive = false;
1286
1422
  };
1287
1423
  }, [canFetch, stableConfig]);
1288
- return flags;
1424
+ return useMemo(() => ({ flags, settled }), [flags, settled]);
1289
1425
  };
1290
1426
  var CONTEXT_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.context");
1291
- var globalObj = globalThis;
1292
- if (!globalObj[CONTEXT_SYMBOL]) {
1293
- globalObj[CONTEXT_SYMBOL] = createContext(null);
1427
+ var featuresContextGlobal = globalThis;
1428
+ if (!featuresContextGlobal[CONTEXT_SYMBOL]) {
1429
+ featuresContextGlobal[CONTEXT_SYMBOL] = createContext(null);
1430
+ }
1431
+ var WireFeaturesContext = featuresContextGlobal[CONTEXT_SYMBOL];
1432
+ var SETTLED_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.settled.context");
1433
+ var settledContextGlobal = globalThis;
1434
+ if (!settledContextGlobal[SETTLED_SYMBOL]) {
1435
+ settledContextGlobal[SETTLED_SYMBOL] = createContext(true);
1294
1436
  }
1295
- var WireFeaturesContext = globalObj[CONTEXT_SYMBOL];
1437
+ var WireFeaturesSettledContext = settledContextGlobal[SETTLED_SYMBOL];
1296
1438
  var useWireFeaturesContext = () => useContext(WireFeaturesContext);
1297
- var useResolvedFeatures = (options) => {
1439
+ var useResolvedFeaturesState = (options) => {
1298
1440
  var _a, _b, _c;
1299
1441
  const ctx = useWireFeaturesContext();
1442
+ const ctxSettled = useContext(WireFeaturesSettledContext);
1300
1443
  const shouldFetch = !(options == null ? void 0 : options.flags) && ctx == null;
1301
- const fetched = useWireFeatures(shouldFetch ? options == null ? void 0 : options.config : void 0);
1302
- return (_c = (_b = (_a = options == null ? void 0 : options.flags) != null ? _a : ctx) != null ? _b : fetched) != null ? _c : defaultWireFeatures;
1444
+ const fetched = useWireFeaturesState(shouldFetch ? options == null ? void 0 : options.config : void 0);
1445
+ const flags = (_c = (_b = (_a = options == null ? void 0 : options.flags) != null ? _a : ctx) != null ? _b : fetched.flags) != null ? _c : defaultWireFeatures;
1446
+ const settled = (options == null ? void 0 : options.flags) ? true : ctx != null ? ctxSettled : fetched.settled;
1447
+ return useMemo(() => ({ flags, settled }), [flags, settled]);
1303
1448
  };
1304
1449
 
1305
1450
  // src/reviews/equality.ts
@@ -1323,18 +1468,8 @@ var sameDecision = (a, b) => {
1323
1468
  return a.fire === b.fire && a.reason === b.reason;
1324
1469
  };
1325
1470
 
1326
- // src/analytics/reportClientEvent.ts
1327
- var makeSessionId = () => `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
1328
-
1329
- // src/analytics/currentSession.ts
1330
- var CURRENT_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
1331
- "@wireai/activation:currentSessionId"
1332
- );
1333
- var globalSlot = globalThis;
1334
- var getCurrentSessionId = () => globalSlot[CURRENT_SESSION_ID_SLOT];
1335
-
1336
1471
  // src/reviews/runtime.ts
1337
- var resolveStorage = (override) => override != null ? override : getCoachmarkStorage();
1472
+ var resolveStorage = (override) => validateGateStorage(override != null ? override : getCoachmarkStorage());
1338
1473
  var NO_STORAGE_WARNED_SLOT = /* @__PURE__ */ Symbol.for(
1339
1474
  "@wireai/activation:gateStorageWarned"
1340
1475
  );
@@ -1379,18 +1514,23 @@ var PROCESS_OPEN_ID_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:proces
1379
1514
  var openIdGlobal = globalThis;
1380
1515
  var currentOpenId = () => {
1381
1516
  const live = getCurrentSessionId();
1517
+ const liveMinted = isMintedSessionId(live);
1382
1518
  const pin = openIdGlobal[PROCESS_OPEN_ID_SLOT];
1383
1519
  if (!pin) {
1384
1520
  const openId = live != null ? live : makeSessionId();
1385
- openIdGlobal[PROCESS_OPEN_ID_SLOT] = { openId, observed: live };
1521
+ openIdGlobal[PROCESS_OPEN_ID_SLOT] = { openId, observed: live, observedMinted: liveMinted };
1386
1522
  return openId;
1387
1523
  }
1388
1524
  if (!live || live === pin.observed) return pin.openId;
1389
- if (pin.observed === void 0) {
1390
- openIdGlobal[PROCESS_OPEN_ID_SLOT] = { openId: pin.openId, observed: live };
1525
+ if (pin.observed === void 0 || pin.observedMinted) {
1526
+ openIdGlobal[PROCESS_OPEN_ID_SLOT] = {
1527
+ openId: pin.openId,
1528
+ observed: live,
1529
+ observedMinted: liveMinted
1530
+ };
1391
1531
  return pin.openId;
1392
1532
  }
1393
- openIdGlobal[PROCESS_OPEN_ID_SLOT] = { openId: live, observed: live };
1533
+ openIdGlobal[PROCESS_OPEN_ID_SLOT] = { openId: live, observed: live, observedMinted: liveMinted };
1394
1534
  return live;
1395
1535
  };
1396
1536
  var bumpSessionCount = (storage, sessionsKey, openKey, openId = currentOpenId()) => {
@@ -1431,7 +1571,11 @@ var useQuestionnaireGate = ({
1431
1571
  }) => {
1432
1572
  const config = useStableValue2(configProp, shallowEqual);
1433
1573
  const decision = useStableValue2(decisionProp, (a, b) => sameDecision(a, b));
1434
- const featureEnabled = useResolvedFeatures({ flags: features, config: featuresConfig }).questionnaire.enabled;
1574
+ const { flags: resolvedFeatures, settled: featuresSettled } = useResolvedFeaturesState({
1575
+ flags: features,
1576
+ config: featuresConfig
1577
+ });
1578
+ const featureEnabled = resolvedFeatures.questionnaire.enabled;
1435
1579
  const testing = isTesting != null ? isTesting : isCoachmarkTesting();
1436
1580
  const seenKey = questionnaireSeenKey(
1437
1581
  config.id,
@@ -1440,11 +1584,22 @@ var useQuestionnaireGate = ({
1440
1584
  const lastKey = questionnaireLastShownKey(config.id);
1441
1585
  const sessionsKey = questionnaireSessionsKey(config.id);
1442
1586
  const sessionOpenKey = questionnaireSessionOpenKey(config.id);
1443
- const sessions = useState(() => {
1587
+ const openSessionId = useSyncExternalStore(
1588
+ subscribeCurrentSessionId,
1589
+ getCurrentSessionId,
1590
+ getCurrentSessionId
1591
+ );
1592
+ const storageRef = useRef(storage);
1593
+ storageRef.current = storage;
1594
+ const [sessions, setSessions] = useState(() => {
1444
1595
  const store = resolveStorage(storage);
1445
1596
  warnMissingGateStorage(store, "questionnaire");
1446
1597
  return bumpSessionCount(store, sessionsKey, sessionOpenKey);
1447
- })[0];
1598
+ });
1599
+ useEffect(() => {
1600
+ const next = bumpSessionCount(resolveStorage(storageRef.current), sessionsKey, sessionOpenKey);
1601
+ setSessions((prev) => prev === next ? prev : next);
1602
+ }, [openSessionId, sessionsKey, sessionOpenKey]);
1448
1603
  const hasServerDecision = decision != null;
1449
1604
  const timeoutMs = config.timeoutFallbackMs;
1450
1605
  const waits = !hasServerDecision && timeoutMs != null && timeoutMs > 0;
@@ -1478,10 +1633,11 @@ var useQuestionnaireGate = ({
1478
1633
  });
1479
1634
  const resolved = decideQuestionnaire(local, decision);
1480
1635
  const alreadySeen = hasSeenGate(seenKey, storage, isTesting);
1481
- const ready = hasServerDecision || elapsed;
1636
+ const ready = (hasServerDecision || elapsed) && featuresSettled;
1482
1637
  return { visible: ready && resolved.fire && !alreadySeen, verdict: resolved };
1483
1638
  }, [
1484
1639
  featureEnabled,
1640
+ featuresSettled,
1485
1641
  testing,
1486
1642
  config,
1487
1643
  decision,