bee-ladybug 0.5.3 → 0.5.4

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
@@ -199,6 +199,7 @@ css.attach();
199
199
  - overflow del viewport e margini negativi
200
200
  - testo trasparente, `opacity: 0`, o colore uguale allo sfondo
201
201
  - chi intercetta il click al centro, inclusi `::before` / `::after` fissi su `html` / `body`
202
+ - un antenato che crea uno stacking context (`transform`, `opacity` sotto 1, `filter`, oppure `position` non static con `z-index`). I figli si raggruppano sulla causa: `div.hero creates stacking context (transform) - z-index of 3 children is trapped`. `will-change: transform` o `opacity` è una riga grigia, con la nota sullo `z-index` sulla stessa riga
202
203
  - `<img>` con rapporto del file diverso di oltre il 5% dal riquadro disegnato (`getBoundingClientRect`), solo se `object-fit` calcolato è `fill` (il default)
203
204
 
204
205
  `S` con la hunt accesa percorre la pagina. L'overflow resta solo sull'elemento che lo causa: un antenato si salta se un discendente sfora della stessa quantità (±2px), oppure se la larghezza del box ci sta nel viewport e a uscire è un figlio. Righe uguali (stesso selettore e stesso problema) diventano una sola, con il conteggio (`article overflows viewport ×4`). L'ordine è: causa di overflow, click bloccato, testo invisibile, immagine distorta, `!important`. Un click sulla riga disegna il contorno rosso. `P` ferma il pannello sull'elemento corrente. **Issues** raggruppa per causa: `body::after blocks clicks on 10 elements` è una riga rossa, e il click la apre sull'elenco degli elementi. In cima alla sezione, la legenda dice rosso = bug e grigio = info. Un foglio illeggibile è una riga grigia, una per foglio, e non entra tra i bug. L'overlay resta alto al massimo metà schermo, scorre al suo interno, e le sezioni Status, Issues, Inspect ed Events si chiudono. In console un elemento pulito non viene scritto, e con la hunt accesa restano fuori anche `hover` e `mutate attr` del DOM, a meno che `F3` SLOW sia acceso. Ogni problema CSS viene scritto una sola volta per elemento ogni 5 secondi. I fogli di stile restano in cache finché un `MutationObserver` non vede un cambio a `<style>` o `<link>`. Hover è limitato a un frame.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bee-ladybug",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
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",
@@ -143,6 +143,7 @@ export class CSSAdapter {
143
143
  pushInvisible(el, findings);
144
144
  pushBlockers(el, findings);
145
145
  pushImage(el, findings);
146
+ pushStacking(el, findings);
146
147
  pushImportant(el, this.#rules, findings);
147
148
  for (let j = 0; j < findings.length; j++) {
148
149
  rows.push({ el, selector: cssPath(el), ...findings[j] });
@@ -293,6 +294,7 @@ export class CSSAdapter {
293
294
  pushInvisible(el, findings);
294
295
  pushBlockers(el, findings);
295
296
  pushImage(el, findings);
297
+ pushStacking(el, findings);
296
298
  const path = cssPath(el);
297
299
  this.core?.sendData('state', { source: this.source, key: 'css.target', value: path });
298
300
  this.core?.sendData('state', {
@@ -340,7 +342,7 @@ export class CSSAdapter {
340
342
  for (let i = 0; i < findings.length; i++) {
341
343
  const hit = findings[i];
342
344
  const text = hit.text || hit.kind || '';
343
- if (!text || text.startsWith('unreadable sheet:')) continue;
345
+ if (!text || text.startsWith('unreadable sheet:') || hit.level === 'info') continue;
344
346
  if (!rememberIssue(this.#issueLog, `${path}\0${text}`, now, this.issueWindow)) continue;
345
347
  const level = hit.level === 'error' ? 'error' : 'warn';
346
348
  this.core?.sendData(level, {
@@ -380,20 +382,25 @@ export class CSSAdapter {
380
382
  }
381
383
 
382
384
  #publishIssues() {
383
- const bugs = this.#issues.map((entry) => ({
384
- level: 'error',
385
- key: entry.key,
386
- text: formatCause(entry),
387
- targets: entry.targets.map((target) => ({
388
- text: target.text,
389
- onSelect: () => this.#pin(target.el)
390
- }))
391
- }));
385
+ const bugs = [];
392
386
  const notes = this.#notes.map((note) => ({
393
387
  level: 'info',
394
388
  key: note.text,
395
389
  text: note.text
396
390
  }));
391
+ for (let i = 0; i < this.#issues.length; i++) {
392
+ const entry = this.#issues[i];
393
+ const item = {
394
+ key: entry.key,
395
+ text: formatCause(entry),
396
+ targets: entry.targets.map((target) => ({
397
+ text: target.text,
398
+ onSelect: () => this.#pin(target.el)
399
+ }))
400
+ };
401
+ if (entry.level === 'info') notes.push({ ...item, level: 'info' });
402
+ else bugs.push({ ...item, level: 'error' });
403
+ }
397
404
  this.core?.publishIssues(bugs.concat(notes));
398
405
  }
399
406
 
@@ -598,9 +605,11 @@ function pushNegativeMargins(el, findings) {
598
605
  const ISSUE_RANK = Object.freeze({
599
606
  overflow: 0,
600
607
  blocker: 1,
608
+ stack: 1,
601
609
  invisible: 2,
602
610
  image: 3,
603
- important: 4
611
+ important: 4,
612
+ hint: 6
604
613
  });
605
614
 
606
615
  /**
@@ -729,12 +738,32 @@ export function pushStickyIssue(list, entry, max = 10) {
729
738
 
730
739
  export function describeCause(kind, path) {
731
740
  if (kind === 'overflows viewport') {
732
- return { key: `overflow\0${path}`, type: 'overflow', cause: path };
741
+ return { key: `overflow\0${path}`, type: 'overflow', cause: path, reason: '', level: 'error' };
733
742
  }
734
743
  const prefix = 'click blocked by ';
735
744
  if (typeof kind === 'string' && kind.startsWith(prefix)) {
736
745
  const cause = kind.slice(prefix.length);
737
- return { key: `block\0${cause}`, type: 'block', cause };
746
+ return { key: `block\0${cause}`, type: 'block', cause, reason: '', level: 'error' };
747
+ }
748
+ const stack = typeof kind === 'string' ? kind.match(/^stacking context \(([^)]+)\) by (.+)$/) : null;
749
+ if (stack) {
750
+ return {
751
+ key: `stack\0${stack[2]}\0${stack[1]}`,
752
+ type: 'stack',
753
+ cause: stack[2],
754
+ reason: stack[1],
755
+ level: 'error'
756
+ };
757
+ }
758
+ const hint = typeof kind === 'string' ? kind.match(/^will-change (.+?) by (.+)$/) : null;
759
+ if (hint) {
760
+ return {
761
+ key: `will\0${hint[2]}\0${hint[1]}`,
762
+ type: 'stack-hint',
763
+ cause: hint[2],
764
+ reason: hint[1],
765
+ level: 'info'
766
+ };
738
767
  }
739
768
  return null;
740
769
  }
@@ -760,6 +789,8 @@ export function addCause(list, entry, max = 10) {
760
789
  key: described.key,
761
790
  type: described.type,
762
791
  cause: described.cause,
792
+ reason: described.reason || '',
793
+ level: described.level || 'error',
763
794
  targets
764
795
  });
765
796
  return rest.slice(0, max);
@@ -771,10 +802,96 @@ export function formatCause(entry) {
771
802
  const noun = count === 1 ? 'element' : 'elements';
772
803
  return `${entry.cause} blocks clicks on ${count} ${noun}`;
773
804
  }
805
+ if (entry.type === 'stack') {
806
+ const noun = count === 1 ? 'child' : 'children';
807
+ return `${entry.cause} creates stacking context (${entry.reason}) - z-index of ${count} ${noun} is trapped`;
808
+ }
809
+ if (entry.type === 'stack-hint') {
810
+ const noun = count === 1 ? 'child' : 'children';
811
+ return `${entry.cause} will-change: ${entry.reason} — z-index of ${count} ${noun} stays in this layer`;
812
+ }
774
813
  if (count <= 1) return `${entry.cause} overflows viewport`;
775
814
  return `${entry.cause} overflows viewport · ${count} elements`;
776
815
  }
777
816
 
817
+ /**
818
+ * Properties that create a stacking context. `will-change` for transform or
819
+ * opacity is only a hint: it may promote a layer, it is not reported as a bug.
820
+ * @param {{ transform?: string, opacity?: string, filter?: string, position?: string, zIndex?: string, willChange?: string, getPropertyValue?: (name: string) => string }} style
821
+ */
822
+ export function stackingReasons(style) {
823
+ const read = (prop, camel) => {
824
+ if (style && typeof style.getPropertyValue === 'function') {
825
+ const raw = style.getPropertyValue(prop);
826
+ if (raw) return String(raw).trim();
827
+ }
828
+ if (style && style[camel] != null && style[camel] !== '') return String(style[camel]).trim();
829
+ return '';
830
+ };
831
+ const bugs = [];
832
+ const transform = read('transform', 'transform');
833
+ if (transform && transform !== 'none') bugs.push('transform');
834
+ const opacity = parseFloat(read('opacity', 'opacity'));
835
+ if (Number.isFinite(opacity) && opacity < 1) bugs.push('opacity');
836
+ const filter = read('filter', 'filter');
837
+ if (filter && filter !== 'none') bugs.push('filter');
838
+ const position = read('position', 'position') || 'static';
839
+ const zIndex = read('z-index', 'zIndex') || 'auto';
840
+ if (position !== 'static' && zIndex !== 'auto') bugs.push('z-index');
841
+ const hint = willChangeHint(read('will-change', 'willChange'));
842
+ return { bugs, hint };
843
+ }
844
+
845
+ function willChangeHint(value) {
846
+ if (!value || value === 'auto') return '';
847
+ const parts = value.split(',').map((part) => part.trim().toLowerCase());
848
+ const hit = [];
849
+ for (let i = 0; i < parts.length; i++) {
850
+ if ((parts[i] === 'transform' || parts[i] === 'opacity') && !hit.includes(parts[i])) hit.push(parts[i]);
851
+ }
852
+ return hit.join(', ');
853
+ }
854
+
855
+ function pushStacking(el, findings) {
856
+ if (!el || typeof getComputedStyle !== 'function' || !el.parentElement) return;
857
+ const own = getComputedStyle(el);
858
+ const ownZ = own.zIndex || own.getPropertyValue?.('z-index') || 'auto';
859
+ if (!ownZ || ownZ === 'auto') return;
860
+ let node = el.parentElement;
861
+ while (node && node.nodeType === 1) {
862
+ if (isBeeChrome(node)) return;
863
+ const found = stackingReasons(getComputedStyle(node));
864
+ const ancestor = cssPath(node);
865
+ if (found.bugs.length) {
866
+ const reason = found.bugs[0];
867
+ findings.push({
868
+ level: 'error',
869
+ rank: ISSUE_RANK.stack,
870
+ score: 35,
871
+ kind: `stacking context (${reason}) by ${ancestor}`,
872
+ text: `z-index trapped by ${ancestor} (${reason})`
873
+ });
874
+ if (found.hint) findings.push(stackHint(ancestor, found.hint));
875
+ return;
876
+ }
877
+ if (found.hint) {
878
+ findings.push(stackHint(ancestor, found.hint));
879
+ return;
880
+ }
881
+ node = node.parentElement;
882
+ }
883
+ }
884
+
885
+ function stackHint(ancestor, hint) {
886
+ return {
887
+ level: 'info',
888
+ rank: ISSUE_RANK.hint,
889
+ score: 1,
890
+ kind: `will-change ${hint} by ${ancestor}`,
891
+ text: `will-change: ${hint} on ${ancestor}`
892
+ };
893
+ }
894
+
778
895
  function sameTarget(a, b) {
779
896
  if (a.el && b.el) return a.el === b.el;
780
897
  return a.text === b.text;
@@ -9,7 +9,7 @@
9
9
  import { UIOverlay } from './UIOverlay.js';
10
10
  import { buildAssistantSnapshot, normalizeAssistant } from './AiAssistant.js';
11
11
 
12
- export const BEE_LADYBUG_VERSION = '0.5.3';
12
+ export const BEE_LADYBUG_VERSION = '0.5.4';
13
13
 
14
14
  export const CORE_DEFAULTS = Object.freeze({
15
15
  toggleKey: 'F2',
@@ -358,6 +358,7 @@ const STYLES = `
358
358
  }
359
359
  .bl-detail button[data-level="warn"] { color: #ffd36a; }
360
360
  .bl-detail button[data-level="error"] { color: #ff6b7d; }
361
+ .bl-detail button[data-level="info"] { color: #8b91a3; }
361
362
  .bl-detail button:hover { background: rgba(255, 255, 255, 0.04); }
362
363
  .bl-adapter {
363
364
  appearance: none;