bee-ladybug 0.6.3 → 0.6.5

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/README.MD CHANGED
@@ -1,10 +1,19 @@
1
1
 
2
2
  # BeeLadybug
3
+ [![types](https://img.shields.io/npm/types/bee-ladybug)](https://www.npmjs.com/package/bee-ladybug)
4
+ [![npm](https://img.shields.io/npm/v/bee-ladybug)](https://www.npmjs.com/package/bee-ladybug)
5
+ [![download](https://img.shields.io/npm/dm/bee-ladybug)](https://www.npmjs.com/package/bee-ladybug)
6
+ [![](https://data.jsdelivr.com/v1/package/npm/bee-ladybug/badge)](https://www.jsdelivr.com/package/npm/bee-ladybug)
7
+ ![licenza](https://img.shields.io/npm/l/bee-ladybug)
8
+ [![Socket Badge](https://badge.socket.dev/npm/package/bee-ladybug/0.6.5)](https://badge.socket.dev/npm/package/bee-ladybug/0.6.5)
9
+ [![size](https://img.shields.io/bundlephobia/minzip/bee-ladybug)](https://bundlephobia.com/package/bee-ladybug)
10
+ [![Snyk](https://snyk.io/test/npm/bee-ladybug/badge.svg)](https://security.snyk.io/package/npm/bee-ladybug)
3
11
 
4
12
  <p align="center">
5
13
  <img src="https://beeenginejs.com/wp-content/uploads/2026/10/beeladybug-logo-animato-ezgif.com-effects.gif" alt="BeeEngine Logo" width="900" />
6
14
  </p>
7
15
 
16
+
8
17
  Universal telemetry and visual inspection overlay.
9
18
 
10
19
  BeeLadybug is **not** a Canvas widget. It is a small open-source debugger
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bee-ladybug",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
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",
@@ -38,6 +38,7 @@ export class AntAdapter {
38
38
  this.source = cfg.source;
39
39
 
40
40
  this.#attached = false;
41
+ this.#active = false;
41
42
  this.#observer = null;
42
43
  this.#counts = new WeakMap();
43
44
  this.#hot = [];
@@ -45,6 +46,8 @@ export class AntAdapter {
45
46
  }
46
47
 
47
48
  #attached;
49
+ /** False while the ANT button is off: setRoot() must not restart the observer then. */
50
+ #active;
48
51
  #observer;
49
52
  #counts;
50
53
  #hot;
@@ -53,17 +56,19 @@ export class AntAdapter {
53
56
  attach() {
54
57
  if (this.#attached) return this;
55
58
  this.#attached = true;
56
- this.#enable();
59
+ this.#turnOn();
57
60
  this.core?.registerAdapter('ant', {
58
- enable: () => this.#enable(),
61
+ enable: () => this.#turnOn(),
59
62
  disable: () => this.#disable(),
60
63
  label: 'ANT',
61
64
  snapshot: () => this.snapshot()
62
65
  });
63
- this.core?.sendData('log', {
64
- source: this.source,
65
- message: 'AntAdapter attached. Watching DOM mutation rate.'
66
- });
66
+ if (this.#observer) {
67
+ this.core?.sendData('log', {
68
+ source: this.source,
69
+ message: 'AntAdapter attached. Watching DOM mutation rate.'
70
+ });
71
+ }
67
72
  return this;
68
73
  }
69
74
 
@@ -75,6 +80,19 @@ export class AntAdapter {
75
80
  return this;
76
81
  }
77
82
 
83
+ /**
84
+ * Watch another root. Counters and hot rows restart; `null` stops watching.
85
+ * @param {ParentNode | null} root
86
+ */
87
+ setRoot(root) {
88
+ this.root = root;
89
+ if (!this.#attached) return this;
90
+ this.#reset();
91
+ if (!root || !this.#active) return this;
92
+ this.#warnNotWatching(this.#observe());
93
+ return this;
94
+ }
95
+
78
96
  /**
79
97
  * Nodes whose current window is still at or above the threshold.
80
98
  * Hidden boxes, the WordPress admin bar, and BeeLadybug chrome are skipped.
@@ -99,49 +117,83 @@ export class AntAdapter {
99
117
  return { name: 'ant', items };
100
118
  }
101
119
 
102
- #enable() {
103
- if (!this.#attached || this.#observer) return;
104
- if (typeof MutationObserver === 'undefined' || !this.root) return;
120
+ #turnOn() {
121
+ if (!this.#attached) return;
122
+ this.#active = true;
123
+ this.#warnNotWatching(this.#observe());
124
+ }
125
+
126
+ #disable() {
127
+ this.#active = false;
128
+ this.#reset();
129
+ }
130
+
131
+ #reset() {
132
+ if (this.#observer) this.#observer.disconnect();
133
+ this.#observer = null;
134
+ this.#counts = new WeakMap();
135
+ this.#hot = [];
136
+ }
137
+
138
+ /** @returns {'' | 'no-root' | 'no-observer' | 'failed'} why nothing is watched, empty when it is */
139
+ #observe() {
140
+ if (this.#observer) return '';
141
+ if (!this.root) return 'no-root';
142
+ if (typeof MutationObserver === 'undefined') return 'no-observer';
105
143
  this.#counts = new WeakMap();
106
- this.#observer = new MutationObserver(this.#onMutate);
144
+ const observer = new MutationObserver(this.#onMutate);
107
145
  try {
108
- this.#observer.observe(this.root, {
146
+ observer.observe(this.root, {
109
147
  childList: true,
110
148
  subtree: true,
111
149
  attributes: true,
112
150
  characterData: true
113
151
  });
114
152
  } catch {
115
- this.#observer.disconnect();
116
- this.#observer = null;
153
+ observer.disconnect();
154
+ return 'failed';
117
155
  }
156
+ this.#observer = observer;
157
+ return '';
118
158
  }
119
159
 
120
- #disable() {
121
- if (this.#observer) this.#observer.disconnect();
122
- this.#observer = null;
123
- this.#counts = new WeakMap();
124
- this.#hot = [];
160
+ #warnNotWatching(reason) {
161
+ if (!reason) return;
162
+ const messages = {
163
+ 'no-root': 'no root: mutation rate is not watched. Pass { root } or call setRoot().',
164
+ 'no-observer': 'MutationObserver is missing: mutation rate is not watched.',
165
+ failed: 'observe() failed: mutation rate is not watched.'
166
+ };
167
+ this.core?.sendData('warn', { source: this.source, message: messages[reason] });
125
168
  }
126
169
 
127
170
  #ingest(records) {
128
171
  if (!this.#attached || !this.core || !records?.length) return;
129
172
  const now = typeof performance !== 'undefined' ? performance.now() : Date.now();
173
+ this.#prune(now);
174
+ // One layout read per element per batch, however many records it produced.
175
+ const skipped = new Map();
130
176
  for (let i = 0; i < records.length; i++) {
131
- const node = records[i]?.target;
132
- if (!node || isBeeChrome(node)) continue;
177
+ const el = elementOf(records[i]?.target);
178
+ if (!el) continue;
179
+ let skip = skipped.get(el);
180
+ if (skip === undefined) {
181
+ skip = isBeeChrome(el) || isReportNoise(el);
182
+ skipped.set(el, skip);
183
+ }
184
+ if (skip) continue;
133
185
 
134
- let slot = this.#counts.get(node);
186
+ let slot = this.#counts.get(el);
135
187
  if (!slot || now - slot.windowStart >= this.windowMs) {
136
188
  slot = { count: 0, windowStart: now, warned: false };
137
- this.#counts.set(node, slot);
189
+ this.#counts.set(el, slot);
138
190
  }
139
191
  slot.count += 1;
140
- this.#track(node, slot);
192
+ this.#track(el, slot);
141
193
  if (slot.count < this.threshold || slot.warned) continue;
142
194
 
143
195
  slot.warned = true;
144
- const selector = nodeSelector(node);
196
+ const selector = nodeSelector(el);
145
197
  this.core.sendData('warn', {
146
198
  source: this.source,
147
199
  message: `High Mutation Rate on ${selector}`,
@@ -150,21 +202,36 @@ export class AntAdapter {
150
202
  }
151
203
  }
152
204
 
153
- #track(node, slot) {
154
- const index = this.#hot.findIndex((row) => row.node === node);
205
+ #prune(now) {
206
+ this.#hot = this.#hot.filter((row) => now - row.windowStart < this.windowMs);
207
+ }
208
+
209
+ #track(el, slot) {
210
+ const index = this.#hot.findIndex((row) => row.node === el);
155
211
  if (slot.count < this.threshold) {
156
212
  if (index >= 0) this.#hot.splice(index, 1);
157
213
  return;
158
214
  }
159
215
  const row = {
160
- node,
161
- selector: nodeSelector(node),
216
+ node: el,
217
+ selector: nodeSelector(el),
162
218
  count: slot.count,
163
219
  windowStart: slot.windowStart
164
220
  };
165
221
  if (index >= 0) this.#hot[index] = row;
166
222
  else this.#hot.push(row);
167
- if (this.#hot.length > MAX_HOT) this.#hot.splice(0, this.#hot.length - MAX_HOT);
223
+ while (this.#hot.length > MAX_HOT) this.#hot.splice(this.#lowestRow(), 1);
224
+ }
225
+
226
+ /** Fewest mutations first; on a tie the older window goes. */
227
+ #lowestRow() {
228
+ let worst = 0;
229
+ for (let i = 1; i < this.#hot.length; i++) {
230
+ const row = this.#hot[i];
231
+ const low = this.#hot[worst];
232
+ if (row.count < low.count || (row.count === low.count && row.windowStart < low.windowStart)) worst = i;
233
+ }
234
+ return worst;
168
235
  }
169
236
  }
170
237
 
@@ -173,15 +240,26 @@ function positive(value, fallback) {
173
240
  return Number.isFinite(n) && n > 0 ? n : fallback;
174
241
  }
175
242
 
243
+ /** A text node counts on its parent element, so one element keeps one window. */
244
+ function elementOf(node) {
245
+ if (!node) return null;
246
+ if (node.nodeType === 1) return node;
247
+ return node.parentElement || null;
248
+ }
249
+
176
250
  function nodeSelector(node) {
177
- const el = node && node.nodeType === 1 ? node : node?.parentElement;
251
+ const el = elementOf(node);
178
252
  if (!el || !el.tagName) return 'node';
179
- if (el.id) return `#${el.id}`;
253
+ if (el.id) return `#${cssEscape(el.id)}`;
180
254
  const cls = el.classList && el.classList[0];
181
- if (cls) return `.${cls}`;
255
+ if (cls) return `.${cssEscape(cls)}`;
182
256
  return el.tagName.toLowerCase();
183
257
  }
184
258
 
259
+ function cssEscape(value) {
260
+ return String(value).replace(/[^\w-]/g, '\\$&');
261
+ }
262
+
185
263
  function isBeeChrome(node) {
186
264
  let el = node && node.nodeType === 1 ? node : node?.parentElement;
187
265
  while (el) {
@@ -189,6 +267,7 @@ function isBeeChrome(node) {
189
267
  if (tag === 'BEE-LADYBUG-OVERLAY') return true;
190
268
  if (el.hasAttribute?.('data-bee-ladybug-dom')) return true;
191
269
  if (el.hasAttribute?.('data-bee-ladybug-canvas')) return true;
270
+ if (el.hasAttribute?.('data-bee-ladybug-css')) return true;
192
271
  el = el.parentElement;
193
272
  }
194
273
  return false;
@@ -61,9 +61,13 @@ export class CSSAdapter {
61
61
  this.#hoverBox = null;
62
62
  this.#pinBox = null;
63
63
  this.#observer = null;
64
+ this.#unsubscribe = null;
65
+ this.#hasPointer = false;
64
66
  this.#onMove = (event) => {
67
+ if (this.core && this.core.visible === false) return;
65
68
  this.#x = event.clientX;
66
69
  this.#y = event.clientY;
70
+ this.#hasPointer = true;
67
71
  this.#schedule();
68
72
  };
69
73
  this.#onKey = (event) => this.#handleKey(event);
@@ -71,7 +75,7 @@ export class CSSAdapter {
71
75
  this.#onMutate = (records) => {
72
76
  if (!records.some(touchesSheet)) return;
73
77
  this.#sheetsDirty = true;
74
- this.#schedule();
78
+ if (this.#hasPointer) this.#schedule();
75
79
  };
76
80
  }
77
81
 
@@ -92,6 +96,9 @@ export class CSSAdapter {
92
96
  #hoverBox;
93
97
  #pinBox;
94
98
  #observer;
99
+ #unsubscribe;
100
+ /** True after the first mousemove: before that (x, y) is not a real pointer. */
101
+ #hasPointer;
95
102
  #onMove;
96
103
  #onKey;
97
104
  #onScroll;
@@ -104,6 +111,9 @@ export class CSSAdapter {
104
111
  attach() {
105
112
  if (this.#attached) return this;
106
113
  this.#attached = true;
114
+ if (this.core && typeof this.core.subscribe === 'function') {
115
+ this.#unsubscribe = this.core.subscribe((packet) => this.#onCore(packet));
116
+ }
107
117
  this.core?.registerAdapter('css', {
108
118
  label: this.label,
109
119
  hotkey: this.hotkey,
@@ -128,10 +138,21 @@ export class CSSAdapter {
128
138
  if (!this.#attached) return this;
129
139
  this.#disable();
130
140
  this.#attached = false;
141
+ if (this.#unsubscribe) this.#unsubscribe();
142
+ this.#unsubscribe = null;
131
143
  this.core?.unregisterAdapter('css');
132
144
  return this;
133
145
  }
134
146
 
147
+ #onCore(packet) {
148
+ if (packet.type !== 'sys' || packet.payload?.op !== 'visibility') return;
149
+ this.#placeBoxes();
150
+ }
151
+
152
+ #boxesVisible() {
153
+ return !(this.core && this.core.visible === false);
154
+ }
155
+
135
156
  /** Page scan. Click a row to outline one element of that group. */
136
157
  scan() {
137
158
  if (!this.#active || typeof document === 'undefined') return this;
@@ -141,15 +162,15 @@ export class CSSAdapter {
141
162
  const rows = [];
142
163
  for (let i = 0; i < nodes.length; i++) {
143
164
  const el = nodes[i];
144
- if (!el || isBeeChrome(el) || isSkippedTag(el)) continue;
165
+ if (!el || isBeeChrome(el) || isSkippedTag(el) || isReportNoise(el)) continue;
145
166
  const measured = measureViewportOverflow(el);
146
167
  if (measured) overflow.push(measured);
147
168
  const findings = [];
148
169
  pushNegativeMargins(el, findings);
149
170
  pushInvisible(el, findings);
150
- pushBlockers(el, findings);
171
+ pushBlockers(el, findings, true);
151
172
  pushImage(el, findings);
152
- pushStacking(el, findings);
173
+ pushStacking(el, findings, true);
153
174
  pushImportant(el, this.#rules, findings);
154
175
  for (let j = 0; j < findings.length; j++) {
155
176
  rows.push({ el, selector: cssPath(el), ...findings[j] });
@@ -245,7 +266,9 @@ export class CSSAdapter {
245
266
  for (let i = 0; i < rows.length; i++) {
246
267
  const row = rows[i];
247
268
  if (describeCause(row.kind, row.selector)) {
248
- causes = addCause(causes, { kind: row.kind, path: row.selector, el: row.el }, MAX_SNAPSHOT);
269
+ // No cap here: MAX_SNAPSHOT applies after the severity sort, so an info row
270
+ // can never push a real error out.
271
+ causes = addCause(causes, { kind: row.kind, path: row.selector, el: row.el }, Infinity);
249
272
  } else {
250
273
  plain.push(row);
251
274
  }
@@ -319,6 +342,7 @@ export class CSSAdapter {
319
342
  #disable() {
320
343
  if (!this.#active) return;
321
344
  this.#active = false;
345
+ this.#hasPointer = false;
322
346
  if (typeof window !== 'undefined') {
323
347
  window.removeEventListener('mousemove', this.#onMove, true);
324
348
  window.removeEventListener('keydown', this.#onKey);
@@ -340,11 +364,16 @@ export class CSSAdapter {
340
364
  this.core?.publishDetail([]);
341
365
  this.core?.publishIssues([]);
342
366
  this.core?.sendData('state', { source: this.source, key: 'css.hunt', value: 'off' });
367
+ this.core?.sendData('state', { source: this.source, key: 'css.target', value: '—' });
368
+ this.core?.sendData('state', { source: this.source, key: 'css.hits', value: '0' });
369
+ this.core?.sendData('state', { source: this.source, key: 'css.pin', value: 'off' });
343
370
  this.core?.sendData('log', { source: this.source, message: 'CSS hunt off.' });
344
371
  }
345
372
 
346
373
  #handleKey(event) {
347
374
  if (!this.#active || event.repeat) return;
375
+ if (event.ctrlKey || event.metaKey || event.altKey) return;
376
+ if (this.core && this.core.visible === false) return;
348
377
  if (isTypingTarget(event.target)) return;
349
378
  if (event.code === this.pinKey) {
350
379
  event.preventDefault();
@@ -377,22 +406,23 @@ export class CSSAdapter {
377
406
 
378
407
  #inspect(x, y) {
379
408
  const el = pickElement(x, y);
380
- placeBox(this.#hoverBox, el);
409
+ placeBox(this.#hoverBox, el, this.#boxesVisible());
381
410
  if (this.#panelLock) return;
382
411
  if (el === this.#lastEl) return;
383
412
  this.#lastEl = el;
384
413
  if (!el) {
385
414
  this.core?.publishDetail([]);
386
415
  this.core?.sendData('state', { source: this.source, key: 'css.target', value: '—' });
416
+ this.core?.sendData('state', { source: this.source, key: 'css.hits', value: '0' });
387
417
  return;
388
418
  }
389
419
  const findings = [];
390
420
  pushImportant(el, this.#rules, findings);
391
421
  pushOverflow(el, findings);
392
422
  pushInvisible(el, findings);
393
- pushBlockers(el, findings);
423
+ pushBlockers(el, findings, true);
394
424
  pushImage(el, findings);
395
- pushStacking(el, findings);
425
+ pushStacking(el, findings, true);
396
426
  const path = cssPath(el);
397
427
  this.core?.sendData('state', { source: this.source, key: 'css.target', value: path });
398
428
  this.core?.sendData('state', {
@@ -419,7 +449,7 @@ export class CSSAdapter {
419
449
  if (this.#panelLock) {
420
450
  this.#panelLock = false;
421
451
  this.#pinned = null;
422
- placeBox(this.#pinBox, null);
452
+ placeBox(this.#pinBox, null, this.#boxesVisible());
423
453
  this.core?.sendData('state', { source: this.source, key: 'css.pin', value: 'off' });
424
454
  this.core?.sendData('log', { source: this.source, message: 'CSS pin off.' });
425
455
  this.#lastEl = null;
@@ -504,15 +534,16 @@ export class CSSAdapter {
504
534
 
505
535
  #pin(el) {
506
536
  this.#pinned = el;
507
- placeBox(this.#pinBox, el);
537
+ placeBox(this.#pinBox, el, this.#boxesVisible());
508
538
  if (el && typeof el.scrollIntoView === 'function') {
509
539
  el.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'smooth' });
510
540
  }
511
541
  }
512
542
 
513
543
  #placeBoxes() {
514
- placeBox(this.#hoverBox, this.#lastEl);
515
- placeBox(this.#pinBox, this.#pinned);
544
+ const visible = this.#boxesVisible();
545
+ placeBox(this.#hoverBox, this.#lastEl, visible);
546
+ placeBox(this.#pinBox, this.#pinned, visible);
516
547
  }
517
548
 
518
549
  #mountBoxes() {
@@ -538,6 +569,8 @@ function collectImportantRules() {
538
569
  const sheets = document.styleSheets;
539
570
  for (let i = 0; i < sheets.length; i++) {
540
571
  const sheet = sheets[i];
572
+ if (sheet.disabled === true) continue;
573
+ if (sheet.media && sheet.media.mediaText && !mediaMatches(sheet.media.mediaText)) continue;
541
574
  let list = null;
542
575
  try {
543
576
  list = sheet.cssRules;
@@ -550,21 +583,21 @@ function collectImportantRules() {
550
583
  return { rules, unreadable };
551
584
  }
552
585
 
586
+ function mediaMatches(mediaText) {
587
+ try {
588
+ return typeof matchMedia === 'function' ? matchMedia(mediaText).matches : true;
589
+ } catch {
590
+ return true;
591
+ }
592
+ }
593
+
553
594
  function walkRules(list, source, into) {
554
595
  if (!list) return;
555
596
  for (let i = 0; i < list.length; i++) {
556
597
  const rule = list[i];
557
598
  if (!rule) continue;
558
599
  if (rule.type === 4 && rule.media) {
559
- let active = true;
560
- try {
561
- active = typeof matchMedia === 'function'
562
- ? matchMedia(rule.media.mediaText).matches
563
- : true;
564
- } catch {
565
- active = true;
566
- }
567
- if (active) walkRules(rule.cssRules, source, into);
600
+ if (mediaMatches(rule.media.mediaText)) walkRules(rule.cssRules, source, into);
568
601
  continue;
569
602
  }
570
603
  if (rule.type === 1 && rule.selectorText && rule.style) {
@@ -745,7 +778,9 @@ function pushInvisible(el, findings) {
745
778
  findings.push(issue('invisible text (opacity 0)', ISSUE_RANK.invisible, 30));
746
779
  }
747
780
  const color = parseColor(style.color);
748
- if (!color || color.a === 0) {
781
+ // Unknown syntax (oklch, lab, color(), color-mix): nothing to compare, nothing to report.
782
+ if (!color) return;
783
+ if (color.a === 0) {
749
784
  findings.push(issue('invisible text (transparent color)', ISSUE_RANK.invisible, 30));
750
785
  return;
751
786
  }
@@ -1096,7 +1131,7 @@ function pickElement(x, y) {
1096
1131
  const stack = document.elementsFromPoint(x, y);
1097
1132
  for (let i = 0; i < stack.length; i++) {
1098
1133
  const el = stack[i];
1099
- if (!el || isBeeChrome(el)) continue;
1134
+ if (!el || isBeeChrome(el) || isReportNoise(el)) continue;
1100
1135
  if (el === document.documentElement || el === document.body) continue;
1101
1136
  return el;
1102
1137
  }
@@ -1154,12 +1189,16 @@ function hasOwnText(el) {
1154
1189
  function cssPath(el) {
1155
1190
  if (!el || !el.tagName) return 'node';
1156
1191
  const tag = el.tagName.toLowerCase();
1157
- if (el.id) return `${tag}#${el.id}`;
1192
+ if (el.id) return `${tag}#${cssEscape(el.id)}`;
1158
1193
  const classes = el.classList ? [...el.classList].slice(0, 2) : [];
1159
- if (classes.length) return `${tag}.${classes.join('.')}`;
1194
+ if (classes.length) return `${tag}.${classes.map(cssEscape).join('.')}`;
1160
1195
  return tag;
1161
1196
  }
1162
1197
 
1198
+ function cssEscape(value) {
1199
+ return String(value).replace(/[^\w-]/g, '\\$&');
1200
+ }
1201
+
1163
1202
  function isSkippedTag(el) {
1164
1203
  const tag = el.tagName;
1165
1204
  return tag === 'SCRIPT' || tag === 'STYLE' || tag === 'LINK' || tag === 'META';
@@ -1179,7 +1218,10 @@ function touchesSheet(record) {
1179
1218
  const name = record.target?.nodeName;
1180
1219
  return name === 'STYLE' || name === 'LINK';
1181
1220
  }
1182
- const nodes = [record.target];
1221
+ // Only what was added or removed: the target's whole subtree always holds some <style> or <link>.
1222
+ // A <style> that receives new text is the one target worth checking, and that check is O(1).
1223
+ if (record.target?.nodeName === 'STYLE') return true;
1224
+ const nodes = [];
1183
1225
  if (record.addedNodes) {
1184
1226
  for (let i = 0; i < record.addedNodes.length; i++) nodes.push(record.addedNodes[i]);
1185
1227
  }
@@ -1220,9 +1262,9 @@ function makeBox(kind, border, fill) {
1220
1262
  return node;
1221
1263
  }
1222
1264
 
1223
- function placeBox(node, el) {
1265
+ function placeBox(node, el, visible = true) {
1224
1266
  if (!node) return;
1225
- if (!el || typeof el.getBoundingClientRect !== 'function' || isBeeChrome(el)) {
1267
+ if (visible === false || !el || typeof el.getBoundingClientRect !== 'function' || isBeeChrome(el)) {
1226
1268
  node.style.display = 'none';
1227
1269
  return;
1228
1270
  }
@@ -520,6 +520,7 @@ function isBeeChrome(node) {
520
520
  if (tag === 'BEE-LADYBUG-OVERLAY') return true;
521
521
  if (el.hasAttribute?.('data-bee-ladybug-dom')) return true;
522
522
  if (el.hasAttribute?.('data-bee-ladybug-canvas')) return true;
523
+ if (el.hasAttribute?.('data-bee-ladybug-css')) return true;
523
524
  el = el.parentElement;
524
525
  }
525
526
  return false;
@@ -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.3';
13
+ export const BEE_LADYBUG_VERSION = '0.6.5';
14
14
 
15
15
  export const CORE_DEFAULTS = Object.freeze({
16
16
  toggleKey: 'F2',
package/src/index.d.ts CHANGED
@@ -424,6 +424,7 @@ export class AntAdapter {
424
424
  constructor(core: BeeLadybugCore, options?: AntAdapterOptions);
425
425
  attach(): this;
426
426
  detach(): this;
427
+ setRoot(root: ParentNode | null): this;
427
428
  snapshot(): ReportSnapshot | null;
428
429
  }
429
430