bee-ladybug 0.6.1 → 0.6.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bee-ladybug",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "Universal telemetry and visual inspection overlay for Canvas, DOM, Python and AI flows.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -70,6 +70,8 @@ export class CanvasAdapter {
70
70
  this.#fps = 0;
71
71
  this.#fpsSamples = [];
72
72
  this.#wasColliding = new Set();
73
+ this.#names = new Map();
74
+ this.#overlayStyle = null;
73
75
  this.#onResize = () => this.#syncOverlay();
74
76
  this.#onTick = () => {
75
77
  this.#rafId = requestAnimationFrame(this.#onTick);
@@ -93,6 +95,10 @@ export class CanvasAdapter {
93
95
  #fps;
94
96
  #fpsSamples;
95
97
  #wasColliding;
98
+ /** id -> display name, refreshed on every pump. */
99
+ #names;
100
+ /** Last style values written to the overlay. */
101
+ #overlayStyle;
96
102
  #onResize;
97
103
  #onTick;
98
104
 
@@ -126,6 +132,12 @@ export class CanvasAdapter {
126
132
  source: this.source,
127
133
  message: `CanvasAdapter attached (${this.entities.length} entities).`
128
134
  });
135
+ if (this.overlayEnabled && !this.canvas) {
136
+ this.core?.sendData('warn', {
137
+ source: this.source,
138
+ message: 'no canvas: hitboxes are not drawn. Pass { canvas } or call setCanvas().'
139
+ });
140
+ }
129
141
  this.core?.sendData('state', {
130
142
  source: this.source,
131
143
  key: 'adapter',
@@ -146,6 +158,11 @@ export class CanvasAdapter {
146
158
  window.removeEventListener('resize', this.#onResize);
147
159
  window.removeEventListener('scroll', this.#onResize, true);
148
160
  }
161
+ this.#wasColliding = new Set();
162
+ this.#names = new Map();
163
+ this.#lastPump = 0;
164
+ this.#fps = 0;
165
+ this.#fpsSamples = [];
149
166
  return this;
150
167
  }
151
168
 
@@ -163,10 +180,26 @@ export class CanvasAdapter {
163
180
  return this;
164
181
  }
165
182
 
183
+ /**
184
+ * Stop the internal loop and make a manual `pump()` valid again.
185
+ * Mirror of `startAutoPump()`.
186
+ */
187
+ stopAutoPump() {
188
+ this.autoPump = false;
189
+ this.#stopAutoPumpLoop();
190
+ return this;
191
+ }
192
+
166
193
  /** @param {HTMLCanvasElement | null} canvas */
167
194
  setCanvas(canvas) {
168
195
  this.canvas = canvas;
169
- this.#syncOverlay();
196
+ if (!this.#attached) return this;
197
+ if (!canvas) {
198
+ this.#unmountOverlay();
199
+ } else if (this.overlayEnabled) {
200
+ if (this.#overlay) this.#syncOverlay();
201
+ else this.#mountOverlay();
202
+ }
170
203
  return this;
171
204
  }
172
205
 
@@ -327,7 +360,9 @@ export class CanvasAdapter {
327
360
 
328
361
  #emitCollisionEdges(boxes) {
329
362
  const now = new Set();
363
+ const names = new Map();
330
364
  for (let i = 0; i < boxes.length; i++) {
365
+ names.set(boxes[i].id, boxes[i].name);
331
366
  if (!boxes[i].hit) continue;
332
367
  now.add(boxes[i].id);
333
368
  if (!this.#wasColliding.has(boxes[i].id)) {
@@ -339,13 +374,14 @@ export class CanvasAdapter {
339
374
  }
340
375
  for (const id of this.#wasColliding) {
341
376
  if (now.has(id)) continue;
342
- const name = idFromKey(id);
377
+ const name = names.get(id) ?? this.#names.get(id) ?? id;
343
378
  this.core.sendData('log', {
344
379
  source: this.source,
345
380
  message: `${name} left collision`
346
381
  });
347
382
  }
348
383
  this.#wasColliding = now;
384
+ this.#names = names;
349
385
  }
350
386
 
351
387
  #drawHitboxes(boxes, hostCtx) {
@@ -386,7 +422,7 @@ export class CanvasAdapter {
386
422
  }
387
423
 
388
424
  #mountOverlay() {
389
- if (typeof document === 'undefined' || this.#overlay) return;
425
+ if (typeof document === 'undefined' || this.#overlay || !this.canvas) return;
390
426
  const canvas = document.createElement('canvas');
391
427
  canvas.setAttribute('data-bee-ladybug-canvas', '');
392
428
  canvas.style.pointerEvents = 'none';
@@ -395,12 +431,14 @@ export class CanvasAdapter {
395
431
  canvas.style.margin = '0';
396
432
  document.body.appendChild(canvas);
397
433
  this.#overlay = canvas;
434
+ this.#overlayStyle = null;
398
435
  this.#syncOverlay();
399
436
  }
400
437
 
401
438
  #unmountOverlay() {
402
439
  if (this.#overlay?.parentNode) this.#overlay.parentNode.removeChild(this.#overlay);
403
440
  this.#overlay = null;
441
+ this.#overlayStyle = null;
404
442
  }
405
443
 
406
444
  #syncOverlay() {
@@ -408,14 +446,22 @@ export class CanvasAdapter {
408
446
  const overlay = this.#overlay;
409
447
  if (!host || !overlay) return;
410
448
  const rect = host.getBoundingClientRect();
411
- overlay.width = host.width;
412
- overlay.height = host.height;
413
- overlay.style.left = `${rect.left}px`;
414
- overlay.style.top = `${rect.top}px`;
415
- overlay.style.width = `${rect.width}px`;
416
- overlay.style.height = `${rect.height}px`;
449
+ // Assigning width/height clears and reallocates the bitmap: only when they change.
450
+ if (overlay.width !== host.width) overlay.width = host.width;
451
+ if (overlay.height !== host.height) overlay.height = host.height;
417
452
  const hidden = !this.#reporting || (this.core && this.core.visible === false);
418
- overlay.style.display = hidden ? 'none' : 'block';
453
+ const next = {
454
+ left: `${rect.left}px`,
455
+ top: `${rect.top}px`,
456
+ width: `${rect.width}px`,
457
+ height: `${rect.height}px`,
458
+ display: hidden ? 'none' : 'block'
459
+ };
460
+ const prev = this.#overlayStyle;
461
+ for (const key of Object.keys(next)) {
462
+ if (!prev || prev[key] !== next[key]) overlay.style[key] = next[key];
463
+ }
464
+ this.#overlayStyle = next;
419
465
  }
420
466
 
421
467
  #enable() {
@@ -487,11 +533,6 @@ function isPrimary(entity) {
487
533
  return key === 'player' || key === 'hero' || entity.primary === true;
488
534
  }
489
535
 
490
- function idFromKey(id) {
491
- const parts = String(id).split(':');
492
- return parts[parts.length - 1] || id;
493
- }
494
-
495
536
  function round1(value) {
496
537
  return Math.round(Number(value) * 10) / 10;
497
538
  }
@@ -17,6 +17,12 @@
17
17
  import { isReportNoise } from '../core/ReportExporter.js';
18
18
 
19
19
  const MAX_PROBLEM_ROWS = 40;
20
+ const OBSERVE_OPTIONS = Object.freeze({
21
+ childList: true,
22
+ subtree: true,
23
+ attributes: true,
24
+ attributeFilter: ['class', 'style', 'hidden', 'disabled', 'open']
25
+ });
20
26
 
21
27
  export const WEB_DOM_ADAPTER_DEFAULTS = Object.freeze({
22
28
  overlay: true,
@@ -74,6 +80,7 @@ export class WebDOMAdapter {
74
80
  this.#seenAttr = 0;
75
81
  this.#mutTimer = 0;
76
82
  this.#lastHoverKey = '';
83
+ this.#overlayStyle = null;
77
84
  this.#onMove = (event) => {
78
85
  this.#pointer.x = event.clientX;
79
86
  this.#pointer.y = event.clientY;
@@ -104,6 +111,8 @@ export class WebDOMAdapter {
104
111
  #seenAttr;
105
112
  #mutTimer;
106
113
  #lastHoverKey;
114
+ /** Last style values written to the overlay. */
115
+ #overlayStyle;
107
116
  #onMove;
108
117
  #onResize;
109
118
  #onTick;
@@ -118,6 +127,7 @@ export class WebDOMAdapter {
118
127
  if (this.core && typeof this.core.subscribe === 'function') {
119
128
  this.#unsubscribe = this.core.subscribe((packet) => this.#onCore(packet));
120
129
  }
130
+ this.#reporting = true;
121
131
  if (this.overlayEnabled) this.#mountOverlay();
122
132
  if (typeof window !== 'undefined') {
123
133
  window.addEventListener('mousemove', this.#onMove, true);
@@ -126,18 +136,12 @@ export class WebDOMAdapter {
126
136
  }
127
137
  if (this.root && typeof MutationObserver !== 'undefined') {
128
138
  this.#observer = new MutationObserver(this.#onMutate);
129
- this.#observer.observe(this.root, {
130
- childList: true,
131
- subtree: true,
132
- attributes: true,
133
- attributeFilter: ['class', 'style', 'hidden', 'disabled', 'open']
134
- });
139
+ this.#observer.observe(this.root, OBSERVE_OPTIONS);
135
140
  }
136
141
  if (this.autoTick && typeof requestAnimationFrame === 'function') {
137
142
  this.#rafId = requestAnimationFrame(this.#onTick);
138
143
  }
139
144
 
140
- this.#reporting = true;
141
145
  this.core?.registerAdapter('dom', {
142
146
  label: 'DOM',
143
147
  enabled: true,
@@ -178,24 +182,41 @@ export class WebDOMAdapter {
178
182
  window.removeEventListener('scroll', this.#onResize, true);
179
183
  }
180
184
  this.#unmountOverlay();
185
+ this.hover = null;
186
+ this.#lastHoverKey = '';
187
+ this.#lastPump = 0;
188
+ this.#fps = 0;
189
+ this.#pointer = { x: 0, y: 0, dirty: false };
190
+ this.#mutAdded = 0;
191
+ this.#mutRemoved = 0;
192
+ this.#mutAttr = 0;
193
+ this.#seenAdded = 0;
194
+ this.#seenRemoved = 0;
195
+ this.#seenAttr = 0;
181
196
  return this;
182
197
  }
183
198
 
184
199
  /** @param {ParentNode | null} root */
185
200
  setRoot(root) {
186
201
  this.root = root;
187
- if (this.#attached && this.#observer && root) {
188
- this.#observer.disconnect();
189
- this.#observer.observe(root, {
190
- childList: true,
191
- subtree: true,
192
- attributes: true,
193
- attributeFilter: ['class', 'style', 'hidden', 'disabled', 'open']
194
- });
202
+ if (this.#observer) this.#observer.disconnect();
203
+ if (!root) {
204
+ this.#observer = null;
205
+ } else if (this.#attached && typeof MutationObserver !== 'undefined') {
206
+ if (!this.#observer) this.#observer = new MutationObserver(this.#onMutate);
207
+ this.#observer.observe(root, OBSERVE_OPTIONS);
195
208
  }
209
+ if (this.hover && !this.#rootHolds(this.hover)) this.hover = null;
196
210
  return this;
197
211
  }
198
212
 
213
+ #rootHolds(node) {
214
+ const root = this.root;
215
+ if (!root) return false;
216
+ if (typeof document !== 'undefined' && root === document) return true;
217
+ return node === root || Boolean(root.contains?.(node));
218
+ }
219
+
199
220
  /**
200
221
  * Box model of the hover and watched nodes, layout problems under root,
201
222
  * and the latest mutation counts. Returns null when detached.
@@ -311,6 +332,7 @@ export class WebDOMAdapter {
311
332
  this.#lastHoverKey = '';
312
333
  this.core.sendData('state', { source: this.source, key: 'hover', value: '—' });
313
334
  this.core.sendData('state', { source: this.source, key: 'box', value: '—' });
335
+ this.core.sendData('state', { source: this.source, key: 'display', value: '—' });
314
336
  }
315
337
  return;
316
338
  }
@@ -341,7 +363,7 @@ export class WebDOMAdapter {
341
363
  }
342
364
 
343
365
  #queueMutations(records) {
344
- if (this.frozen) return;
366
+ if (this.frozen || !this.#reporting) return;
345
367
  for (let i = 0; i < records.length; i++) {
346
368
  const rec = records[i];
347
369
  if (isBeeChrome(rec.target)) continue;
@@ -433,12 +455,14 @@ export class WebDOMAdapter {
433
455
  canvas.style.margin = '0';
434
456
  document.body.appendChild(canvas);
435
457
  this.#overlay = canvas;
458
+ this.#overlayStyle = null;
436
459
  this.#syncOverlay();
437
460
  }
438
461
 
439
462
  #unmountOverlay() {
440
463
  if (this.#overlay?.parentNode) this.#overlay.parentNode.removeChild(this.#overlay);
441
464
  this.#overlay = null;
465
+ this.#overlayStyle = null;
442
466
  }
443
467
 
444
468
  #syncOverlay() {
@@ -447,12 +471,22 @@ export class WebDOMAdapter {
447
471
  const dpr = Math.min(window.devicePixelRatio || 1, 2);
448
472
  const w = window.innerWidth;
449
473
  const h = window.innerHeight;
450
- canvas.style.width = `${w}px`;
451
- canvas.style.height = `${h}px`;
452
- canvas.width = Math.round(w * dpr);
453
- canvas.height = Math.round(h * dpr);
474
+ // Assigning width/height clears and reallocates the bitmap: only when they change.
475
+ const bitmapW = Math.round(w * dpr);
476
+ const bitmapH = Math.round(h * dpr);
477
+ if (canvas.width !== bitmapW) canvas.width = bitmapW;
478
+ if (canvas.height !== bitmapH) canvas.height = bitmapH;
454
479
  const hidden = !this.#reporting || (this.core && this.core.visible === false);
455
- canvas.style.display = hidden ? 'none' : 'block';
480
+ const next = {
481
+ width: `${w}px`,
482
+ height: `${h}px`,
483
+ display: hidden ? 'none' : 'block'
484
+ };
485
+ const prev = this.#overlayStyle;
486
+ for (const key of Object.keys(next)) {
487
+ if (!prev || prev[key] !== next[key]) canvas.style[key] = next[key];
488
+ }
489
+ this.#overlayStyle = next;
456
490
  }
457
491
 
458
492
  #enable() {
@@ -462,6 +496,11 @@ export class WebDOMAdapter {
462
496
 
463
497
  #disable() {
464
498
  this.#reporting = false;
499
+ if (this.#mutTimer && typeof clearTimeout === 'function') clearTimeout(this.#mutTimer);
500
+ this.#mutTimer = 0;
501
+ this.#mutAdded = 0;
502
+ this.#mutRemoved = 0;
503
+ this.#mutAttr = 0;
465
504
  this.#syncOverlay();
466
505
  }
467
506
 
@@ -10,7 +10,7 @@ import { UIOverlay } from './UIOverlay.js';
10
10
  import { buildAssistantSnapshot, normalizeAssistant } from './AiAssistant.js';
11
11
  import { exportReport as writeReport } from './ReportExporter.js';
12
12
 
13
- export const BEE_LADYBUG_VERSION = '0.6.1';
13
+ export const BEE_LADYBUG_VERSION = '0.6.3';
14
14
 
15
15
  export const CORE_DEFAULTS = Object.freeze({
16
16
  toggleKey: 'F2',
package/src/index.d.ts CHANGED
@@ -334,6 +334,7 @@ export class CanvasAdapter {
334
334
  attach(): this;
335
335
  detach(): this;
336
336
  startAutoPump(): this;
337
+ stopAutoPump(): this;
337
338
  setCanvas(canvas: HTMLCanvasElement | null): this;
338
339
  setEntities(entities: CanvasEntity[]): this;
339
340
  pump(ctx?: CanvasRenderingContext2D): this;
@@ -355,6 +356,8 @@ export interface WebDOMAdapterOptions {
355
356
 
356
357
  export const WEB_DOM_ADAPTER_DEFAULTS: Required<Omit<WebDOMAdapterOptions, 'root'>>;
357
358
 
359
+ export function domNoiseQuiet(cssEnabled: boolean, frozen: boolean, timeScale: number): boolean;
360
+
358
361
  export class WebDOMAdapter {
359
362
  core: BeeLadybugCore | null;
360
363
  root: ParentNode | null;
package/src/index.js CHANGED
@@ -2,7 +2,7 @@ export { BeeLadybugCore, BEE_LADYBUG_VERSION, CORE_DEFAULTS, WELL_KNOWN_TYPES, f
2
2
  export { exportReport, isReportNoise } from './core/ReportExporter.js';
3
3
  export { UIOverlay } from './core/UIOverlay.js';
4
4
  export { CanvasAdapter, CANVAS_ADAPTER_DEFAULTS } from './adapters/CanvasAdapter.js';
5
- export { WebDOMAdapter, WEB_DOM_ADAPTER_DEFAULTS } from './adapters/WebDOMAdapter.js';
5
+ export { WebDOMAdapter, WEB_DOM_ADAPTER_DEFAULTS, domNoiseQuiet } from './adapters/WebDOMAdapter.js';
6
6
  export { CSSAdapter, CSS_ADAPTER_DEFAULTS } from './adapters/CSSAdapter.js';
7
7
  export { AntAdapter, ANT_ADAPTER_DEFAULTS } from './adapters/AntAdapter.js';
8
8
  export { SpiderAdapter, SPIDER_ADAPTER_DEFAULTS } from './adapters/SpiderAdapter.js';