@qoretechnologies/reqraft 0.10.56 → 0.10.57-pr.127.g0a792ea

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 (157) hide show
  1. package/.claude/CLAUDE.md +1 -0
  2. package/README.md +20 -0
  3. package/design/TRACKING.md +290 -0
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +5 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/tracking/browser/attach.d.ts +8 -0
  9. package/dist/tracking/browser/attach.d.ts.map +1 -0
  10. package/dist/tracking/browser/attach.js +182 -0
  11. package/dist/tracking/browser/attach.js.map +1 -0
  12. package/dist/tracking/browser/env.d.ts +14 -0
  13. package/dist/tracking/browser/env.d.ts.map +1 -0
  14. package/dist/tracking/browser/env.js +150 -0
  15. package/dist/tracking/browser/env.js.map +1 -0
  16. package/dist/tracking/core/consent.d.ts +21 -0
  17. package/dist/tracking/core/consent.d.ts.map +1 -0
  18. package/dist/tracking/core/consent.js +39 -0
  19. package/dist/tracking/core/consent.js.map +1 -0
  20. package/dist/tracking/core/engagement.d.ts +20 -0
  21. package/dist/tracking/core/engagement.d.ts.map +1 -0
  22. package/dist/tracking/core/engagement.js +52 -0
  23. package/dist/tracking/core/engagement.js.map +1 -0
  24. package/dist/tracking/core/experiments.d.ts +109 -0
  25. package/dist/tracking/core/experiments.d.ts.map +1 -0
  26. package/dist/tracking/core/experiments.js +294 -0
  27. package/dist/tracking/core/experiments.js.map +1 -0
  28. package/dist/tracking/core/hash.d.ts +16 -0
  29. package/dist/tracking/core/hash.d.ts.map +1 -0
  30. package/dist/tracking/core/hash.js +31 -0
  31. package/dist/tracking/core/hash.js.map +1 -0
  32. package/dist/tracking/core/ids.d.ts +6 -0
  33. package/dist/tracking/core/ids.d.ts.map +1 -0
  34. package/dist/tracking/core/ids.js +31 -0
  35. package/dist/tracking/core/ids.js.map +1 -0
  36. package/dist/tracking/core/observable.d.ts +12 -0
  37. package/dist/tracking/core/observable.d.ts.map +1 -0
  38. package/dist/tracking/core/observable.js +32 -0
  39. package/dist/tracking/core/observable.js.map +1 -0
  40. package/dist/tracking/core/queue.d.ts +47 -0
  41. package/dist/tracking/core/queue.d.ts.map +1 -0
  42. package/dist/tracking/core/queue.js +205 -0
  43. package/dist/tracking/core/queue.js.map +1 -0
  44. package/dist/tracking/core/sections.d.ts +54 -0
  45. package/dist/tracking/core/sections.d.ts.map +1 -0
  46. package/dist/tracking/core/sections.js +156 -0
  47. package/dist/tracking/core/sections.js.map +1 -0
  48. package/dist/tracking/core/storage.d.ts +15 -0
  49. package/dist/tracking/core/storage.d.ts.map +1 -0
  50. package/dist/tracking/core/storage.js +72 -0
  51. package/dist/tracking/core/storage.js.map +1 -0
  52. package/dist/tracking/core/tracker.d.ts +122 -0
  53. package/dist/tracking/core/tracker.d.ts.map +1 -0
  54. package/dist/tracking/core/tracker.js +390 -0
  55. package/dist/tracking/core/tracker.js.map +1 -0
  56. package/dist/tracking/core/types.d.ts +130 -0
  57. package/dist/tracking/core/types.d.ts.map +1 -0
  58. package/dist/tracking/core/types.js +69 -0
  59. package/dist/tracking/core/types.js.map +1 -0
  60. package/dist/tracking/core/utm.d.ts +6 -0
  61. package/dist/tracking/core/utm.d.ts.map +1 -0
  62. package/dist/tracking/core/utm.js +30 -0
  63. package/dist/tracking/core/utm.js.map +1 -0
  64. package/dist/tracking/index.d.ts +46 -0
  65. package/dist/tracking/index.d.ts.map +1 -0
  66. package/dist/tracking/index.js +91 -0
  67. package/dist/tracking/index.js.map +1 -0
  68. package/dist/tracking/markup.d.ts +12 -0
  69. package/dist/tracking/markup.d.ts.map +1 -0
  70. package/dist/tracking/markup.js +19 -0
  71. package/dist/tracking/markup.js.map +1 -0
  72. package/dist/tracking/react/Experiment.d.ts +23 -0
  73. package/dist/tracking/react/Experiment.d.ts.map +1 -0
  74. package/dist/tracking/react/Experiment.js +28 -0
  75. package/dist/tracking/react/Experiment.js.map +1 -0
  76. package/dist/tracking/react/TrackingProvider.d.ts +26 -0
  77. package/dist/tracking/react/TrackingProvider.d.ts.map +1 -0
  78. package/dist/tracking/react/TrackingProvider.js +45 -0
  79. package/dist/tracking/react/TrackingProvider.js.map +1 -0
  80. package/dist/tracking/react/hooks.d.ts +39 -0
  81. package/dist/tracking/react/hooks.d.ts.map +1 -0
  82. package/dist/tracking/react/hooks.js +96 -0
  83. package/dist/tracking/react/hooks.js.map +1 -0
  84. package/dist/tracking/react/index.d.ts +7 -0
  85. package/dist/tracking/react/index.d.ts.map +1 -0
  86. package/dist/tracking/react/index.js +17 -0
  87. package/dist/tracking/react/index.js.map +1 -0
  88. package/dist/tracking/testing.d.ts +40 -0
  89. package/dist/tracking/testing.d.ts.map +1 -0
  90. package/dist/tracking/testing.js +140 -0
  91. package/dist/tracking/testing.js.map +1 -0
  92. package/dist/tracking/ui/ExperimentCard.d.ts +55 -0
  93. package/dist/tracking/ui/ExperimentCard.d.ts.map +1 -0
  94. package/dist/tracking/ui/ExperimentCard.js +340 -0
  95. package/dist/tracking/ui/ExperimentCard.js.map +1 -0
  96. package/dist/tracking/ui/ExperimentsOverlay.d.ts +59 -0
  97. package/dist/tracking/ui/ExperimentsOverlay.d.ts.map +1 -0
  98. package/dist/tracking/ui/ExperimentsOverlay.js +359 -0
  99. package/dist/tracking/ui/ExperimentsOverlay.js.map +1 -0
  100. package/dist/tracking/ui/anchor.d.ts +107 -0
  101. package/dist/tracking/ui/anchor.d.ts.map +1 -0
  102. package/dist/tracking/ui/anchor.js +108 -0
  103. package/dist/tracking/ui/anchor.js.map +1 -0
  104. package/dist/tracking/ui/card.d.ts +121 -0
  105. package/dist/tracking/ui/card.d.ts.map +1 -0
  106. package/dist/tracking/ui/card.js +417 -0
  107. package/dist/tracking/ui/card.js.map +1 -0
  108. package/dist/tracking/ui/client.d.ts +42 -0
  109. package/dist/tracking/ui/client.d.ts.map +1 -0
  110. package/dist/tracking/ui/client.js +123 -0
  111. package/dist/tracking/ui/client.js.map +1 -0
  112. package/dist/tracking/ui/index.d.ts +20 -0
  113. package/dist/tracking/ui/index.d.ts.map +1 -0
  114. package/dist/tracking/ui/index.js +60 -0
  115. package/dist/tracking/ui/index.js.map +1 -0
  116. package/dist/tracking/ui/types.d.ts +120 -0
  117. package/dist/tracking/ui/types.d.ts.map +1 -0
  118. package/dist/tracking/ui/types.js +8 -0
  119. package/dist/tracking/ui/types.js.map +1 -0
  120. package/dist/tracking/ui/useExperimentsAdmin.d.ts +31 -0
  121. package/dist/tracking/ui/useExperimentsAdmin.d.ts.map +1 -0
  122. package/dist/tracking/ui/useExperimentsAdmin.js +136 -0
  123. package/dist/tracking/ui/useExperimentsAdmin.js.map +1 -0
  124. package/package.json +1 -1
  125. package/src/index.tsx +5 -0
  126. package/src/stories/tracking.tsx +206 -0
  127. package/src/tracking/browser/attach.ts +186 -0
  128. package/src/tracking/browser/env.ts +78 -0
  129. package/src/tracking/core/consent.ts +37 -0
  130. package/src/tracking/core/engagement.ts +47 -0
  131. package/src/tracking/core/experiments.ts +258 -0
  132. package/src/tracking/core/hash.ts +26 -0
  133. package/src/tracking/core/ids.ts +25 -0
  134. package/src/tracking/core/observable.ts +23 -0
  135. package/src/tracking/core/queue.ts +131 -0
  136. package/src/tracking/core/sections.ts +166 -0
  137. package/src/tracking/core/storage.ts +69 -0
  138. package/src/tracking/core/tracker.ts +417 -0
  139. package/src/tracking/core/types.ts +162 -0
  140. package/src/tracking/core/utm.ts +23 -0
  141. package/src/tracking/index.ts +72 -0
  142. package/src/tracking/markup.ts +17 -0
  143. package/src/tracking/react/Experiment.tsx +44 -0
  144. package/src/tracking/react/TrackingProvider.tsx +56 -0
  145. package/src/tracking/react/hooks.ts +106 -0
  146. package/src/tracking/react/index.ts +13 -0
  147. package/src/tracking/testing.ts +108 -0
  148. package/src/tracking/ui/ExperimentCard.stories.tsx +290 -0
  149. package/src/tracking/ui/ExperimentCard.tsx +581 -0
  150. package/src/tracking/ui/ExperimentsOverlay.stories.tsx +155 -0
  151. package/src/tracking/ui/ExperimentsOverlay.tsx +437 -0
  152. package/src/tracking/ui/anchor.ts +180 -0
  153. package/src/tracking/ui/card.ts +464 -0
  154. package/src/tracking/ui/client.ts +117 -0
  155. package/src/tracking/ui/index.ts +77 -0
  156. package/src/tracking/ui/types.ts +110 -0
  157. package/src/tracking/ui/useExperimentsAdmin.ts +103 -0
package/.claude/CLAUDE.md CHANGED
@@ -307,6 +307,7 @@ yarn build:test # Type-check without emit
307
307
  | `src/components/form/` | Form field components |
308
308
  | `src/components/log/` | Log display component |
309
309
  | `src/components/menu/` | Navigation menu component |
310
+ | `src/tracking/` | First-party tracking, consent and A/B testing (`core/` no DOM, `browser/`, `react/`, and `ui/`: the A/B test card and overlay on Reqore); how-to in `design/TRACKING.md` |
310
311
  | `src/types/` | Shared TypeScript types |
311
312
  | `__tests__/` | Vitest unit tests (jsdom) |
312
313
 
package/README.md CHANGED
@@ -148,6 +148,26 @@ client.onNotification('qonsole/sessionStateChanged', (params) => { /* … */ });
148
148
  client.disconnect();
149
149
  ```
150
150
 
151
+ ## Tracking, consent and A/B testing
152
+
153
+ First-party analytics with a consent gate and client-side A/B testing, for any Qore web app. The core is plain TypeScript with no DOM, the browser layer attaches the listeners, and the React bindings sit on top. Everything product-specific comes in through config; the server keeps each product's data separate by `property`.
154
+
155
+ ```tsx
156
+ import { createBrowserTracker, Experiment, TrackingProvider } from '@qoretechnologies/reqraft/dist/tracking';
157
+
158
+ const tracking = createBrowserTracker({
159
+ property: 'ide',
160
+ endpoint: '/qorus-cloud/collect',
161
+ experimentsEndpoint: '/qorus-cloud/experiments',
162
+ });
163
+
164
+ <TrackingProvider tracker={tracking.tracker} attach={tracking.attach} location={pathname + search}>
165
+ <Experiment id='signup-cta' variants={{ a: <Current />, b: <New /> }} />
166
+ </TrackingProvider>;
167
+ ```
168
+
169
+ `@qoretechnologies/reqraft/dist/tracking` loads nothing else from Reqraft; the same names are exported from the package root. The A/B test UI every product shows (`<ExperimentsOverlay>`, `<ExperimentCard>`, `useExperimentsAdmin`) is in `@qoretechnologies/reqraft/dist/tracking/ui`. Config, consent, `useExperiment`, `trackClick` / `trackSection` markup, `createMemoryTracker` for tests and the server contract: [design/TRACKING.md](design/TRACKING.md).
170
+
151
171
  Clients on the same LSP endpoint share ONE underlying WebSocket — each `ReqraftLspClient` is a per-document facade over the shared connection, multiplexed by document URI (the server keys language sessions per document), so N editors cost one socket. Auto-reconnect re-opens every document on the shared socket, 15s request timeout, request/response correlation by `id`, pending requests rejected on close. A `connect()` never outlives its socket: once the socket exhausts `maxReconnectTries`, a handshake still waiting rejects, and the next `connect()` — from that client or any other — dials again, so an editor opened after an outage connects once the server is back, without a page reload. Document URIs should be opaque and client-generated — per the Qonsole LSP contract they must not contain session tokens, usernames, sandbox identifiers, or other secrets that end up in server logs.
152
172
 
153
173
  ## Community
@@ -0,0 +1,290 @@
1
+ # Tracking, consent and A/B testing
2
+
3
+ First-party analytics, a consent gate and client-side A/B testing for any Qore web app (the
4
+ landing site, the Qorus IDE, the admin portal). One module, one server: every product sends to
5
+ the same collector and reads the same experiments, and **the server keeps each product's data
6
+ separate by `property`**: events, goals, experiments and results never mix between properties.
7
+
8
+ The contract (event shapes, the batch envelope, experiment definitions, goals, statistics,
9
+ storage, the admin API) is owned by qorus-api:
10
+ [`design/analytics-and-experiments-design.md`](https://git.qoretechnologies.com/qorus/qorus-api/-/blob/develop/design/analytics-and-experiments-design.md)
11
+ (introduced in [qorus-api MR !34](https://git.qoretechnologies.com/qorus/qorus-api/-/merge_requests/34)).
12
+ Change the contract there first, then the code here. Section numbers below (§3, §5 …) refer to it.
13
+
14
+ ## 1. What it does
15
+
16
+ - **Anonymous, with consent.** Nothing is sent and no id exists until the user allows tracking.
17
+ Then a random visitor id (`vid`) lives in `localStorage["<prefix>vid"]`. Declining or
18
+ withdrawing deletes it and stops everything.
19
+ - **Events** go in batches to the collector: page views, a page summary (time, engaged time,
20
+ scroll depth, per-section attention), clicks on tagged elements, a heartbeat, goals, custom
21
+ events and experiment exposures.
22
+ - **Experiments** are fetched from the experiments endpoint (`?property=<property>` appended)
23
+ and every visitor is assigned locally with GrowthBook's hash v2 on the `vid`, the same way on
24
+ every visit and every page.
25
+ - **Fails open.** No backend, no consent, blocked storage, an error anywhere: the app works as
26
+ without the module, every experiment shows its control, and nothing is thrown into the page.
27
+
28
+ ## 2. Install and import
29
+
30
+ ```bash
31
+ yarn add @qoretechnologies/reqraft
32
+ ```
33
+
34
+ Everything is exported from the package root, and from its own entry, which loads nothing else
35
+ from Reqraft (no Reqore, react-query or editors; React is its only dependency). Prefer the entry
36
+ in an app that does not already use Reqraft:
37
+
38
+ ```ts
39
+ import { createBrowserTracker, TrackingProvider, Experiment } from '@qoretechnologies/reqraft/dist/tracking';
40
+ // or
41
+ import { createBrowserTracker, TrackingProvider, Experiment } from '@qoretechnologies/reqraft';
42
+ ```
43
+
44
+ The module has three layers, and each only imports the ones above it:
45
+
46
+ | Layer | Folder | What |
47
+ |---|---|---|
48
+ | core | `src/tracking/core/` | plain TypeScript, no DOM: `Tracker`, consent, the batching queue, section and engagement clocks, hashing, assignment, the experiments client |
49
+ | browser | `src/tracking/browser/` | the only code that touches `window` / `document`: transport (`sendBeacon` → `fetch` keepalive), storage, listeners, `IntersectionObserver` on sections, delegated clicks |
50
+ | react | `src/tracking/react/` | `<TrackingProvider>`, `useTracker`, `useConsent`, `useExperiment`, `<Experiment>` |
51
+
52
+ Plus `markup.ts` (`trackClick`, `trackSection`) and `testing.ts` (`createMemoryTracker`). The A/B test UI (the card and the overlay, on Reqore) is a fourth layer, `src/tracking/ui/`, with its own entry (§8); the engine never imports it.
53
+
54
+ ## 3. Configure one tracker per app
55
+
56
+ Create the tracker once, at module level, and mount the provider at the app shell:
57
+
58
+ ```tsx
59
+ import { createBrowserTracker, TrackingProvider } from '@qoretechnologies/reqraft/dist/tracking';
60
+
61
+ export const tracking = createBrowserTracker({
62
+ property: 'ide', // required: which product the data belongs to
63
+ endpoint: '/qorus-cloud/collect', // required: the collector (POST)
64
+ experimentsEndpoint: '/qorus-cloud/experiments', // required: running experiments (GET)
65
+ storagePrefix: 'qa.ide.', // when another product shares the origin
66
+ consentVersion: 1,
67
+ enabled: import.meta.env.PROD,
68
+ debug: false,
69
+ });
70
+
71
+ const App = () => {
72
+ const location = useLocation();
73
+ return (
74
+ <TrackingProvider tracker={tracking.tracker} attach={tracking.attach} location={location.pathname + location.search}>
75
+ <Routes>…</Routes>
76
+ </TrackingProvider>
77
+ );
78
+ };
79
+ ```
80
+
81
+ Each `location` change is a new page view. Without a provider (or with `tracker={null}`),
82
+ everything still renders: consent reads `unknown` and experiments show their control.
83
+
84
+ | Option | Default | Meaning |
85
+ |---|---|---|
86
+ | `property` | required | The product (`landing`, `ide`, `admin`, …). Sent with every batch and with the experiments request; the server keeps each property's data apart. |
87
+ | `endpoint` | required | Collector URL. Same origin in the products so far. |
88
+ | `experimentsEndpoint` | required | Running-experiments URL; `?property=` is appended. |
89
+ | `enabled` | `true` | `false`: nothing is ever sent and every experiment shows its control. Turn it off where a page view is not a visit (a preview iframe, a test build). |
90
+ | `consentVersion` | `1` | Bump when the consent text changes materially: stored answers to older versions count as no answer, so the prompt asks again. |
91
+ | `storagePrefix` | `qa.` | Prefix of every storage key. **Two products on one origin need different prefixes**, or they share one consent answer, one visitor id and one experiments cache. |
92
+ | `previewParam` | `qa_variant` | Query parameter of a shareable preview link. |
93
+ | `debug` | `false` | Log every batch (and every swallowed error) to the console. |
94
+ | `flushIntervalMs`, `maxQueued`, `heartbeatMs`, `sessionTimeoutMs`, `engagedWindowMs` | 5 s, 20, 15 s, 30 min, 10 s | Batching and timing. |
95
+
96
+ `resolveConfig(options)` gives the full config; `DEFAULT_CONFIG` holds the defaults.
97
+
98
+ ## 4. Consent
99
+
100
+ ```tsx
101
+ const { state, grant, deny, available } = useConsent(); // state: 'unknown' | 'granted' | 'denied'
102
+ ```
103
+
104
+ - Stored in `localStorage["<prefix>consent"]` (`granted` | `denied`) and `<prefix>consent.v`
105
+ (the version answered).
106
+ - Before `granted`: no id, no request to the collector, controls only, sign-up links
107
+ undecorated. The experiments list is still fetched: it is public and carries no visitor data.
108
+ - `deny()` (declining or withdrawing) deletes `vid`, `exp`, `sid` and `utm` and drops unsent events.
109
+ - The consent prompt is the app's own UI. `openConsentSettings()` / `closeConsentSettings()` /
110
+ `useConsentPanelOpen()` let a "Cookie settings" link reopen it.
111
+
112
+ ## 5. Experiments
113
+
114
+ Wrap what is being tested:
115
+
116
+ ```tsx
117
+ <Experiment id='signup-cta' trigger='pricing' variants={{ a: <Current />, b: <New /> }} />
118
+ ```
119
+
120
+ or, for logic rather than markup:
121
+
122
+ ```tsx
123
+ const { variant, assignment, triggerRef } = useExperiment('signup-cta', { variants: ['a', 'b'] });
124
+ ```
125
+
126
+ - `a` is the control unless `control="…"` says otherwise; the control must be today's behaviour.
127
+ - **The variant is decided once when the component mounts** and kept for the page view: no
128
+ flicker. A consent or definition that arrives later applies from the next page view.
129
+ - **Exposure** is logged once per page view, only for enrolled visitors, when the trigger is
130
+ seen: the definition's `trigger.section`, else the `trigger` prop, else the element you put
131
+ `triggerRef` on, else at render.
132
+ - Visitors without consent, outside `traffic`, or not targeted see the control and log nothing.
133
+ A concluded experiment shows its winner to everybody and logs nothing.
134
+ - Order of `tracker.decide()`: a concluded winner, then the in-app switch or a preview link, then
135
+ consent, the definition and the assignment. `assignment.reason` says which one decided.
136
+ - `<Experiment>` wraps its output in a `display: contents` element (no box, so the layout is
137
+ unchanged) with `data-experiment`, `data-variant`, `data-variants` and
138
+ `data-experiment-reason`, so an editor overlay can find every test on the page.
139
+ - **Preview link:** `?qa_variant=signup-cta:b` (several: `?qa_variant=a-test:b,other:a`) shows a
140
+ version for QA, sticks for the browser session, works without consent and is never logged.
141
+ `?qa_variant=clear` removes it.
142
+ - **In-app switch** (for an editor): `tracker.experiments.setEditorOverride(key, variant | null)`
143
+ shows one version in this browser, live and never logged; `tracker.experiments.refresh()`
144
+ reloads the definitions and makes mounted experiments decide again.
145
+
146
+ Assignment (§5.2): `n = fnv1a32(String(fnv1a32(key + vid))) % 10000 / 10000`; variant *i* owns
147
+ `[c_i, c_i + traffic × w_i)` where `c_i` is the sum of the weights before it. Raising `traffic`
148
+ never moves an enrolled visitor. Fixed vectors: `__tests__/tracking/hash.test.ts`; if one of them
149
+ changes, every visitor's assignment changes.
150
+
151
+ The experiment itself (key, variants, weights, traffic, targeting, trigger, metrics) is created
152
+ and started through the admin API (§9); until it runs, everybody sees the control.
153
+
154
+ ## 6. Events, goals and markup
155
+
156
+ ```ts
157
+ const tracker = useTracker(); // null outside the provider: always use ?.
158
+ tracker?.goal('contact-submitted'); // a conversion the server's goals can match
159
+ tracker?.goal('demo-booked', 1); // with a value
160
+ tracker?.custom('plan-picked', { plan: 'pro' }); // a named event with props
161
+ ```
162
+
163
+ Never put free text a user typed, emails or names in props.
164
+
165
+ **Sections** (visible time, "seen", exit section) and **clicks** are tagged in markup; one
166
+ `IntersectionObserver` and one delegated click listener pick them up:
167
+
168
+ ```tsx
169
+ <section data-track-section='pricing'>…</section>
170
+ <ReqoreButton data-track-click='get-started' data-track-placement='hero'>Get started</ReqoreButton>
171
+ // where JSX attributes cannot be written (object-literal props, e.g. panel actions):
172
+ <ReqorePanel actions={[{ ...trackClick('contact', 'pricing-panel'), label: 'Talk to us' }]} />
173
+ <div {...trackSection('pricing')}>…</div>
174
+ ```
175
+
176
+ - `data-track-click` is what the click means (`get-started`, `contact`, …);
177
+ `data-track-placement` is where (defaults to the enclosing section's id);
178
+ `data-track-label` overrides the label (defaults to the element's text).
179
+ - "Seen" is at least 50 % of the section, or 50 % of the screen covered by it, for at least one
180
+ second in one stretch, with the tab visible. Reports key on section ids: keep them stable.
181
+
182
+ **Sign-up attribution** (§5.4): `tracker.decorateSignupUrl(url)` adds
183
+ `qa_exp=<experiment>:<variant>,…` and the session's first-touch `utm_*` to a sign-up link (with
184
+ consent only, never the `vid`); the server's sign-up flow reads them.
185
+
186
+ ## 7. Tests and stories
187
+
188
+ `createMemoryTracker(options)` is a tracker with no browser behind it: memory storage, a clock
189
+ and timers you drive, and a transport that records every batch.
190
+
191
+ ```ts
192
+ import { createMemoryTracker } from '@qoretechnologies/reqraft/dist/tracking';
193
+
194
+ const m = createMemoryTracker({ consent: 'granted', vid: 'visitor-1', experiments: [definition] });
195
+ m.tracker.start();
196
+ await m.advance(5000); // runs due timers: the queue flushes
197
+ m.sent[0].batch.events; // what reached the collector
198
+ m.local.get(m.keys.vid); // storage, under the configured prefix
199
+ ```
200
+
201
+ Options: `config` (any config field; `property` defaults to `test`), `consent`, `experiments`
202
+ (a list, a function read on every load, or `'fail'`), `page` (path, search, device, …), `vid`,
203
+ `start` (the clock), `seed` (localStorage before the tracker starts). In a story, pass
204
+ `m.tracker` to `<TrackingProvider>` without `attach`; seed the experiments cache
205
+ (`KEYS.experimentsCache`) so the first render already has the definitions — see
206
+ `src/tracking/ui/ExperimentsOverlay.stories.tsx`.
207
+
208
+ Unit tests: `__tests__/tracking/` (hashing vectors, assignment, consent gating, batching,
209
+ sections, page views, sign-up links, concluded winners, the in-app switch, product config,
210
+ the React bindings, and the layer rules).
211
+
212
+ ## 8. The A/B test UI: embed the overlay
213
+
214
+ The same A/B test controls every product shows, built on Reqore, in their own entry so the engine
215
+ stays light:
216
+
217
+ ```ts
218
+ import { ExperimentsOverlay, ExperimentCard, useExperimentsAdmin } from '@qoretechnologies/reqraft/dist/tracking/ui';
219
+ ```
220
+
221
+ | Export | What |
222
+ |---|---|
223
+ | `<ExperimentsOverlay>` | Finds every `<Experiment>` on the page (its `data-experiment` marker) and draws a dashed outline and an "A/B test · <name>" handle at its corner. Hover, keyboard focus or a click opens the test's card, anchored to the element: it scrolls and resizes with it and flips above or below the handle to stay in view. ✕, Esc and a click outside close it. An ended test shows "Winner live · code clean-up pending" instead. |
224
+ | `<ExperimentCard>` | One test: name, status tag, a "?" help and ✕; the verdict; one row per version, best first (conversion on the primary metric, then visitors; re-sorted only when new data arrives), the best one tagged "Leading", or "Winner" for a likely winner or an ended test's winner, the original tagged "Original". A row shows visitors, conversion, chance to beat the original and a bar; the shown version is the info-coloured row (`aria-current`); the whole row shows that version (Enter / Space too). Row actions: a chart icon for that version's analytics (`versionDetailsUrl`), minimal Accept, and a bin icon (Remove) at the far end. Footer: the optional "Open in …" link and Start / Resume (solid), Pause, Stop. Past three and a half rows the list scrolls inside the card. Every action asks first (Reqore's confirm dialog) and notifies what happened. |
225
+ | `useExperimentsAdmin(options)` | Optional: the list and the actions from the qorus-api routes. |
226
+ | `createExperimentsAdminClient(options)` | The same without React. |
227
+ | `placeCard`, `cardReducer`, `versionRows`, `verdictLine`, `versionsMaxHeight`, … | The plain logic behind them, for tests and custom UIs. |
228
+
229
+ Both components are driven by props only: the data is the qorus-api editor / admin list (an array of
230
+ `{ experiment, results }`: the admin experiment object with `status`, `variants`, `phase`,
231
+ `phases`, `history`, `winner`, and the results object with `verdict_text` and the per-metric
232
+ numbers), and every action is a callback that may return a promise (the button shows busy until it
233
+ settles, and a rejection's message is shown). An action whose callback is not given does not
234
+ appear. Neither makes a request of its own.
235
+
236
+ ```tsx
237
+ import { ExperimentsOverlay, useExperimentsAdmin } from '@qoretechnologies/reqraft/dist/tracking/ui';
238
+
239
+ const AbTestsLayer = ({ visible }: { visible: boolean }) => {
240
+ const admin = useExperimentsAdmin({
241
+ listUrl: '/qorus-cloud/editor/experiments?property=ide', // actions go to …/experiments/<key>/<action>
242
+ headers: { Authorization: `Bearer ${editorToken}` }, // or leave it to a proxy
243
+ enabled: visible,
244
+ });
245
+ return (
246
+ <ExperimentsOverlay
247
+ visible={visible} // the product's "hide editor elements"
248
+ tests={admin.tests}
249
+ loadError={admin.error}
250
+ onAccept={admin.accept}
251
+ onRemove={admin.remove}
252
+ onStart={admin.start}
253
+ onPause={admin.pause}
254
+ onStop={admin.stop}
255
+ detailsUrl={(key) => `${ADMIN_PORTAL}/analytics?dashboard=tracking&experiment=${key}`}
256
+ actorLabels={{ 'ide-editor': 'in the IDE' }}
257
+ topInset={() => document.querySelector('header')?.getBoundingClientRect().bottom ?? 0}
258
+ />
259
+ );
260
+ };
261
+ ```
262
+
263
+ - Mount it inside `<TrackingProvider>`: then clicking a version's row uses the tracker's in-app
264
+ switch (`tracker.experiments.setEditorOverride`, this browser only, never logged), and after
265
+ an action the page shows what visitors see now (the switch is cleared and the tracker reloads
266
+ the definitions). Pass `onShow` / `onSettled` to do something else.
267
+ - `versionDetailsUrl={(key, variant) => \`${ADMIN_PORTAL}/analytics?dashboard=tracking&experiment=${key}&variant=${variant}\`}`
268
+ adds the per-version "Show analytics" action (the admin portal opens the test with that version
269
+ expanded); without it the action is hidden. `detailsUrl` stays for the whole test.
270
+ - The "?" opens a dialog explaining the test for a marketer: every button, how visitors are split
271
+ and counted, reading the verdict and the chance to beat, the too-early rule (the test's
272
+ `min_sample`, 200 visitors per version and 7 days by default), a test that is not set up, and
273
+ where the full analytics are (`analyticsLabel`).
274
+ - A test that is in the page's code (`<Experiment>`) but not registered in the analytics service
275
+ yet shows "Not set up" and asks to have it set up (`notSetUpText` changes the words); when the list
276
+ cannot be loaded (`loadError`) it shows "No connection" instead.
277
+ - `exclude` (a selector) skips markers inside the product's own UI (e.g. its preview frames);
278
+ markers inside the overlay itself are always skipped.
279
+ - The same overlay works inside a phone-preview iframe: mount it in the frame's page too. The
280
+ switch is shared through `localStorage`, so both follow it.
281
+ - `useExperimentsAdmin` reloads the list every minute (`refreshMs`) and after every action; its
282
+ errors explain a 401 / 403 as a missing or wrong key. `actionUrl` and `createUrl` change the
283
+ routes; `fetch` replaces the transport.
284
+ - The card on its own: `<ExperimentCard experimentKey="signup-cta" test={entry} shown="a" codeVariants={['a', 'b']} onShow={…} onAccept={…} … />`.
285
+
286
+ Stories: `Tracking/Experiment Card` (draft, running with two and five versions, paused, concluded,
287
+ no data yet, not set up, no connection, the confirm step on Remove, the help) and
288
+ `Tracking/Experiments Overlay` (anchored, flipped above near the bottom, hidden), each in dark,
289
+ light and phone. The visually empty ones (hidden, the phone flip) run as tests without a Qlip
290
+ snapshot (`parameters.qlip.skip`).
package/dist/index.d.ts CHANGED
@@ -29,4 +29,6 @@ export * from './components/ticketThread';
29
29
  export * from './components/ticketReferences';
30
30
  export * from './components/searchFilterBar';
31
31
  export * from './components/codeSize';
32
+ export * from './tracking';
33
+ export * from './tracking/ui';
32
34
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,OAAO,EACL,aAAa,EACb,uBAAuB,EACvB,UAAU,EACV,gBAAgB,EAChB,cAAc,EACd,4BAA4B,EAC5B,eAAe,EACf,gCAAgC,GACjC,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,cAAc,4BAA4B,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,YAAY,EACV,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,GACjB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACrD,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,YAAY,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACxE,cAAc,mCAAmC,CAAC;AAClD,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,kBAAkB,GACnB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,gBAAgB,IAAI,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAC/F,YAAY,EACV,YAAY,EACZ,iBAAiB,GAClB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,WAAW,EACX,KAAK,EACL,QAAQ,EACR,cAAc,EACd,eAAe,EACf,UAAU,GACX,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,eAAe,CAAC;AACvB,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,YAAY,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAClE,YAAY,EACV,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,wBAAwB,EACxB,YAAY,EACZ,SAAS,EACT,iBAAiB,EACjB,wBAAwB,EACxB,sBAAsB,EACtB,iBAAiB,EACjB,wBAAwB,EACxB,YAAY,EACZ,gBAAgB,GACjB,MAAM,yBAAyB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,OAAO,EACL,aAAa,EACb,uBAAuB,EACvB,UAAU,EACV,gBAAgB,EAChB,cAAc,EACd,4BAA4B,EAC5B,eAAe,EACf,gCAAgC,GACjC,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,cAAc,4BAA4B,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,YAAY,EACV,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,GACjB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACrD,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,YAAY,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACxE,cAAc,mCAAmC,CAAC;AAClD,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,kBAAkB,GACnB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,gBAAgB,IAAI,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAC/F,YAAY,EACV,YAAY,EACZ,iBAAiB,GAClB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,WAAW,EACX,KAAK,EACL,QAAQ,EACR,cAAc,EACd,eAAe,EACf,UAAU,GACX,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,eAAe,CAAC;AACvB,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,YAAY,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAClE,YAAY,EACV,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,wBAAwB,EACxB,YAAY,EACZ,SAAS,EACT,iBAAiB,EACjB,wBAAwB,EACxB,sBAAsB,EACtB,iBAAiB,EACjB,wBAAwB,EACxB,YAAY,EACZ,gBAAgB,GACjB,MAAM,yBAAyB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uBAAuB,CAAC;AAItC,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC"}
package/dist/index.js CHANGED
@@ -61,4 +61,9 @@ __exportStar(require("./components/ticketThread"), exports);
61
61
  __exportStar(require("./components/ticketReferences"), exports);
62
62
  __exportStar(require("./components/searchFilterBar"), exports);
63
63
  __exportStar(require("./components/codeSize"), exports);
64
+ // First-party tracking, consent and A/B testing (design/TRACKING.md). Also importable
65
+ // on its own, without the rest of Reqraft: `@qoretechnologies/reqraft/dist/tracking`
66
+ // (the engine, React only) and `@qoretechnologies/reqraft/dist/tracking/ui` (the A/B test UI).
67
+ __exportStar(require("./tracking"), exports);
68
+ __exportStar(require("./tracking/ui"), exports);
64
69
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,oDAAkC;AAClC,uDAAqC;AACrC,qDAS+B;AAR7B,4GAAA,aAAa,OAAA;AACb,sHAAA,uBAAuB,OAAA;AACvB,yGAAA,UAAU,OAAA;AACV,+GAAA,gBAAgB,OAAA;AAChB,6GAAA,cAAc,OAAA;AACd,2HAAA,4BAA4B,OAAA;AAC5B,8GAAA,eAAe,OAAA;AACf,+HAAA,gCAAgC,OAAA;AAGlC,iDAAoD;AAA3C,0GAAA,aAAa,OAAA;AACtB,6DAA2C;AAC3C,+CAAqD;AAA5C,mGAAA,WAAW,OAAA;AAQpB,sDAAqD;AAA5C,oGAAA,QAAQ,OAAA;AAEjB,4DAAkE;AAAzD,+GAAA,iBAAiB,OAAA;AAE1B,oEAAkD;AAClD,+DAIqC;AAHnC,oHAAA,iBAAiB,OAAA;AACjB,kHAAA,eAAe,OAAA;AACf,qHAAA,kBAAkB,OAAA;AAEpB,gEAA+F;AAAtF,sHAAA,gBAAgB,OAA2B;AAKpD,uCAOuB;AANrB,oGAAA,WAAW,OAAA;AACX,8FAAA,KAAK,OAAA;AACL,iGAAA,QAAQ,OAAA;AACR,uGAAA,cAAc,OAAA;AACd,wGAAA,eAAe,OAAA;AACf,mGAAA,UAAU,OAAA;AAQZ,oDAAkC;AAClC,+CAAqD;AAA5C,6GAAA,gBAAgB,OAAA;AAiBzB,wDAAsC;AACtC,2DAAyC;AACzC,0DAAwC;AACxC,iEAA+C;AAC/C,6DAA2C;AAC3C,4DAA0C;AAC1C,gEAA8C;AAC9C,+DAA6C;AAC7C,wDAAsC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,oDAAkC;AAClC,uDAAqC;AACrC,qDAS+B;AAR7B,4GAAA,aAAa,OAAA;AACb,sHAAA,uBAAuB,OAAA;AACvB,yGAAA,UAAU,OAAA;AACV,+GAAA,gBAAgB,OAAA;AAChB,6GAAA,cAAc,OAAA;AACd,2HAAA,4BAA4B,OAAA;AAC5B,8GAAA,eAAe,OAAA;AACf,+HAAA,gCAAgC,OAAA;AAGlC,iDAAoD;AAA3C,0GAAA,aAAa,OAAA;AACtB,6DAA2C;AAC3C,+CAAqD;AAA5C,mGAAA,WAAW,OAAA;AAQpB,sDAAqD;AAA5C,oGAAA,QAAQ,OAAA;AAEjB,4DAAkE;AAAzD,+GAAA,iBAAiB,OAAA;AAE1B,oEAAkD;AAClD,+DAIqC;AAHnC,oHAAA,iBAAiB,OAAA;AACjB,kHAAA,eAAe,OAAA;AACf,qHAAA,kBAAkB,OAAA;AAEpB,gEAA+F;AAAtF,sHAAA,gBAAgB,OAA2B;AAKpD,uCAOuB;AANrB,oGAAA,WAAW,OAAA;AACX,8FAAA,KAAK,OAAA;AACL,iGAAA,QAAQ,OAAA;AACR,uGAAA,cAAc,OAAA;AACd,wGAAA,eAAe,OAAA;AACf,mGAAA,UAAU,OAAA;AAQZ,oDAAkC;AAClC,+CAAqD;AAA5C,6GAAA,gBAAgB,OAAA;AAiBzB,wDAAsC;AACtC,2DAAyC;AACzC,0DAAwC;AACxC,iEAA+C;AAC/C,6DAA2C;AAC3C,4DAA0C;AAC1C,gEAA8C;AAC9C,+DAA6C;AAC7C,wDAAsC;AACtC,sFAAsF;AACtF,qFAAqF;AACrF,+FAA+F;AAC/F,6CAA2B;AAC3B,gDAA8B"}
@@ -0,0 +1,8 @@
1
+ import type { Tracker } from '../core/tracker';
2
+ export interface IAttached {
3
+ detach: () => void;
4
+ /** Observes an element under a synthetic id (an experiment's wrapped element); not in the page summary. */
5
+ observeElement: (el: Element, id: string) => () => void;
6
+ }
7
+ export declare const attachToPage: (tracker: Tracker, heartbeatMs: number) => IAttached;
8
+ //# sourceMappingURL=attach.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attach.d.ts","sourceRoot":"","sources":["../../../src/tracking/browser/attach.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAoB/C,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,2GAA2G;IAC3G,cAAc,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,KAAK,MAAM,IAAI,CAAC;CACzD;AAOD,eAAO,MAAM,YAAY,YAAa,OAAO,eAAe,MAAM,KAAG,SAyJpE,CAAC"}
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.attachToPage = void 0;
4
+ var sections_1 = require("../core/sections");
5
+ /**
6
+ * Connects a `Tracker` to the page: tab visibility, page leave, interactions,
7
+ * scroll depth, the heartbeat, section visibility (IntersectionObserver on every
8
+ * `[data-track-section]`) and clicks (one delegated listener for
9
+ * `[data-track-click]`). Returns a function that removes all of it.
10
+ *
11
+ * Markup contract (see design/TRACKING.md):
12
+ * data-track-section="<id>" a section: visible time, seen, exit section
13
+ * data-track-click="<target>" a tracked click (`get-started`, `contact`, …)
14
+ * data-track-placement="<where>" optional; defaults to the enclosing section id
15
+ * data-track-label="<text>" optional; defaults to the element's text
16
+ */
17
+ var SECTION_SELECTOR = '[data-track-section]';
18
+ var THRESHOLDS = Array.from({ length: 21 }, function (_, i) { return i / 20; });
19
+ var TICK_MS = 250;
20
+ var ACTIVITY_EVENTS = ['pointerdown', 'keydown', 'wheel', 'touchstart', 'mousemove', 'scroll'];
21
+ var clean = function (s, max) {
22
+ if (max === void 0) { max = 80; }
23
+ var t = (s !== null && s !== void 0 ? s : '').replace(/\s+/g, ' ').trim();
24
+ return t ? t.slice(0, max) : null;
25
+ };
26
+ var attachToPage = function (tracker, heartbeatMs) {
27
+ var cleanup = [];
28
+ var on = function (target, type, fn, opts) {
29
+ target.addEventListener(type, fn, opts);
30
+ cleanup.push(function () { return target.removeEventListener(type, fn, opts); });
31
+ };
32
+ var safe = function (fn) { return function () {
33
+ try {
34
+ fn();
35
+ }
36
+ catch (_a) {
37
+ /* never into the page */
38
+ }
39
+ }; };
40
+ // ── tab visibility and page leave
41
+ on(document, 'visibilitychange', safe(function () { return tracker.setTabVisible(document.visibilityState === 'visible'); }));
42
+ on(window, 'pagehide', safe(function () { return tracker.leave(); }));
43
+ if (document.visibilityState !== 'visible')
44
+ tracker.setTabVisible(false);
45
+ // ── engagement (throttled to one signal per second)
46
+ var lastActivity = 0;
47
+ var activity = safe(function () {
48
+ var now = Date.now();
49
+ if (now - lastActivity < 1000)
50
+ return;
51
+ lastActivity = now;
52
+ tracker.activity();
53
+ });
54
+ ACTIVITY_EVENTS.forEach(function (t) { return on(window, t, activity, { passive: true, capture: true }); });
55
+ // ── scroll depth (once per frame)
56
+ var scrollQueued = false;
57
+ var measureScroll = safe(function () {
58
+ var _a, _b;
59
+ scrollQueued = false;
60
+ var doc = document.documentElement;
61
+ var scrollable = Math.max(doc.scrollHeight, (_b = (_a = document.body) === null || _a === void 0 ? void 0 : _a.scrollHeight) !== null && _b !== void 0 ? _b : 0);
62
+ var seen = window.scrollY + window.innerHeight;
63
+ tracker.scrolled(scrollable > 0 ? (seen / scrollable) * 100 : 100);
64
+ });
65
+ on(window, 'scroll', function () {
66
+ if (scrollQueued)
67
+ return;
68
+ scrollQueued = true;
69
+ requestAnimationFrame(measureScroll);
70
+ }, { passive: true });
71
+ // ── heartbeat while visible, and the section clock
72
+ var heartbeat = setInterval(safe(function () { return document.visibilityState === 'visible' && tracker.heartbeat(); }), heartbeatMs);
73
+ var tick = setInterval(safe(function () { return tracker.sections.tick(tracker.now()); }), TICK_MS);
74
+ cleanup.push(function () { return clearInterval(heartbeat); }, function () { return clearInterval(tick); });
75
+ // ── sections
76
+ var ratios = new Map();
77
+ var ids = new Map();
78
+ var report = new Map();
79
+ var apply = function (id) {
80
+ var best = 0;
81
+ ids.forEach(function (other, el) { var _a; return other === id && (best = Math.max(best, (_a = ratios.get(el)) !== null && _a !== void 0 ? _a : 0)); });
82
+ tracker.sections.update(id, best, tracker.now());
83
+ };
84
+ var io = typeof IntersectionObserver !== 'undefined'
85
+ ? new IntersectionObserver(function (entries) {
86
+ return safe(function () {
87
+ var vh = window.innerHeight || document.documentElement.clientHeight;
88
+ entries.forEach(function (e) {
89
+ var _a, _b;
90
+ var id = ids.get(e.target);
91
+ if (!id)
92
+ return;
93
+ ratios.set(e.target, (0, sections_1.effectiveRatio)(e.intersectionRatio, e.intersectionRect.height, (_b = (_a = e.rootBounds) === null || _a === void 0 ? void 0 : _a.height) !== null && _b !== void 0 ? _b : vh));
94
+ apply(id);
95
+ });
96
+ })();
97
+ }, { threshold: THRESHOLDS })
98
+ : null;
99
+ var track = function (el, id, inSummary) {
100
+ var _a;
101
+ if (ids.get(el) === id)
102
+ return;
103
+ ids.set(el, id);
104
+ report.set(id, ((_a = report.get(id)) !== null && _a !== void 0 ? _a : false) || inSummary);
105
+ tracker.sections.register(id, inSummary);
106
+ io === null || io === void 0 ? void 0 : io.observe(el);
107
+ };
108
+ var untrack = function (el) {
109
+ var id = ids.get(el);
110
+ if (!id)
111
+ return;
112
+ io === null || io === void 0 ? void 0 : io.unobserve(el);
113
+ ids.delete(el);
114
+ ratios.delete(el);
115
+ apply(id);
116
+ };
117
+ var scan = safe(function () {
118
+ var present = new Set(Array.from(document.querySelectorAll(SECTION_SELECTOR)));
119
+ ids.forEach(function (id, el) { return report.get(id) && !present.has(el) && !el.isConnected && untrack(el); });
120
+ present.forEach(function (el) {
121
+ var id = el.dataset.trackSection;
122
+ if (id)
123
+ track(el, id, true);
124
+ });
125
+ });
126
+ // Re-measure everything at a new page view: the section clock starts from zero,
127
+ // and re-observing makes the observer report every element's current state.
128
+ var remeasure = safe(function () {
129
+ scan();
130
+ ids.forEach(function (_id, el) {
131
+ io === null || io === void 0 ? void 0 : io.unobserve(el);
132
+ io === null || io === void 0 ? void 0 : io.observe(el);
133
+ });
134
+ });
135
+ cleanup.push(tracker.pageViewId.subscribe(remeasure));
136
+ var scanTimer = null;
137
+ var mo = typeof MutationObserver !== 'undefined'
138
+ ? new MutationObserver(function () {
139
+ if (scanTimer)
140
+ return;
141
+ scanTimer = setTimeout(function () {
142
+ scanTimer = null;
143
+ scan();
144
+ }, 150);
145
+ })
146
+ : null;
147
+ mo === null || mo === void 0 ? void 0 : mo.observe(document.body, { childList: true, subtree: true });
148
+ scan();
149
+ cleanup.push(function () {
150
+ mo === null || mo === void 0 ? void 0 : mo.disconnect();
151
+ io === null || io === void 0 ? void 0 : io.disconnect();
152
+ if (scanTimer)
153
+ clearTimeout(scanTimer);
154
+ });
155
+ // ── clicks: one delegated listener
156
+ var onClick = function (e) {
157
+ return safe(function () {
158
+ var _a, _b, _c, _d, _e;
159
+ var el = (_b = (_a = e.target) === null || _a === void 0 ? void 0 : _a.closest) === null || _b === void 0 ? void 0 : _b.call(_a, '[data-track-click]');
160
+ if (!el)
161
+ return;
162
+ var d = el.dataset;
163
+ var link = el.closest('a');
164
+ tracker.click({
165
+ target: d.trackClick || 'unknown',
166
+ placement: d.trackPlacement || ((_c = el.closest(SECTION_SELECTOR)) === null || _c === void 0 ? void 0 : _c.dataset.trackSection) || null,
167
+ label: (_e = (_d = clean(d.trackLabel)) !== null && _d !== void 0 ? _d : clean(el.innerText)) !== null && _e !== void 0 ? _e : clean(el.getAttribute('aria-label')),
168
+ href: d.trackHref || (link === null || link === void 0 ? void 0 : link.href) || null,
169
+ });
170
+ })();
171
+ };
172
+ on(document, 'click', onClick, { capture: true });
173
+ return {
174
+ detach: function () { return cleanup.forEach(function (fn) { return fn(); }); },
175
+ observeElement: function (el, id) {
176
+ track(el, id, false);
177
+ return function () { return untrack(el); };
178
+ },
179
+ };
180
+ };
181
+ exports.attachToPage = attachToPage;
182
+ //# sourceMappingURL=attach.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attach.js","sourceRoot":"","sources":["../../../src/tracking/browser/attach.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { ITransport } from '../core/queue';
2
+ import type { IPageInfo, ITrackerEnv } from '../core/tracker';
3
+ /**
4
+ * Sends a batch. `beacon` (page going away): `navigator.sendBeacon`, falling back
5
+ * to `fetch(…, { keepalive: true })` when the beacon is refused (too big, or the
6
+ * browser has none). Any HTTP answer counts as delivered — the collector always
7
+ * answers 204, and a 404 (collector not installed) must not turn into a retry loop;
8
+ * only a network error keeps the batch for the next flush.
9
+ */
10
+ export declare const browserTransport: (endpoint: string) => ITransport;
11
+ /** desktop / tablet / mobile from the user agent (targeting only; the server classifies for reports). */
12
+ export declare const deviceOf: (ua: string) => IPageInfo['device'];
13
+ export declare const browserEnv: (endpoint: string) => ITrackerEnv;
14
+ //# sourceMappingURL=env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../../src/tracking/browser/env.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9D;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,aAAc,MAAM,KAAG,UAuBlD,CAAC;AAcH,yGAAyG;AACzG,eAAO,MAAM,QAAQ,OAAQ,MAAM,KAAG,SAAS,CAAC,QAAQ,CAIvD,CAAC;AAaF,eAAO,MAAM,UAAU,aAAc,MAAM,KAAG,WAW5C,CAAC"}