@cross-deck/web 1.5.7 → 1.6.2

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/CHANGELOG.md CHANGED
@@ -2,6 +2,78 @@
2
2
 
3
3
  All notable changes to `@cross-deck/web` will be documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4
4
 
5
+ ## [1.6.2] — 2026-05-31
6
+
7
+ Patch — session-boundary correctness. A stored session could span a gap
8
+ longer than the 30-minute inactivity window in two cases the resume logic
9
+ missed, fragmenting journey grouping. Both are now fixed. No public API
10
+ change; `session.ended` emission timing changes as described, so read
11
+ before upgrading if you have downstream logic keyed on it.
12
+
13
+ **A tab left open and idle, then used again, no longer stretches one
14
+ session across the gap.** `markActivity()` now rolls to a new session when
15
+ an event lands after the 30-minute window has lapsed — covering the case
16
+ the page-load and tab-return resume checks miss entirely (a tab kept open
17
+ and idle, then interacted with, with no visibility transition). A single
18
+ stored session can no longer contain a >30-minute gap.
19
+
20
+ **Returning to a long-idle tab no longer back-dates `session.ended`.** The
21
+ visibility-resume path emitted `session.ended` at the moment of return —
22
+ more than 30 minutes after the session's last real event — which itself
23
+ opened an intra-session gap. The prior session now ends implicitly (its
24
+ end inferred from its last event), consistent with the page-load resume
25
+ path. If you key downstream logic on `session.ended`, note it no longer
26
+ fires on a >30-minute tab-return.
27
+
28
+ ## [1.6.1] — 2026-05-30
29
+
30
+ Minor — two autocapture fidelity fixes, plus a repair to the release
31
+ pipeline itself. (1.6.0 carried the same two fixes but never reached npm:
32
+ its automated publish hit a pre-existing broken test gate — a schema-lock
33
+ test that loaded a contract via a monorepo-only path that doesn't exist
34
+ in the published package. Fixed by reading the contract from the copy
35
+ bundled into the SDK, so this is the first build of these changes to
36
+ ship.) No public API change; event emission behaviour changes as
37
+ described, so read before upgrading if you have downstream logic keyed on
38
+ `session.ended` timing.
39
+
40
+ **Sessions now survive full-page navigations.** Session state was
41
+ in-memory only, so on a multi-page site (where the SDK re-installs on
42
+ every navigation) one visit was split into a separate session per page —
43
+ each `session.ended` on `pagehide` landing at the same instant the next
44
+ page's `session.started` fired. Sessions are now persisted (id, start,
45
+ last-activity, first-touch acquisition) to the same storage adapter as
46
+ identity and **resumed** across page loads within a rolling 30-minute
47
+ inactivity window.
48
+
49
+ - `session.started` no longer fires on a resumed page load — only on a
50
+ genuinely new session (first visit, or first load after >30 min idle).
51
+ - `session.ended` no longer fires on `pagehide` / `beforeunload` (a
52
+ navigation is not a session end). It fires only on real 30-min
53
+ inactivity or an explicit `Crossdeck.stop()`.
54
+ - The inactivity window is bumped by every tracked event (auto or
55
+ custom), not just pageviews/clicks.
56
+ - Honours consent posture: with `persistIdentity: false` or a
57
+ `MemoryStorage` adapter, the session is in-memory only (per-page, the
58
+ prior behaviour).
59
+ - Continuity is same-origin (localStorage); cross-subdomain stitching is
60
+ not yet handled.
61
+
62
+ **Click autocapture no longer mashes labels.** A click on a control that
63
+ wraps other controls or a content block (a card around several buttons, a
64
+ hero `<a>` around a heading + paragraph) used to collapse the whole
65
+ subtree into one string — `"Log inContinue with GoogleContinue with
66
+ Apple…"`, `"Tudo que você é,em um só link.Portfolio…"`. The resolver now
67
+ returns the control's own label (word boundaries preserved, decorative
68
+ `svg`/`style`/`script` skipped), or for a wrapper its direct label / the
69
+ first heading inside it, falling through to the selector rather than a
70
+ concatenation. Attribute precedence (`data-*` → aria → value → text →
71
+ title → img/svg) is unchanged.
72
+
73
+ Bundle-size budget for the core bundles raised 55 → 58 KB gzipped (ESM +
74
+ CJS) to fit the ~1 KB of new code; react/vue/UMD bundles unchanged and
75
+ still under budget.
76
+
5
77
  ## [1.5.1] — 2026-05-27
6
78
 
7
79
  `crossdeck.contract_failed` is now single-fire to a dedicated
package/README.md CHANGED
@@ -86,13 +86,15 @@ That's the full happy path.
86
86
 
87
87
  | Event | When |
88
88
  |---|---|
89
- | `session.started` | On boot. Carries `sessionId`. |
90
- | `session.ended` | On `pagehide` / `beforeunload`, OR when returning to a tab after >30 min idle. Carries `sessionId` and `durationMs`. |
89
+ | `session.started` | When a **new** session begins — first visit, or the first page load after >30 min of inactivity. A session resumed across a page navigation does **not** re-fire it. Carries `sessionId`. |
90
+ | `session.ended` | On a real session end only: >30 min of inactivity, or an explicit `Crossdeck.stop()`. Carries `sessionId` and `durationMs`. |
91
91
  | `page.viewed` | On initial load + every SPA navigation (`history.pushState`, `replaceState`, `popstate`). Carries `path`, `url`, `search`, `hash`, `title`, `referrer`. |
92
92
 
93
- Every event — auto-tracked and developer-emitted — is enriched with the device-info payload below. Quick tab switches (Cmd-Tab, switching browser tabs) don't end the session — only real closes do, matching GA4's session-window convention.
93
+ Every event — auto-tracked and developer-emitted — is enriched with the device-info payload below.
94
94
 
95
- **Per-session acquisition (v0.6.0+):** when a session starts the SDK reads `window.location.search` and `document.referrer` and captures `utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term`, plus `referrer`. Non-empty values are auto-attached to every subsequent event of that session first-touch attribution stays pinned to the entry URL even after SPA route changes strip the params away. A new session (>30 min idle) re-reads the URL.
95
+ **Sessions survive page loads (v1.6.0+).** A session is a *visit*, not a page. The SDK persists the session (id, start time, last-activity time, first-touch acquisition) to the same storage as identity, so a full-page navigation on a multi-page site **resumes the same session** rather than starting a new one. The window is a rolling 30-minute inactivity timer bumped by every tracked event (auto or custom); only real 30-min inactivity or `Crossdeck.stop()` ends it. A page unload (`pagehide`/`beforeunload`) is treated as a navigation, not an end. Quick tab switches (Cmd-Tab) likewise don't end the session — matching GA4's session-window convention. Continuity is same-origin; cross-subdomain stitching is on the roadmap. With `persistIdentity: false` (or a `MemoryStorage` adapter) the session is in-memory only and resets per page.
96
+
97
+ **Per-session acquisition (v0.6.0+):** when a session starts the SDK reads `window.location.search` and `document.referrer` and captures `utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term`, plus `referrer`. Non-empty values are auto-attached to every subsequent event of that session — first-touch attribution stays pinned to the entry URL even after SPA route changes strip the params away, and now stays pinned across full-page navigations within the session too. A new session (>30 min idle) re-reads the URL.
96
98
 
97
99
  ## Auto-attached device info
98
100
 
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "generatedAt": "2026-05-28T10:12:32.152Z",
3
+ "generatedAt": "2026-06-01T05:25:25.624Z",
4
4
  "sdk": "@cross-deck/web",
5
- "sdkVersion": "1.5.7",
6
- "bundledIn": "@cross-deck/web@1.5.7",
7
- "count": 8,
5
+ "sdkVersion": "1.6.2",
6
+ "bundledIn": "@cross-deck/web@1.6.2",
7
+ "count": 9,
8
8
  "contracts": [
9
9
  {
10
10
  "id": "contract-failed-payload-schema-lock",
@@ -126,7 +126,7 @@
126
126
  "legal/security/index.html#diagnostic",
127
127
  "legal/sdk-data/index.html#b-diagnostic"
128
128
  ],
129
- "bundledIn": "@cross-deck/web@1.5.7"
129
+ "bundledIn": "@cross-deck/web@1.6.2"
130
130
  },
131
131
  {
132
132
  "id": "error-envelope-shape",
@@ -165,7 +165,7 @@
165
165
  ],
166
166
  "registeredAt": "2026-05-26",
167
167
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 — phase 8 (codifies existing contract)",
168
- "bundledIn": "@cross-deck/web@1.5.7"
168
+ "bundledIn": "@cross-deck/web@1.6.2"
169
169
  },
170
170
  {
171
171
  "id": "flush-interval-parity",
@@ -210,7 +210,7 @@
210
210
  ],
211
211
  "registeredAt": "2026-05-26",
212
212
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 — phase 3.3",
213
- "bundledIn": "@cross-deck/web@1.5.7"
213
+ "bundledIn": "@cross-deck/web@1.6.2"
214
214
  },
215
215
  {
216
216
  "id": "idempotency-key-deterministic",
@@ -315,7 +315,7 @@
315
315
  ],
316
316
  "registeredAt": "2026-05-26",
317
317
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 — phase 2.2.a + 2.2.b + 2.2.c",
318
- "bundledIn": "@cross-deck/web@1.5.7"
318
+ "bundledIn": "@cross-deck/web@1.6.2"
319
319
  },
320
320
  {
321
321
  "id": "init-reentry-drains-prior-queue",
@@ -342,7 +342,7 @@
342
342
  ],
343
343
  "registeredAt": "2026-05-26",
344
344
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 — phase 5.5",
345
- "bundledIn": "@cross-deck/web@1.5.7"
345
+ "bundledIn": "@cross-deck/web@1.6.2"
346
346
  },
347
347
  {
348
348
  "id": "per-user-cache-isolation",
@@ -421,7 +421,7 @@
421
421
  ],
422
422
  "registeredAt": "2026-05-26",
423
423
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 — phase 1.3 (web/RN) + dogfood-gap fix (swift + android)",
424
- "bundledIn": "@cross-deck/web@1.5.7"
424
+ "bundledIn": "@cross-deck/web@1.6.2"
425
425
  },
426
426
  {
427
427
  "id": "sdk-error-codes-catalogue",
@@ -435,9 +435,14 @@
435
435
  "codeRef": [
436
436
  "sdks/web/src/error-codes.ts",
437
437
  "sdks/node/src/error-codes.ts",
438
+ "sdks/web/src/_contract-verifiers.ts",
438
439
  "backend/src/api/v1-errors.ts"
439
440
  ],
440
441
  "testRef": [
442
+ {
443
+ "file": "sdks/web/tests/contract-verifiers.test.ts",
444
+ "name": "sdk-error-codes-catalogue covers every backend wire code with remediation"
445
+ },
441
446
  {
442
447
  "file": "sdks/web/tests/error-codes-backfill.test.ts",
443
448
  "name": "includes backend code"
@@ -461,7 +466,48 @@
461
466
  ],
462
467
  "registeredAt": "2026-05-26",
463
468
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 — phase 6.2",
464
- "bundledIn": "@cross-deck/web@1.5.7"
469
+ "bundledIn": "@cross-deck/web@1.6.2"
470
+ },
471
+ {
472
+ "id": "super-property-merge-precedence",
473
+ "pillar": "analytics",
474
+ "status": "enforced",
475
+ "claim": "Every Crossdeck SDK merges event properties with the precedence device < super < caller (caller-supplied values win over registered super-properties, which win over auto-attached device info). Pre-v1.4.0 Swift had it INVERTED (super < device < caller — device clobbered super), so a `register('plan', 'pro')` super-property was silently overridden by auto-attached device fields whenever keys collided. Cross-SDK funnel queries on super-property keys returned different answers per platform.",
476
+ "appliesTo": [
477
+ "web",
478
+ "swift"
479
+ ],
480
+ "codeRef": [
481
+ "sdks/web/src/super-properties.ts",
482
+ "sdks/web/src/_contract-verifiers.ts",
483
+ "sdks/swift/Sources/Crossdeck/EventPropertyMerge.swift",
484
+ "sdks/swift/Sources/Crossdeck/Crossdeck.swift"
485
+ ],
486
+ "testRef": [
487
+ {
488
+ "file": "sdks/web/tests/contract-verifiers.test.ts",
489
+ "name": "super-property-merge-precedence verifies caller > super > device"
490
+ },
491
+ {
492
+ "file": "sdks/swift/Tests/CrossdeckTests/EventPropertyMergeTests.swift",
493
+ "name": "test_super_overrides_device"
494
+ },
495
+ {
496
+ "file": "sdks/swift/Tests/CrossdeckTests/EventPropertyMergeTests.swift",
497
+ "name": "test_caller_overrides_super"
498
+ },
499
+ {
500
+ "file": "sdks/swift/Tests/CrossdeckTests/EventPropertyMergeTests.swift",
501
+ "name": "test_full_precedence_chain"
502
+ },
503
+ {
504
+ "file": "sdks/swift/Tests/CrossdeckTests/EventPropertyMergeTests.swift",
505
+ "name": "test_matchesWebNodeRNPrecedence"
506
+ }
507
+ ],
508
+ "registeredAt": "2026-05-26",
509
+ "firstRegisteredIn": "bank-grade reconciliation v1.4.0 — phase 3.2",
510
+ "bundledIn": "@cross-deck/web@1.6.2"
465
511
  },
466
512
  {
467
513
  "id": "sync-purchases-funnel-parity",
@@ -494,7 +540,7 @@
494
540
  ],
495
541
  "registeredAt": "2026-05-26",
496
542
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 — phase 3.5",
497
- "bundledIn": "@cross-deck/web@1.5.7"
543
+ "bundledIn": "@cross-deck/web@1.6.2"
498
544
  }
499
545
  ]
500
546
  }