@cross-deck/web 1.5.6 → 1.6.1

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "generatedAt": "2026-05-27T15:13:48.445Z",
3
+ "generatedAt": "2026-05-30T11:19:57.320Z",
4
4
  "sdk": "@cross-deck/web",
5
5
  "codes": [
6
6
  {
package/dist/index.cjs CHANGED
@@ -99,7 +99,7 @@ function typeMapForStatus(status) {
99
99
  }
100
100
 
101
101
  // src/_version.ts
102
- var SDK_VERSION = "1.5.3";
102
+ var SDK_VERSION = "1.6.1";
103
103
  var SDK_NAME = "@cross-deck/web";
104
104
 
105
105
  // src/http.ts
@@ -1448,6 +1448,8 @@ var DEFAULT_AUTO_TRACK = {
1448
1448
  errors: true
1449
1449
  };
1450
1450
  var SESSION_RESUME_THRESHOLD_MS = 30 * 60 * 1e3;
1451
+ var SESSION_STORAGE_KEY = "crossdeck:session";
1452
+ var ACTIVITY_PERSIST_THROTTLE_MS = 5e3;
1451
1453
  var EMPTY_ACQUISITION = {
1452
1454
  utm_source: "",
1453
1455
  utm_medium: "",
@@ -1463,11 +1465,13 @@ var EMPTY_ACQUISITION = {
1463
1465
  twclid: ""
1464
1466
  };
1465
1467
  var AutoTracker = class {
1466
- constructor(cfg, track) {
1468
+ constructor(cfg, track, opts) {
1467
1469
  this.cfg = cfg;
1468
1470
  this.track = track;
1469
1471
  this.session = null;
1470
1472
  this.cleanups = [];
1473
+ /** Last time we flushed lastActivityAt to storage (throttle gate). */
1474
+ this.lastPersistAt = 0;
1471
1475
  /**
1472
1476
  * Stable per-page-view identifier. Minted at every `page.viewed`
1473
1477
  * emission and attached to every subsequent event until the next
@@ -1479,6 +1483,8 @@ var AutoTracker = class {
1479
1483
  * install if `autoTrack.pageViews !== false`).
1480
1484
  */
1481
1485
  this.pageviewId = null;
1486
+ this.storage = opts?.storage ?? null;
1487
+ this.sessionKey = opts?.storageKey ?? SESSION_STORAGE_KEY;
1482
1488
  }
1483
1489
  install() {
1484
1490
  if (!isBrowserSafe()) return;
@@ -1497,6 +1503,7 @@ var AutoTracker = class {
1497
1503
  if (this.session && !this.session.endedSent) {
1498
1504
  this.emitSessionEnd();
1499
1505
  }
1506
+ this.clearStoredSession();
1500
1507
  this.session = null;
1501
1508
  }
1502
1509
  /** Exposed for tests + consumers that want to reset the session manually. */
@@ -1504,8 +1511,24 @@ var AutoTracker = class {
1504
1511
  if (this.session && !this.session.endedSent) this.emitSessionEnd();
1505
1512
  this.pageviewId = null;
1506
1513
  this.session = this.startNewSession();
1514
+ this.persistSession();
1507
1515
  this.emitSessionStart();
1508
1516
  }
1517
+ /**
1518
+ * Keep the rolling session window alive. Called by the host on EVERY
1519
+ * tracked event (auto or custom) so any activity — not just the
1520
+ * pageviews/clicks AutoTracker emits itself — pushes the 30-min idle
1521
+ * boundary forward. In-memory time is updated exactly; the storage
1522
+ * flush is throttled (pagehide forces a final flush).
1523
+ */
1524
+ markActivity() {
1525
+ if (!this.session) return;
1526
+ const now = Date.now();
1527
+ this.session.lastActivityAt = now;
1528
+ if (now - this.lastPersistAt >= ACTIVITY_PERSIST_THROTTLE_MS) {
1529
+ this.persistSession();
1530
+ }
1531
+ }
1509
1532
  /** Exposed for inspection/tests — returns the current sessionId (or null if not in a session). */
1510
1533
  get currentSessionId() {
1511
1534
  return this.session?.sessionId ?? null;
@@ -1528,26 +1551,43 @@ var AutoTracker = class {
1528
1551
  }
1529
1552
  // ---------- sessions ----------
1530
1553
  installSessionTracking() {
1531
- this.session = this.startNewSession();
1532
- this.emitSessionStart();
1554
+ const now = Date.now();
1555
+ const stored = this.readStoredSession();
1556
+ if (stored && now - stored.lastActivityAt < SESSION_RESUME_THRESHOLD_MS) {
1557
+ this.session = {
1558
+ sessionId: stored.id,
1559
+ startedAt: stored.startedAt,
1560
+ lastActivityAt: now,
1561
+ hiddenAt: null,
1562
+ endedSent: false,
1563
+ acquisition: stored.acquisition
1564
+ };
1565
+ this.persistSession();
1566
+ } else {
1567
+ this.session = this.startNewSession();
1568
+ this.persistSession();
1569
+ this.emitSessionStart();
1570
+ }
1533
1571
  const onVisChange = () => {
1534
1572
  if (!this.session) return;
1535
1573
  const doc2 = globalThis.document;
1536
1574
  if (doc2.visibilityState === "hidden") {
1537
1575
  this.session.hiddenAt = Date.now();
1576
+ this.persistSession();
1538
1577
  } else if (doc2.visibilityState === "visible") {
1539
- const hiddenFor = this.session.hiddenAt ? Date.now() - this.session.hiddenAt : 0;
1540
- if (hiddenFor >= SESSION_RESUME_THRESHOLD_MS) {
1578
+ const idleFor = Date.now() - this.session.lastActivityAt;
1579
+ if (idleFor >= SESSION_RESUME_THRESHOLD_MS) {
1541
1580
  this.emitSessionEnd();
1542
1581
  this.pageviewId = null;
1543
1582
  this.session = this.startNewSession();
1583
+ this.persistSession();
1544
1584
  this.emitSessionStart();
1545
1585
  } else {
1546
1586
  this.session.hiddenAt = null;
1547
1587
  }
1548
1588
  }
1549
1589
  };
1550
- const onPageHide = () => this.emitSessionEnd();
1590
+ const onPageHide = () => this.persistSession();
1551
1591
  const w = globalThis.window;
1552
1592
  const doc = globalThis.document;
1553
1593
  doc.addEventListener("visibilitychange", onVisChange);
@@ -1560,14 +1600,63 @@ var AutoTracker = class {
1560
1600
  });
1561
1601
  }
1562
1602
  startNewSession() {
1603
+ const now = Date.now();
1563
1604
  return {
1564
1605
  sessionId: mintSessionId(),
1565
- startedAt: Date.now(),
1606
+ startedAt: now,
1607
+ lastActivityAt: now,
1566
1608
  hiddenAt: null,
1567
1609
  endedSent: false,
1568
1610
  acquisition: captureAcquisition()
1569
1611
  };
1570
1612
  }
1613
+ /**
1614
+ * Read the persisted session continuity record. Returns null on no
1615
+ * storage, no record, malformed JSON, or a record missing its required
1616
+ * fields — every failure degrades to "no session to resume", which is
1617
+ * the safe (start-fresh) path.
1618
+ */
1619
+ readStoredSession() {
1620
+ if (!this.storage) return null;
1621
+ try {
1622
+ const raw = this.storage.getItem(this.sessionKey);
1623
+ if (!raw) return null;
1624
+ const p = JSON.parse(raw);
1625
+ if (!p || typeof p.id !== "string" || typeof p.startedAt !== "number" || typeof p.lastActivityAt !== "number") {
1626
+ return null;
1627
+ }
1628
+ return {
1629
+ id: p.id,
1630
+ startedAt: p.startedAt,
1631
+ lastActivityAt: p.lastActivityAt,
1632
+ acquisition: p.acquisition && typeof p.acquisition === "object" ? p.acquisition : EMPTY_ACQUISITION
1633
+ };
1634
+ } catch {
1635
+ return null;
1636
+ }
1637
+ }
1638
+ /** Flush the current session to storage. No-op without storage/session. */
1639
+ persistSession() {
1640
+ if (!this.storage || !this.session) return;
1641
+ this.lastPersistAt = Date.now();
1642
+ try {
1643
+ const rec = {
1644
+ id: this.session.sessionId,
1645
+ startedAt: this.session.startedAt,
1646
+ lastActivityAt: this.session.lastActivityAt,
1647
+ acquisition: this.session.acquisition
1648
+ };
1649
+ this.storage.setItem(this.sessionKey, JSON.stringify(rec));
1650
+ } catch {
1651
+ }
1652
+ }
1653
+ clearStoredSession() {
1654
+ if (!this.storage) return;
1655
+ try {
1656
+ this.storage.removeItem(this.sessionKey);
1657
+ } catch {
1658
+ }
1659
+ }
1571
1660
  emitSessionStart() {
1572
1661
  if (!this.session) return;
1573
1662
  this.track("session.started", { sessionId: this.session.sessionId });
@@ -1739,6 +1828,78 @@ function isInsidePasswordField(el) {
1739
1828
  if (el.closest('input[type="password"]')) return true;
1740
1829
  return false;
1741
1830
  }
1831
+ var INLINE_LABEL_TAGS = /* @__PURE__ */ new Set([
1832
+ "span",
1833
+ "b",
1834
+ "strong",
1835
+ "em",
1836
+ "i",
1837
+ "small",
1838
+ "mark",
1839
+ "u",
1840
+ "label",
1841
+ "abbr",
1842
+ "time",
1843
+ "bdi",
1844
+ "cite",
1845
+ "code",
1846
+ "kbd",
1847
+ "q",
1848
+ "sub",
1849
+ "sup"
1850
+ ]);
1851
+ var NON_LABEL_SUBTREES = /* @__PURE__ */ new Set(["svg", "style", "script", "noscript"]);
1852
+ var CONTAINER_MARKERS = "a[href], button, input, select, textarea, [role='button'], [role='link'], h1, h2, h3, h4, h5, h6";
1853
+ var HEADING_SELECTOR = "h1, h2, h3, h4, h5, h6";
1854
+ function collapseWs(s) {
1855
+ return s.replace(/\s+/g, " ").trim();
1856
+ }
1857
+ function boundaryText(el) {
1858
+ let out = "";
1859
+ const walk = (node) => {
1860
+ const kids = node.childNodes;
1861
+ for (let i = 0; i < kids.length; i++) {
1862
+ const child = kids[i];
1863
+ if (!child) continue;
1864
+ if (child.nodeType === 3) {
1865
+ out += child.textContent || "";
1866
+ } else if (child.nodeType === 1) {
1867
+ if (NON_LABEL_SUBTREES.has(child.tagName.toLowerCase())) continue;
1868
+ out += " ";
1869
+ walk(child);
1870
+ out += " ";
1871
+ }
1872
+ }
1873
+ };
1874
+ walk(el);
1875
+ return collapseWs(out);
1876
+ }
1877
+ function directLabel(el) {
1878
+ let out = "";
1879
+ const kids = el.childNodes;
1880
+ for (let i = 0; i < kids.length; i++) {
1881
+ const child = kids[i];
1882
+ if (!child) continue;
1883
+ if (child.nodeType === 3) {
1884
+ out += " " + (child.textContent || "");
1885
+ } else if (child.nodeType === 1 && INLINE_LABEL_TAGS.has(child.tagName.toLowerCase())) {
1886
+ out += " " + boundaryText(child);
1887
+ }
1888
+ }
1889
+ return collapseWs(out);
1890
+ }
1891
+ function visibleLabel(el) {
1892
+ const isContainer = el.querySelector(CONTAINER_MARKERS) !== null;
1893
+ if (!isContainer) return boundaryText(el);
1894
+ const direct = directLabel(el);
1895
+ if (direct) return direct;
1896
+ const heading = el.querySelector(HEADING_SELECTOR);
1897
+ if (heading) {
1898
+ const h = boundaryText(heading);
1899
+ if (h) return h;
1900
+ }
1901
+ return "";
1902
+ }
1742
1903
  function extractText(el) {
1743
1904
  const clean = (s) => s.replace(/\s+/g, " ").trim();
1744
1905
  const explicit = el.getAttribute("data-cd-track") || el.getAttribute("data-track") || el.getAttribute("data-testid");
@@ -1765,7 +1926,7 @@ function extractText(el) {
1765
1926
  const t = clean(el.value);
1766
1927
  if (t) return t;
1767
1928
  }
1768
- const text = clean(el.textContent || "");
1929
+ const text = visibleLabel(el);
1769
1930
  if (text) return text;
1770
1931
  const title = el.getAttribute("title");
1771
1932
  if (title) {
@@ -2406,7 +2567,7 @@ var BreadcrumbBuffer = class {
2406
2567
  };
2407
2568
 
2408
2569
  // src/_diagnostic-telemetry.ts
2409
- var DIAGNOSTIC_TELEMETRY_ENDPOINT = "https://api.cross-deck.com/v1/sdk/diagnostic";
2570
+ var DIAGNOSTIC_TELEMETRY_ENDPOINT = "https://api.cross-deck.com/v1/events";
2410
2571
  var DIAGNOSTIC_TELEMETRY_PUBLISHABLE_KEY = "cd_pub_live_9490e7aa029c432abf";
2411
2572
  function isDiagnosticTelemetryEnabled() {
2412
2573
  return !DIAGNOSTIC_TELEMETRY_PUBLISHABLE_KEY.startsWith(
@@ -2444,7 +2605,15 @@ function sendDiagnosticTelemetry(payload) {
2444
2605
  if (!isDiagnosticTelemetryEnabled()) return;
2445
2606
  const filtered = filterDiagnosticPayload(payload);
2446
2607
  if (Object.keys(filtered).length === 0) return;
2447
- const body = JSON.stringify(filtered);
2608
+ const body = JSON.stringify({
2609
+ events: [
2610
+ {
2611
+ name: "crossdeck.contract_failed",
2612
+ timestamp: Date.now(),
2613
+ properties: filtered
2614
+ }
2615
+ ]
2616
+ });
2448
2617
  const f = globalThis.fetch;
2449
2618
  if (typeof f !== "function") return;
2450
2619
  try {
@@ -3304,19 +3473,58 @@ var DEFAULT_ERROR_CAPTURE = {
3304
3473
  "ResizeObserver loop completed with undelivered notifications",
3305
3474
  "Non-Error promise rejection captured"
3306
3475
  // NOTE: We deliberately do NOT drop cross-origin "Script error."
3307
- // events here. They used to be silently filtered as noise, but
3308
- // silent drops are the opposite of "developer sleeps well at
3309
- // night". We now capture them with a clear label and the
3310
- // `cross_origin` tag so the dashboard surfaces them as a
3311
- // distinct, actionable category (the fix is always the same
3312
- // add `crossorigin="anonymous"` to the script tag + CORS
3313
- // headers on the script's origin). Apps that genuinely want
3314
- // them muted can re-add "Script error" to ignoreErrors via
3315
- // init config.
3476
+ // events here. The actionability principle (see denyUrls
3477
+ // comment below) draws the line at "developer cannot act":
3478
+ // cross-origin CORS opacity HAS a real, code-shaped fix (add
3479
+ // `crossorigin="anonymous"` to the script tag + CORS headers
3480
+ // on the script's origin). The dashboard surfaces these with a
3481
+ // `cross_origin` tag pointing at that fix. Apps that genuinely
3482
+ // want them muted can re-add "Script error" to ignoreErrors
3483
+ // via init config.
3316
3484
  ],
3317
3485
  allowUrls: [],
3318
3486
  denyUrls: [
3319
- // Common third-party extensions that pollute error streams.
3487
+ // The actionability principle
3488
+ // ---------------------------
3489
+ // Crossdeck's default philosophy is "classify, don't silently
3490
+ // drop" — surfacing errors the developer can fix is more useful
3491
+ // than hiding them. That's right for cross-origin "Script
3492
+ // error." (real, code-shaped CORS fix) and for plain
3493
+ // application bugs (obviously real).
3494
+ //
3495
+ // It's NOT right for events the developer cannot act on.
3496
+ // - A user's installed browser extension throwing inside
3497
+ // its own `chrome-extension://` URL: the developer can't
3498
+ // ship a fix for someone else's extension.
3499
+ // - An ad blocker preventing `googletagmanager.com` from
3500
+ // loading: the developer can't unblock the user's blocker.
3501
+ //
3502
+ // Capturing these creates a noise tab that's always non-empty
3503
+ // and never actionable, which trains the dev to ignore the
3504
+ // noise tab — and then the actionable noise (CORS opacity,
3505
+ // etc.) gets ignored along with it. Same lesson as Crossdeck's
3506
+ // "0-2 notifications a week" discipline: a signal that fires
3507
+ // constantly with nothing actionable behind it stops being a
3508
+ // signal.
3509
+ //
3510
+ // So we drop at the source for the unactionable category, and
3511
+ // keep capturing for the actionable category. Same principle
3512
+ // applied to two structurally different inputs — not a new
3513
+ // philosophy.
3514
+ //
3515
+ // The bootstrap list below is the minimum that ships in code.
3516
+ // The full, versioned list arrives at boot via /v1/config —
3517
+ // see `backend/src/lib/error-noise-deny-list.ts`. The SDK
3518
+ // applies the union of (bootstrap + remote), so a freshly-
3519
+ // installed SDK protects users immediately, and remote updates
3520
+ // (new ad-network domains, new pixel hosts) reach every
3521
+ // install without an SDK release.
3522
+ //
3523
+ // The backend Layer-2 classifier
3524
+ // (`backend/src/api/lib/noise-classifier.ts`) is the safety
3525
+ // net for events that slip past these patterns — older SDK
3526
+ // versions in the wild that haven't fetched the remote list
3527
+ // yet, or brand-new patterns the list hasn't named.
3320
3528
  /^chrome-extension:\/\//,
3321
3529
  /^moz-extension:\/\//,
3322
3530
  /^safari-extension:\/\//,
@@ -4109,7 +4317,12 @@ var CrossdeckClient = class {
4109
4317
  if (autoTrack.sessions || autoTrack.pageViews) {
4110
4318
  const tracker = new AutoTracker(
4111
4319
  autoTrack,
4112
- (name, properties) => this.track(name, properties)
4320
+ (name, properties) => this.track(name, properties),
4321
+ // Persist session continuity on the SAME adapter as identity, so
4322
+ // a visit survives full-page navigations (multi-page sites
4323
+ // re-install the SDK on every page) and honours the same consent
4324
+ // posture — MemoryStorage when identity persistence is off.
4325
+ { storage: effectiveStorage, storageKey: opts.storagePrefix + "session" }
4113
4326
  );
4114
4327
  this.state.autoTracker = tracker;
4115
4328
  tracker.install();
@@ -4574,6 +4787,12 @@ var CrossdeckClient = class {
4574
4787
  * true even before the session's first network round-trip. Returns
4575
4788
  * false only for a genuinely new install that has never completed a
4576
4789
  * getEntitlements(), or for an entitlement past its own validUntil.
4790
+ *
4791
+ * Throws `not_initialized` (CrossdeckError, type
4792
+ * `configuration_error`) if called before `Crossdeck.init()`. The
4793
+ * `useEntitlement` React hook and the Vue composable both swallow
4794
+ * this and return `false`; bare callers must guard for it (or call
4795
+ * after `init()` resolves).
4577
4796
  */
4578
4797
  isEntitled(key) {
4579
4798
  const s = this.requireStarted();
@@ -4589,8 +4808,21 @@ var CrossdeckClient = class {
4589
4808
  *
4590
4809
  * The listener is invoked AFTER the cache mutates — once after a
4591
4810
  * successful `getEntitlements()` warms it, again after `syncPurchases()`
4592
- * delivers fresh entitlements, and once on `reset()` to fire the
4593
- * empty-cache state for logout flows.
4811
+ * delivers fresh entitlements, once on `reset()` to fire the empty-
4812
+ * cache state for logout flows, AND once on `identify()` after the
4813
+ * per-user cache slot rotates and re-hydrates from device storage.
4814
+ *
4815
+ * IMPORTANT — the `identify()` fire is a TRAP if you treat it as
4816
+ * authoritative network state. `identify()` does NOT fetch entitlements;
4817
+ * it switches the per-user cache slot and rehydrates from device
4818
+ * storage (which is empty for a brand-new install, and last-known-good
4819
+ * — possibly stale — for a returning user). A listener that gates a
4820
+ * paywall on the first fire after an identity switch will read
4821
+ * `false` for a paying customer on a fresh device and let them past
4822
+ * the gate as free. The network-truth fire is the one that follows
4823
+ * the next `getEntitlements()` resolution. Either call
4824
+ * `getEntitlements()` explicitly after `identify()`, or have your
4825
+ * gating code tolerate the empty-then-populated transition.
4594
4826
  *
4595
4827
  * It is NOT invoked synchronously on subscribe. Callers that need
4596
4828
  * the current state should read it via `isEntitled()` / `listEntitlements()`
@@ -4699,6 +4931,7 @@ var CrossdeckClient = class {
4699
4931
  );
4700
4932
  }
4701
4933
  }
4934
+ s.autoTracker?.markActivity();
4702
4935
  const enriched = { ...s.deviceInfo };
4703
4936
  const sessionId = s.autoTracker?.currentSessionId;
4704
4937
  if (sessionId) enriched.sessionId = sessionId;
@@ -4744,7 +4977,7 @@ var CrossdeckClient = class {
4744
4977
  callerProperties: validation.properties,
4745
4978
  superProperties: supers,
4746
4979
  deviceProperties: s.deviceInfo,
4747
- mergedProperties: finalProperties
4980
+ mergedProperties: enriched
4748
4981
  });
4749
4982
  }
4750
4983
  if (!isError && !isWebVital) {
@@ -4953,6 +5186,25 @@ var CrossdeckClient = class {
4953
5186
  events: s.events.getStats()
4954
5187
  };
4955
5188
  }
5189
+ /**
5190
+ * The stable reference to hand Stripe at checkout so the resulting
5191
+ * purchase attributes to THIS person. Stamp it on the Checkout Session
5192
+ * as `metadata.crossdeck_ref` (see docs/connect-stripe) — the platform
5193
+ * webhook reads it back, validates it, and attaches the subscription to
5194
+ * the right customer instead of stranding it on an anonymous record.
5195
+ *
5196
+ * Returns the strongest identifier the SDK currently holds, in the same
5197
+ * precedence the server resolves by:
5198
+ * crossdeckCustomerId (cdcust_…) > developerUserId > anonymousId
5199
+ * anonymousId is always present, so this always returns a usable,
5200
+ * non-empty reference — even before identify(). Identify the user as
5201
+ * early as you can, though, so the reference is their stable identity
5202
+ * and not a per-device anon id.
5203
+ */
5204
+ getCheckoutReference() {
5205
+ const s = this.requireStarted();
5206
+ return s.identity.crossdeckCustomerId ?? s.developerUserId ?? s.identity.anonymousId;
5207
+ }
4956
5208
  // ---------- private helpers ----------
4957
5209
  requireStarted() {
4958
5210
  if (!this.state) {
@@ -5274,8 +5526,8 @@ function getErrorCode(code) {
5274
5526
  }
5275
5527
 
5276
5528
  // src/_contracts-bundled.ts
5277
- var BUNDLED_IN = "@cross-deck/web@1.5.3";
5278
- var SDK_VERSION2 = "1.5.3";
5529
+ var BUNDLED_IN = "@cross-deck/web@1.6.1";
5530
+ var SDK_VERSION2 = "1.6.1";
5279
5531
  var BUNDLED_CONTRACTS = Object.freeze([
5280
5532
  {
5281
5533
  "id": "contract-failed-payload-schema-lock",
@@ -5397,7 +5649,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5397
5649
  "legal/security/index.html#diagnostic",
5398
5650
  "legal/sdk-data/index.html#b-diagnostic"
5399
5651
  ],
5400
- "bundledIn": "@cross-deck/web@1.5.3"
5652
+ "bundledIn": "@cross-deck/web@1.6.1"
5401
5653
  },
5402
5654
  {
5403
5655
  "id": "error-envelope-shape",
@@ -5436,7 +5688,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5436
5688
  ],
5437
5689
  "registeredAt": "2026-05-26",
5438
5690
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 8 (codifies existing contract)",
5439
- "bundledIn": "@cross-deck/web@1.5.3"
5691
+ "bundledIn": "@cross-deck/web@1.6.1"
5440
5692
  },
5441
5693
  {
5442
5694
  "id": "flush-interval-parity",
@@ -5481,7 +5733,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5481
5733
  ],
5482
5734
  "registeredAt": "2026-05-26",
5483
5735
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.3",
5484
- "bundledIn": "@cross-deck/web@1.5.3"
5736
+ "bundledIn": "@cross-deck/web@1.6.1"
5485
5737
  },
5486
5738
  {
5487
5739
  "id": "idempotency-key-deterministic",
@@ -5586,7 +5838,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5586
5838
  ],
5587
5839
  "registeredAt": "2026-05-26",
5588
5840
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 2.2.a + 2.2.b + 2.2.c",
5589
- "bundledIn": "@cross-deck/web@1.5.3"
5841
+ "bundledIn": "@cross-deck/web@1.6.1"
5590
5842
  },
5591
5843
  {
5592
5844
  "id": "init-reentry-drains-prior-queue",
@@ -5613,7 +5865,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5613
5865
  ],
5614
5866
  "registeredAt": "2026-05-26",
5615
5867
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 5.5",
5616
- "bundledIn": "@cross-deck/web@1.5.3"
5868
+ "bundledIn": "@cross-deck/web@1.6.1"
5617
5869
  },
5618
5870
  {
5619
5871
  "id": "per-user-cache-isolation",
@@ -5692,7 +5944,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5692
5944
  ],
5693
5945
  "registeredAt": "2026-05-26",
5694
5946
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 1.3 (web/RN) + dogfood-gap fix (swift + android)",
5695
- "bundledIn": "@cross-deck/web@1.5.3"
5947
+ "bundledIn": "@cross-deck/web@1.6.1"
5696
5948
  },
5697
5949
  {
5698
5950
  "id": "sdk-error-codes-catalogue",
@@ -5732,7 +5984,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5732
5984
  ],
5733
5985
  "registeredAt": "2026-05-26",
5734
5986
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 6.2",
5735
- "bundledIn": "@cross-deck/web@1.5.3"
5987
+ "bundledIn": "@cross-deck/web@1.6.1"
5736
5988
  },
5737
5989
  {
5738
5990
  "id": "sync-purchases-funnel-parity",
@@ -5765,7 +6017,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5765
6017
  ],
5766
6018
  "registeredAt": "2026-05-26",
5767
6019
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.5",
5768
- "bundledIn": "@cross-deck/web@1.5.3"
6020
+ "bundledIn": "@cross-deck/web@1.6.1"
5769
6021
  }
5770
6022
  ]);
5771
6023