@sailfish-ai/recorder 1.12.5 → 1.12.7

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 (144) hide show
  1. package/README.md +8 -0
  2. package/dist/canvas/adapters.js +382 -0
  3. package/dist/canvas/adapters.js.br +0 -0
  4. package/dist/canvas/adapters.js.gz +0 -0
  5. package/dist/canvas/canvasManager.js +1949 -0
  6. package/dist/canvas/canvasManager.js.br +0 -0
  7. package/dist/canvas/canvasManager.js.gz +0 -0
  8. package/dist/canvas/canvasPrivacy.js +233 -0
  9. package/dist/canvas/canvasPrivacy.js.br +0 -0
  10. package/dist/canvas/canvasPrivacy.js.gz +0 -0
  11. package/dist/canvas/codec.js +314 -0
  12. package/dist/canvas/codec.js.br +0 -0
  13. package/dist/canvas/codec.js.gz +0 -0
  14. package/dist/canvas/deviceClass.js +67 -0
  15. package/dist/canvas/deviceClass.js.br +0 -0
  16. package/dist/canvas/deviceClass.js.gz +0 -0
  17. package/dist/canvas/encodeWorker.js +284 -0
  18. package/dist/canvas/encodeWorker.js.br +0 -0
  19. package/dist/canvas/encodeWorker.js.gz +0 -0
  20. package/dist/canvas/governor.js +79 -0
  21. package/dist/canvas/governor.js.br +0 -0
  22. package/dist/canvas/governor.js.gz +0 -0
  23. package/dist/canvas/keyframes.js +183 -0
  24. package/dist/canvas/keyframes.js.br +0 -0
  25. package/dist/canvas/keyframes.js.gz +0 -0
  26. package/dist/canvas/mediaRecorderCapture.js +257 -0
  27. package/dist/canvas/mediaRecorderCapture.js.br +0 -0
  28. package/dist/canvas/mediaRecorderCapture.js.gz +0 -0
  29. package/dist/canvas/objectLog.js +150 -0
  30. package/dist/canvas/objectLog.js.br +0 -0
  31. package/dist/canvas/objectLog.js.gz +0 -0
  32. package/dist/canvas/patch2d.js +1302 -0
  33. package/dist/canvas/patch2d.js.br +0 -0
  34. package/dist/canvas/patch2d.js.gz +0 -0
  35. package/dist/canvas/patchGL.js +371 -0
  36. package/dist/canvas/patchGL.js.br +0 -0
  37. package/dist/canvas/patchGL.js.gz +0 -0
  38. package/dist/canvas/patchWebGPU.js +361 -0
  39. package/dist/canvas/patchWebGPU.js.br +0 -0
  40. package/dist/canvas/patchWebGPU.js.gz +0 -0
  41. package/dist/canvas/piiDetect.js +155 -0
  42. package/dist/canvas/piiDetect.js.br +0 -0
  43. package/dist/canvas/piiDetect.js.gz +0 -0
  44. package/dist/canvas/protocol.js +122 -0
  45. package/dist/canvas/protocol.js.br +0 -0
  46. package/dist/canvas/protocol.js.gz +0 -0
  47. package/dist/canvas/videoCapture.js +252 -0
  48. package/dist/canvas/videoCapture.js.br +0 -0
  49. package/dist/canvas/videoCapture.js.gz +0 -0
  50. package/dist/canvas/workerRelay.js +99 -0
  51. package/dist/canvas/workerRelay.js.br +0 -0
  52. package/dist/canvas/workerRelay.js.gz +0 -0
  53. package/dist/canvas/workerShim.js +227 -0
  54. package/dist/canvas/workerShim.js.br +0 -0
  55. package/dist/canvas/workerShim.js.gz +0 -0
  56. package/dist/canvas-worker.cjs +74 -0
  57. package/dist/canvas-worker.cjs.br +0 -0
  58. package/dist/canvas-worker.cjs.gz +0 -0
  59. package/dist/canvas-worker.js +78 -0
  60. package/dist/canvas-worker.js.br +0 -0
  61. package/dist/canvas-worker.js.gz +0 -0
  62. package/dist/canvasFrameStore.js +198 -0
  63. package/dist/canvasFrameStore.js.br +0 -0
  64. package/dist/canvasFrameStore.js.gz +0 -0
  65. package/dist/chunks/canvasManager-B9uQ-PFS.js +1456 -0
  66. package/dist/chunks/canvasManager-B9uQ-PFS.js.br +0 -0
  67. package/dist/chunks/canvasManager-B9uQ-PFS.js.gz +0 -0
  68. package/dist/chunks/canvasManager-Ce7vazTt.js +1440 -0
  69. package/dist/chunks/canvasManager-Ce7vazTt.js.br +0 -0
  70. package/dist/chunks/canvasManager-Ce7vazTt.js.gz +0 -0
  71. package/dist/chunks/{chunkSerializer-pkpgT-FH.js → chunkSerializer-B-z7p0r-.js} +1 -1
  72. package/dist/chunks/chunkSerializer-B-z7p0r-.js.br +0 -0
  73. package/dist/chunks/chunkSerializer-B-z7p0r-.js.gz +0 -0
  74. package/dist/chunks/{chunkSerializer-Cd8y9tID.js → chunkSerializer-C1knjCQ5.js} +1 -1
  75. package/dist/chunks/chunkSerializer-C1knjCQ5.js.br +0 -0
  76. package/dist/chunks/chunkSerializer-C1knjCQ5.js.gz +0 -0
  77. package/dist/chunks/{index-CXvHbLRT.js → index-BMHQ-0NY.js} +1115 -724
  78. package/dist/chunks/index-BMHQ-0NY.js.br +0 -0
  79. package/dist/chunks/index-BMHQ-0NY.js.gz +0 -0
  80. package/dist/chunks/{index-Bvd8oTpV.js → index-qgM1hFXO.js} +1025 -655
  81. package/dist/chunks/index-qgM1hFXO.js.br +0 -0
  82. package/dist/chunks/index-qgM1hFXO.js.gz +0 -0
  83. package/dist/chunks/patch2d-9voowhJ_.js +816 -0
  84. package/dist/chunks/patch2d-9voowhJ_.js.br +0 -0
  85. package/dist/chunks/patch2d-9voowhJ_.js.gz +0 -0
  86. package/dist/chunks/patch2d-PQxcaE6r.js +886 -0
  87. package/dist/chunks/patch2d-PQxcaE6r.js.br +0 -0
  88. package/dist/chunks/patch2d-PQxcaE6r.js.gz +0 -0
  89. package/dist/graphql.js +12 -1
  90. package/dist/graphql.js.br +0 -0
  91. package/dist/graphql.js.gz +0 -0
  92. package/dist/inAppReportIssueModal/index.js +10 -1
  93. package/dist/inAppReportIssueModal/index.js.br +0 -0
  94. package/dist/inAppReportIssueModal/index.js.gz +0 -0
  95. package/dist/index.js +144 -2
  96. package/dist/index.js.br +0 -0
  97. package/dist/index.js.gz +0 -0
  98. package/dist/recorder.cjs +2 -2
  99. package/dist/recorder.cjs.br +0 -0
  100. package/dist/recorder.cjs.gz +0 -0
  101. package/dist/recorder.js +52 -41
  102. package/dist/recorder.js.br +0 -0
  103. package/dist/recorder.js.gz +0 -0
  104. package/dist/recorder.umd.cjs +4842 -2215
  105. package/dist/recorder.umd.cjs.br +0 -0
  106. package/dist/recorder.umd.cjs.gz +0 -0
  107. package/dist/recording.js +119 -10
  108. package/dist/recording.js.br +0 -0
  109. package/dist/recording.js.gz +0 -0
  110. package/dist/types/canvas/adapters.d.ts +71 -0
  111. package/dist/types/canvas/canvasManager.d.ts +273 -0
  112. package/dist/types/canvas/canvasPrivacy.d.ts +184 -0
  113. package/dist/types/canvas/codec.d.ts +27 -0
  114. package/dist/types/canvas/deviceClass.d.ts +40 -0
  115. package/dist/types/canvas/encodeWorker.d.ts +54 -0
  116. package/dist/types/canvas/governor.d.ts +44 -0
  117. package/dist/types/canvas/keyframes.d.ts +81 -0
  118. package/dist/types/canvas/mediaRecorderCapture.d.ts +91 -0
  119. package/dist/types/canvas/objectLog.d.ts +75 -0
  120. package/dist/types/canvas/patch2d.d.ts +339 -0
  121. package/dist/types/canvas/patchGL.d.ts +74 -0
  122. package/dist/types/canvas/patchWebGPU.d.ts +37 -0
  123. package/dist/types/canvas/piiDetect.d.ts +60 -0
  124. package/dist/types/canvas/protocol.d.ts +176 -0
  125. package/dist/types/canvas/videoCapture.d.ts +67 -0
  126. package/dist/types/canvas/workerRelay.d.ts +29 -0
  127. package/dist/types/canvas/workerShim.d.ts +10 -0
  128. package/dist/types/canvasFrameStore.d.ts +23 -0
  129. package/dist/types/graphql.d.ts +1 -1
  130. package/dist/types/index.d.ts +99 -1
  131. package/dist/types/types.d.ts +40 -0
  132. package/dist/types/websocket.d.ts +19 -0
  133. package/dist/websocket.js +210 -0
  134. package/dist/websocket.js.br +0 -0
  135. package/dist/websocket.js.gz +0 -0
  136. package/package.json +7 -1
  137. package/dist/chunks/chunkSerializer-Cd8y9tID.js.br +0 -0
  138. package/dist/chunks/chunkSerializer-Cd8y9tID.js.gz +0 -0
  139. package/dist/chunks/chunkSerializer-pkpgT-FH.js.br +0 -0
  140. package/dist/chunks/chunkSerializer-pkpgT-FH.js.gz +0 -0
  141. package/dist/chunks/index-Bvd8oTpV.js.br +0 -0
  142. package/dist/chunks/index-Bvd8oTpV.js.gz +0 -0
  143. package/dist/chunks/index-CXvHbLRT.js.br +0 -0
  144. package/dist/chunks/index-CXvHbLRT.js.gz +0 -0
package/README.md CHANGED
@@ -239,6 +239,14 @@ initRecorder({
239
239
 
240
240
  `scripts/bench-perf.js` loads the same page twice — once with capture on, once with capture off — under headless Chrome, and reports CPU, JS heap, FCP/LCP, long-task time, and total load time side-by-side.
241
241
 
242
+ > **Canvas recording** has its own full, customer-facing cost/fidelity report —
243
+ > per-frame slowdown, CPU, RAM, recorded size and page weight for **2D, WebGL and
244
+ > WebGPU** canvases, on **desktop and (CPU-throttled) mobile**, plus replay SSIM,
245
+ > Lighthouse page-level deltas, and a glossary explaining every metric. See
246
+ > **[PERFORMANCE.md](./PERFORMANCE.md)**. Headline: lossless replay (SSIM 1.0),
247
+ > **~0.5 µs per draw** (~1% of a 60 fps frame for typical scenes), **0 ms** added
248
+ > Total Blocking Time, no memory leak, **~10 KB gzipped**.
249
+
242
250
  #### One-time app change (required before running the bench)
243
251
 
244
252
  The bench needs a way to flip capture on/off without rebuilding your app between variants. Wire `?sf_perf=off` in your `initRecorder` call so the URL controls the option — paste this where you call `initRecorder`:
@@ -0,0 +1,382 @@
1
+ /**
2
+ * Phase 4 — framework semantic adapters (contract §9).
3
+ *
4
+ * Canvas pixels know WHERE a click landed (the pick buffer resolves that to a
5
+ * draw command / source line). Frameworks additionally know WHAT logical
6
+ * object the user clicked: a Konva "Upgrade CTA" shape, a Chart.js datapoint,
7
+ * a Three.js mesh. These adapters capture that semantic target on pointerdown
8
+ * and emit it as a `sf-canvas` op:"hit" index event:
9
+ *
10
+ * {tag:"sf-canvas", op:"hit", canvasId, t, x, y,
11
+ * target:{framework, label, objectType?, path?}}
12
+ *
13
+ * The SDK cannot import the customer's framework (three/konva/fabric/pixi/
14
+ * chart.js), so the customer (or a future build plugin) registers their live
15
+ * instance via the exported register* helpers below — each returns an
16
+ * unregister fn for component teardown. A window-global fallback covers
17
+ * script-tag (UMD) usage where the framework publishes a global.
18
+ *
19
+ * Every adapter callback is try/catch'd: a framework hiccup never breaks
20
+ * recording. SSR-safe: no window/document access at module scope.
21
+ */
22
+ // [creation-site stamping] object -> build-time creation id. Set by:
23
+ // - Layer A: the build plugin's `__sfnew` helper via globalThis.__sfCreateSite
24
+ // (marks a `new X()` result directly), and
25
+ // - Layer B: the build plugin's `__sfcreate` helper via globalThis.__sfCreateFrom
26
+ // (marks a loader/clone/factory RESULT subtree — see markSubtree).
27
+ // WeakMap -> no retention.
28
+ const createIdOf = new WeakMap();
29
+ /** Creation provenance for the clicked object: nearest stamped createId (self or
30
+ * up the `.parent` chain). Never throws. */
31
+ function creationOf(obj) {
32
+ const out = {};
33
+ try {
34
+ let o = obj;
35
+ for (let i = 0; i < 8 && o && typeof o === "object"; i++) {
36
+ const id = createIdOf.get(o);
37
+ if (id) {
38
+ out.createId = id;
39
+ break;
40
+ }
41
+ o = o.parent;
42
+ }
43
+ }
44
+ catch { /* best effort */ }
45
+ return out;
46
+ }
47
+ // ─── Layer B: produced-object subtree marking (globalThis.__sfCreateFrom) ─────
48
+ /** Depth and total-node caps for markSubtree — bound the walk so a pathological
49
+ * scene graph can never stall the click path or hold work proportional to N. */
50
+ const MARK_MAX_DEPTH = 6;
51
+ const MARK_MAX_NODES = 2000;
52
+ /** Framework child collections for an object: three.js `.children` (+ GLTF result
53
+ * `.scene`/`.scenes`), pixi `.children`, fabric/konva `getObjects?.()`. Returns the
54
+ * direct child objects to descend into. Never throws. */
55
+ function frameworkChildren(node) {
56
+ const out = [];
57
+ const push = (v) => { if (v && typeof v === "object")
58
+ out.push(v); };
59
+ try {
60
+ const n = node;
61
+ // three.js Object3D / pixi Container: `.children` array
62
+ if (Array.isArray(n.children))
63
+ for (const c of n.children)
64
+ push(c);
65
+ // GLTF loader result: `.scene` (a Group) and/or `.scenes` (Group[])
66
+ push(n.scene);
67
+ if (Array.isArray(n.scenes))
68
+ for (const s of n.scenes)
69
+ push(s);
70
+ // fabric.Group / konva.Container: `getObjects()` returns the child array
71
+ if (typeof n.getObjects === "function") {
72
+ const objs = n.getObjects();
73
+ if (Array.isArray(objs))
74
+ for (const o of objs)
75
+ push(o);
76
+ }
77
+ }
78
+ catch { /* best effort — a hostile getter must never break marking */ }
79
+ return out;
80
+ }
81
+ /** Mark a produced object and its bounded framework subtree with the creation-site
82
+ * id. Sets createId ONLY where none exists (Layer A / a nearer site wins). Walks
83
+ * breadth-first to MARK_MAX_DEPTH and MARK_MAX_NODES; WeakMap only (no retention);
84
+ * never throws. */
85
+ function markSubtree(root, id) {
86
+ try {
87
+ if (!root || typeof root !== "object")
88
+ return;
89
+ // BFS with a visited set so cyclic graphs (a child re-added to an ancestor)
90
+ // cannot loop, and the node cap bounds total work.
91
+ const seen = new WeakSet();
92
+ let frontier = [root];
93
+ seen.add(root);
94
+ let nodes = 0;
95
+ for (let depth = 0; depth <= MARK_MAX_DEPTH && frontier.length; depth++) {
96
+ const next = [];
97
+ for (const node of frontier) {
98
+ if (nodes >= MARK_MAX_NODES)
99
+ return;
100
+ nodes++;
101
+ try {
102
+ if (!createIdOf.has(node))
103
+ createIdOf.set(node, id);
104
+ }
105
+ catch { /* best effort */ }
106
+ if (depth < MARK_MAX_DEPTH) {
107
+ for (const child of frameworkChildren(node)) {
108
+ if (!seen.has(child)) {
109
+ seen.add(child);
110
+ next.push(child);
111
+ }
112
+ }
113
+ }
114
+ }
115
+ frontier = next;
116
+ }
117
+ }
118
+ catch { /* never throw out of the marker */ }
119
+ }
120
+ /** True for a thenable (Promise / promise-like). Guarded `.then` read. */
121
+ function isThenable(v) {
122
+ try {
123
+ return !!v && (typeof v === "object" || typeof v === "function") &&
124
+ typeof v.then === "function";
125
+ }
126
+ catch {
127
+ return false;
128
+ }
129
+ }
130
+ /** Runtime hook for Layer B build-time `__sfcreate(() => EXPR.method(args), id)`.
131
+ * Marks the producer's RESULT subtree with the call-site id so a later click on a
132
+ * loaded/cloned object resolves to the producer's source line. Async producers
133
+ * (loadAsync / promise loaders) resolve later — attach `.then` and mark the
134
+ * resolved value; rejection behavior is untouched. Fully guarded, SSR-safe. */
135
+ function createFrom(result, siteId) {
136
+ try {
137
+ if (!siteId)
138
+ return;
139
+ if (isThenable(result)) {
140
+ // Mark on fulfillment; swallow rejection so we never alter the promise's
141
+ // own rejection handling (the returned promise is the original — see helper).
142
+ result.then((v) => { markSubtree(v, siteId); }, () => { });
143
+ return;
144
+ }
145
+ markSubtree(result, siteId);
146
+ }
147
+ catch { /* best effort — never throw into the producer's call path */ }
148
+ }
149
+ /** canvas element -> its framework resolver (explicit registrations) */
150
+ const resolvers = new Map();
151
+ function isCanvas(v) {
152
+ return (typeof HTMLCanvasElement !== "undefined" &&
153
+ v instanceof HTMLCanvasElement);
154
+ }
155
+ function bind(canvas, resolver) {
156
+ if (!isCanvas(canvas))
157
+ return () => { };
158
+ resolvers.set(canvas, resolver);
159
+ return () => {
160
+ if (resolvers.get(canvas) === resolver)
161
+ resolvers.delete(canvas);
162
+ };
163
+ }
164
+ // ─── Register helpers (public API; re-exported from index.tsx) ────────────────
165
+ /** Konva: hit-test the stage's hit graph; label from node name()/id()/class. */
166
+ export function registerKonvaStage(stage) {
167
+ try {
168
+ const s = stage;
169
+ // Konva layer canvases are all stage-sized and stacked at (0,0), so binding
170
+ // any one of them covers the whole stage, and getIntersection hit-tests across
171
+ // every layer regardless of which canvas element received the pointer. Bind
172
+ // the first layer's canvas (binding all would just emit N redundant hits).
173
+ const layers = s.getLayers?.() ?? [];
174
+ const canvas = layers[0]?.getCanvas?.()?._canvas;
175
+ return bind(canvas, (cv, ev) => {
176
+ const rect = cv.getBoundingClientRect();
177
+ const node = s.getIntersection?.({ x: ev.clientX - rect.left, y: ev.clientY - rect.top });
178
+ if (!node)
179
+ return null;
180
+ const cls = node.getClassName?.();
181
+ const label = node.name?.() || node.id?.() || cls || "shape";
182
+ return { framework: "konva", label, objectType: cls, ...creationOf(node) };
183
+ });
184
+ }
185
+ catch {
186
+ return () => { };
187
+ }
188
+ }
189
+ /** Fabric: findTarget(ev) on the canvas; label from object name/id/type. */
190
+ export function registerFabricCanvas(fabricCanvas) {
191
+ try {
192
+ const fc = fabricCanvas;
193
+ return bind(fc.lowerCanvasEl, (_cv, ev) => {
194
+ const t = fc.findTarget?.(ev);
195
+ if (!t)
196
+ return null;
197
+ const label = t.name || t.id || t.type || "object";
198
+ return { framework: "fabric", label, objectType: t.type, ...creationOf(t) };
199
+ });
200
+ }
201
+ catch {
202
+ return () => { };
203
+ }
204
+ }
205
+ /** Chart.js: getElementsAtEventForMode -> "datasetLabel[index]". */
206
+ export function registerChart(chart) {
207
+ try {
208
+ const c = chart;
209
+ return bind(c.canvas, (_cv, ev) => {
210
+ const els = c.getElementsAtEventForMode?.(ev, "nearest", { intersect: true }, false);
211
+ if (!els || !els.length)
212
+ return null;
213
+ const { datasetIndex, index } = els[0];
214
+ const dsLabel = c.data?.datasets?.[datasetIndex]?.label ?? `dataset${datasetIndex}`;
215
+ // Chart.js has no per-datum object; Layer A stamps the `new Chart(...)` site
216
+ // (createId of the chart instance), and configRef pins which datum. [Layer D]
217
+ return {
218
+ framework: "chartjs", label: `${dsLabel}[${index}]`, objectType: c.config?.type,
219
+ ...creationOf(c), configRef: { datasetIndex, index },
220
+ };
221
+ });
222
+ }
223
+ catch {
224
+ return () => { };
225
+ }
226
+ }
227
+ /**
228
+ * Three.js: raycast on pointerdown. The customer passes their THREE namespace
229
+ * (the SDK must not bundle three) plus the scene/camera/renderer.
230
+ */
231
+ export function registerThreeScene(scene, camera, renderer, three) {
232
+ try {
233
+ const THREE = three;
234
+ const r = renderer;
235
+ const sc = scene;
236
+ const raycaster = new THREE.Raycaster();
237
+ const v2 = new THREE.Vector2();
238
+ return bind(r.domElement, (cv, ev) => {
239
+ const rect = cv.getBoundingClientRect();
240
+ v2.x = ((ev.clientX - rect.left) / rect.width) * 2 - 1;
241
+ v2.y = -((ev.clientY - rect.top) / rect.height) * 2 + 1;
242
+ raycaster.setFromCamera(v2, camera);
243
+ const hits = raycaster.intersectObjects(sc.children ?? [], true);
244
+ if (!hits.length)
245
+ return null;
246
+ const obj = hits[0].object;
247
+ let named = obj;
248
+ while (named && !named.name && named.parent)
249
+ named = named.parent;
250
+ const label = named?.name || obj.name || obj.uuid || "mesh";
251
+ // Read creation provenance from the LEAF `obj`. Layer B's markSubtree stamps
252
+ // every node of a loaded/cloned subtree, and creationOf also walks `.parent`
253
+ // for a stamped ancestor — so loader-produced meshes resolve to their `load`
254
+ // call site and `new`-stamped ancestors are still found. [audit]
255
+ return { framework: "three", label, objectType: obj.type, path: obj.uuid, ...creationOf(obj) };
256
+ });
257
+ }
258
+ catch {
259
+ return () => { };
260
+ }
261
+ }
262
+ /** PixiJS (v8): hit-test the event boundary; label from object label/name. */
263
+ export function registerPixiApp(app) {
264
+ try {
265
+ const a = app;
266
+ const canvas = a.canvas ?? a.view;
267
+ return bind(canvas, (cv, ev) => {
268
+ const rect = cv.getBoundingClientRect();
269
+ const x = ev.clientX - rect.left;
270
+ const y = ev.clientY - rect.top;
271
+ const obj = a.renderer?.events?.rootBoundary?.hitTest?.(x, y);
272
+ if (!obj)
273
+ return null;
274
+ const objectType = pixiObjectType(obj);
275
+ const label = obj.label || obj.name || objectType || "displayObject";
276
+ return { framework: "pixi", label, objectType, ...creationOf(obj) };
277
+ });
278
+ }
279
+ catch {
280
+ return () => { };
281
+ }
282
+ }
283
+ /** Best-effort stable Pixi (v8) type name. Production builds minify
284
+ * `constructor.name` (→ "t", "e"), so duck-type the common display objects by
285
+ * their distinguishing fields first; fall back to the ctor name only when it
286
+ * survived minification (length > 1). [audit MEDIUM] */
287
+ function pixiObjectType(obj) {
288
+ // Order matters: `texture` is NOT unique to Sprite in Pixi v8 — a Mesh exposes
289
+ // it as a data getter and a Graphics as a METHOD — so test the distinctive
290
+ // fields first, and treat `texture` as a Sprite signal only when it's a real
291
+ // object (not a function). [audit MEDIUM]
292
+ if (typeof obj.text === "string")
293
+ return "Text";
294
+ const hasTextureObj = !!obj.texture && typeof obj.texture !== "function";
295
+ if (hasTextureObj && (obj.tilePosition || obj.tileScale))
296
+ return "TilingSprite";
297
+ if (obj.geometry && obj.shader)
298
+ return "Mesh";
299
+ if (obj.context)
300
+ return "Graphics";
301
+ if (hasTextureObj)
302
+ return "Sprite";
303
+ if (Array.isArray(obj.children))
304
+ return "Container";
305
+ const ctor = obj.constructor?.name;
306
+ return ctor && ctor.length > 1 ? ctor : undefined;
307
+ }
308
+ let deps = null;
309
+ let listening = false;
310
+ function onPointerDown(ev) {
311
+ const d = deps;
312
+ if (!d)
313
+ return;
314
+ for (const [canvas, resolver] of resolvers) {
315
+ try {
316
+ // Prune detached canvases (SPA route changes that re-register without an
317
+ // intervening uninstall leave stale entries holding detached elements). [audit LOW]
318
+ if (!canvas.isConnected) {
319
+ resolvers.delete(canvas);
320
+ continue;
321
+ }
322
+ const rect = canvas.getBoundingClientRect();
323
+ if (ev.clientX < rect.left || ev.clientX > rect.right ||
324
+ ev.clientY < rect.top || ev.clientY > rect.bottom)
325
+ continue;
326
+ const canvasId = d.canvasIdFor(canvas);
327
+ if (!canvasId)
328
+ continue;
329
+ const target = resolver(canvas, ev);
330
+ if (!target)
331
+ continue;
332
+ const w = canvas.width, h = canvas.height;
333
+ const x = rect.width ? Math.round((ev.clientX - rect.left) * w / rect.width) : 0;
334
+ const y = rect.height ? Math.round((ev.clientY - rect.top) * h / rect.height) : 0;
335
+ d.emitHit({ op: "hit", canvasId, t: d.now(), x, y, target });
336
+ }
337
+ catch {
338
+ /* a framework hiccup must never break recording */
339
+ }
340
+ }
341
+ }
342
+ export function installAdapters(d) {
343
+ deps = d;
344
+ // [creation-site stamping — Layer A] The build plugin rewrites `new X(...)` to
345
+ // `__sfnew(new X(...), id)`, whose helper invokes globalThis.__sfCreateSite(obj,
346
+ // id). Mark the object with its construction-site id so a later click resolves
347
+ // to the exact `new X()` line. Always safe to define (SSR: globalThis exists).
348
+ const g = globalThis;
349
+ g.__sfCreateSite = (o, id) => {
350
+ try {
351
+ if (o && typeof o === "object")
352
+ createIdOf.set(o, id);
353
+ }
354
+ catch { /* best effort */ }
355
+ };
356
+ // [creation-site stamping — Layer B] The build plugin rewrites object-PRODUCING
357
+ // calls `EXPR.method(args)` (load/loadAsync/parse/parseAsync/clone/copy) to
358
+ // `__sfcreate(() => EXPR.method(args), id)`, whose helper invokes
359
+ // globalThis.__sfCreateFrom(result, id). Mark the produced object subtree with
360
+ // the producer's call-site id so a later click on a loaded/cloned object
361
+ // resolves to that line. Async results (loadAsync/promise loaders) mark on
362
+ // resolution. Always safe to define (SSR: globalThis exists).
363
+ g.__sfCreateFrom = (result, id) => { createFrom(result, id); };
364
+ if (listening || typeof window === "undefined")
365
+ return;
366
+ window.addEventListener("pointerdown", onPointerDown, { capture: true, passive: true });
367
+ listening = true;
368
+ }
369
+ export function uninstallAdapters() {
370
+ deps = null;
371
+ if (listening && typeof window !== "undefined") {
372
+ window.removeEventListener("pointerdown", onPointerDown, { capture: true });
373
+ }
374
+ listening = false;
375
+ resolvers.clear();
376
+ try {
377
+ const g = globalThis;
378
+ delete g.__sfCreateSite;
379
+ delete g.__sfCreateFrom;
380
+ }
381
+ catch { /* ignore */ }
382
+ }
Binary file
Binary file