@qontinui/ui-bridge-native 0.7.0 → 0.8.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"useRouteAwareness.d.ts","sourceRoot":"","sources":["../../src/react/useRouteAwareness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0FG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE/C,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAMD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,GAAE,wBAA6B,GAAG,IAAI,CAwB/F"}
1
+ {"version":3,"file":"useRouteAwareness.d.ts","sourceRoot":"","sources":["../../src/react/useRouteAwareness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0FG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE/C,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAWD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,GAAE,wBAA6B,GAAG,IAAI,CAwB/F"}
@@ -1 +1 @@
1
- {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/server/handlers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAaH,OAAO,EACL,KAAK,sBAAsB,EAK5B,MAAM,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EAAwB,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACnF,OAAO,KAAK,EAQV,oBAAoB,EAKrB,MAAM,SAAS,CAAC;AAEjB,OAAO,KAAK,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AA4VhF;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,sBAAsB,EAChC,QAAQ,EAAE,oBAAoB,EAC9B,MAAM,CAAC,EAAE;IACP,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAClF,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,iFAAiF;IACjF,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C;;;;;;;;;OASG;IACH,gBAAgB,CAAC,EAAE,MAAM;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5D,GACA,oBAAoB,CAmhCtB"}
1
+ {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/server/handlers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAaH,OAAO,EACL,KAAK,sBAAsB,EAM5B,MAAM,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EAAwB,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACnF,OAAO,KAAK,EAQV,oBAAoB,EAKrB,MAAM,SAAS,CAAC;AAEjB,OAAO,KAAK,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AA4VhF;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,sBAAsB,EAChC,QAAQ,EAAE,oBAAoB,EAC9B,MAAM,CAAC,EAAE;IACP,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAClF,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,iFAAiF;IACjF,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C;;;;;;;;;OASG;IACH,gBAAgB,CAAC,EAAE,MAAM;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5D,GACA,oBAAoB,CAqhCtB"}
@@ -337,6 +337,164 @@ var UI_BRIDGE_NATIVE_ROUTES = {
337
337
  }
338
338
  };
339
339
 
340
+ // src/core/sha256.ts
341
+ var K = new Uint32Array([
342
+ 1116352408,
343
+ 1899447441,
344
+ 3049323471,
345
+ 3921009573,
346
+ 961987163,
347
+ 1508970993,
348
+ 2453635748,
349
+ 2870763221,
350
+ 3624381080,
351
+ 310598401,
352
+ 607225278,
353
+ 1426881987,
354
+ 1925078388,
355
+ 2162078206,
356
+ 2614888103,
357
+ 3248222580,
358
+ 3835390401,
359
+ 4022224774,
360
+ 264347078,
361
+ 604807628,
362
+ 770255983,
363
+ 1249150122,
364
+ 1555081692,
365
+ 1996064986,
366
+ 2554220882,
367
+ 2821834349,
368
+ 2952996808,
369
+ 3210313671,
370
+ 3336571891,
371
+ 3584528711,
372
+ 113926993,
373
+ 338241895,
374
+ 666307205,
375
+ 773529912,
376
+ 1294757372,
377
+ 1396182291,
378
+ 1695183700,
379
+ 1986661051,
380
+ 2177026350,
381
+ 2456956037,
382
+ 2730485921,
383
+ 2820302411,
384
+ 3259730800,
385
+ 3345764771,
386
+ 3516065817,
387
+ 3600352804,
388
+ 4094571909,
389
+ 275423344,
390
+ 430227734,
391
+ 506948616,
392
+ 659060556,
393
+ 883997877,
394
+ 958139571,
395
+ 1322822218,
396
+ 1537002063,
397
+ 1747873779,
398
+ 1955562222,
399
+ 2024104815,
400
+ 2227730452,
401
+ 2361852424,
402
+ 2428436474,
403
+ 2756734187,
404
+ 3204031479,
405
+ 3329325298
406
+ ]);
407
+ var H0 = [
408
+ 1779033703,
409
+ 3144134277,
410
+ 1013904242,
411
+ 2773480762,
412
+ 1359893119,
413
+ 2600822924,
414
+ 528734635,
415
+ 1541459225
416
+ ];
417
+ function utf8Bytes(input) {
418
+ const out = [];
419
+ for (let i = 0; i < input.length; i++) {
420
+ let cp = input.charCodeAt(i);
421
+ if (cp >= 55296 && cp <= 56319) {
422
+ const next = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
423
+ if (next >= 56320 && next <= 57343) {
424
+ cp = 65536 + (cp - 55296 << 10) + (next - 56320);
425
+ i++;
426
+ } else {
427
+ cp = 65533;
428
+ }
429
+ } else if (cp >= 56320 && cp <= 57343) {
430
+ cp = 65533;
431
+ }
432
+ if (cp < 128) {
433
+ out.push(cp);
434
+ } else if (cp < 2048) {
435
+ out.push(192 | cp >> 6, 128 | cp & 63);
436
+ } else if (cp < 65536) {
437
+ out.push(224 | cp >> 12, 128 | cp >> 6 & 63, 128 | cp & 63);
438
+ } else {
439
+ out.push(
440
+ 240 | cp >> 18,
441
+ 128 | cp >> 12 & 63,
442
+ 128 | cp >> 6 & 63,
443
+ 128 | cp & 63
444
+ );
445
+ }
446
+ }
447
+ return Uint8Array.from(out);
448
+ }
449
+ var rotr = (x, n) => x >>> n | x << 32 - n;
450
+ function sha256Hex(input) {
451
+ const bytes = utf8Bytes(input);
452
+ const blockBytes = Math.ceil((bytes.length + 9) / 64) * 64;
453
+ const buffer = new Uint8Array(blockBytes);
454
+ buffer.set(bytes);
455
+ buffer[bytes.length] = 128;
456
+ const view = new DataView(buffer.buffer);
457
+ const bitLength = bytes.length * 8;
458
+ view.setUint32(blockBytes - 8, Math.floor(bitLength / 4294967296));
459
+ view.setUint32(blockBytes - 4, bitLength >>> 0);
460
+ const h = H0.slice();
461
+ const w = new Uint32Array(64);
462
+ for (let block = 0; block < blockBytes; block += 64) {
463
+ for (let t = 0; t < 16; t++) w[t] = view.getUint32(block + t * 4);
464
+ for (let t = 16; t < 64; t++) {
465
+ const s0 = rotr(w[t - 15], 7) ^ rotr(w[t - 15], 18) ^ w[t - 15] >>> 3;
466
+ const s1 = rotr(w[t - 2], 17) ^ rotr(w[t - 2], 19) ^ w[t - 2] >>> 10;
467
+ w[t] = w[t - 16] + s0 + w[t - 7] + s1;
468
+ }
469
+ let [a, b, c, d, e, f, g, hh] = h;
470
+ for (let t = 0; t < 64; t++) {
471
+ const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
472
+ const ch = e & f ^ ~e & g;
473
+ const t1 = hh + S1 + ch + K[t] + w[t] | 0;
474
+ const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
475
+ const maj = a & b ^ a & c ^ b & c;
476
+ const t2 = S0 + maj | 0;
477
+ hh = g;
478
+ g = f;
479
+ f = e;
480
+ e = d + t1 | 0;
481
+ d = c;
482
+ c = b;
483
+ b = a;
484
+ a = t1 + t2 | 0;
485
+ }
486
+ h[0] = h[0] + a | 0;
487
+ h[1] = h[1] + b | 0;
488
+ h[2] = h[2] + c | 0;
489
+ h[3] = h[3] + d | 0;
490
+ h[4] = h[4] + e | 0;
491
+ h[5] = h[5] + f | 0;
492
+ h[6] = h[6] + g | 0;
493
+ h[7] = h[7] + hh | 0;
494
+ }
495
+ return h.map((word) => (word >>> 0).toString(16).padStart(8, "0")).join("");
496
+ }
497
+
340
498
  // src/core/vision-fields.ts
341
499
  var INTERACTIVE_HANDLER_NAMES = /* @__PURE__ */ new Set([
342
500
  "onPress",
@@ -420,13 +578,18 @@ var NAMED_COLORS = {
420
578
  gray: { r: 128, g: 128, b: 128 },
421
579
  grey: { r: 128, g: 128, b: 128 },
422
580
  silver: { r: 192, g: 192, b: 192 },
423
- transparent: { r: 0, g: 0, b: 0 },
424
581
  orange: { r: 255, g: 165, b: 0 },
425
582
  yellow: { r: 255, g: 255, b: 0 },
426
583
  purple: { r: 128, g: 0, b: 128 },
427
584
  navy: { r: 0, g: 0, b: 128 }
428
585
  };
429
586
  var clamp255 = (n) => Math.max(0, Math.min(255, Math.round(n)));
587
+ function parseAlpha(p) {
588
+ const pct = p.endsWith("%");
589
+ const n = parseFloat(pct ? p.slice(0, -1) : p);
590
+ if (!Number.isFinite(n)) return void 0;
591
+ return Math.min(1, pct ? n / 100 : n);
592
+ }
430
593
  function parseColorToRgb(input) {
431
594
  if (typeof input !== "string") return void 0;
432
595
  const raw = input.trim().toLowerCase();
@@ -437,6 +600,7 @@ function parseColorToRgb(input) {
437
600
  if (raw.startsWith("#")) {
438
601
  const hex = raw.slice(1);
439
602
  if (hex.length === 3 || hex.length === 4) {
603
+ if (hex.length === 4 && hex[3] !== "f") return void 0;
440
604
  const r = parseInt(hex[0] + hex[0], 16);
441
605
  const g = parseInt(hex[1] + hex[1], 16);
442
606
  const b = parseInt(hex[2] + hex[2], 16);
@@ -444,6 +608,7 @@ function parseColorToRgb(input) {
444
608
  return void 0;
445
609
  }
446
610
  if (hex.length === 6 || hex.length === 8) {
611
+ if (hex.length === 8 && hex.slice(6, 8) !== "ff") return void 0;
447
612
  const r = parseInt(hex.slice(0, 2), 16);
448
613
  const g = parseInt(hex.slice(2, 4), 16);
449
614
  const b = parseInt(hex.slice(4, 6), 16);
@@ -455,7 +620,11 @@ function parseColorToRgb(input) {
455
620
  const rgbMatch = raw.match(/^rgba?\(([^)]+)\)$/);
456
621
  if (rgbMatch) {
457
622
  const parts = rgbMatch[1].split(",").map((p) => p.trim());
458
- if (parts.length < 3) return void 0;
623
+ if (parts.length < 3 || parts.length > 4) return void 0;
624
+ if (parts.length === 4) {
625
+ const alpha = parseAlpha(parts[3]);
626
+ if (alpha === void 0 || alpha < 1) return void 0;
627
+ }
459
628
  const channel = (p) => {
460
629
  if (p.endsWith("%")) {
461
630
  const pct = parseFloat(p.slice(0, -1));
@@ -487,13 +656,87 @@ function parseFontSizePx(input) {
487
656
  }
488
657
  return void 0;
489
658
  }
659
+ var ASCII_WHITESPACE_EDGES = /^[\t\n\f\r ]+|[\t\n\f\r ]+$/g;
660
+ function nonEmptyString(v) {
661
+ return typeof v === "string" && v.length > 0 ? v : void 0;
662
+ }
663
+ var OBJECT_FIT_TO_RESIZE_MODE = /* @__PURE__ */ new Map([
664
+ ["contain", "contain"],
665
+ ["cover", "cover"],
666
+ ["fill", "stretch"],
667
+ ["scale-down", "contain"],
668
+ ["none", "none"]
669
+ ]);
670
+ var RESIZE_MODE_TO_OBJECT_FIT = /* @__PURE__ */ new Map([
671
+ ["cover", "cover"],
672
+ ["contain", "contain"],
673
+ ["stretch", "fill"],
674
+ ["center", "scale-down"],
675
+ ["none", "none"]
676
+ ]);
677
+ function resolveObjectFit(props, flatStyle) {
678
+ const styleFit = nonEmptyString(flatStyle?.objectFit);
679
+ const mode = (styleFit !== void 0 ? OBJECT_FIT_TO_RESIZE_MODE.get(styleFit) : void 0) ?? nonEmptyString(props?.resizeMode) ?? nonEmptyString(flatStyle?.resizeMode);
680
+ return mode !== void 0 ? RESIZE_MODE_TO_OBJECT_FIT.get(mode) : void 0;
681
+ }
682
+ function resolveImageSource(props) {
683
+ if (!props) return void 0;
684
+ if (props.srcSet !== void 0 && props.srcSet !== null) return void 0;
685
+ const src = nonEmptyString(props.src);
686
+ if (src !== void 0) return src;
687
+ const source = props.source;
688
+ const one = Array.isArray(source) ? source.length === 1 ? source[0] : void 0 : source;
689
+ if (one && typeof one === "object") {
690
+ return nonEmptyString(one.uri);
691
+ }
692
+ return void 0;
693
+ }
694
+ function isHiddenFromAccessibility(props) {
695
+ if (!props) return false;
696
+ return props.accessible === false && props.alt === void 0 || props.importantForAccessibility === "no" || props.importantForAccessibility === "no-hide-descendants" || props.accessibilityElementsHidden === true;
697
+ }
698
+ function resolveImageLabel(props, registryLabel) {
699
+ if (props === void 0) return registryLabel;
700
+ const own = props["aria-label"] ?? props.accessibilityLabel ?? props.alt;
701
+ return typeof own === "string" ? own : void 0;
702
+ }
703
+ function projectMedia(args) {
704
+ const { layout, props, flatStyle, accessibilityLabel, inViewport } = args;
705
+ const media = { kind: "img" };
706
+ if (layout && Number.isFinite(layout.width) && Number.isFinite(layout.height) && layout.width >= 0 && layout.height >= 0) {
707
+ media.rendered_px = { w: layout.width, h: layout.height };
708
+ }
709
+ const objectFit = resolveObjectFit(props, flatStyle);
710
+ if (objectFit !== void 0) media.object_fit = objectFit;
711
+ if (isHiddenFromAccessibility(props)) {
712
+ media.alt = { decorative: true };
713
+ } else {
714
+ const label = resolveImageLabel(props, accessibilityLabel);
715
+ const text = label?.replace(ASCII_WHITESPACE_EDGES, "");
716
+ if (text) media.alt = { decorative: false, text };
717
+ }
718
+ const src = resolveImageSource(props);
719
+ if (src !== void 0) media.src_digest = sha256Hex(src);
720
+ if (inViewport !== void 0) media.in_viewport = inViewport;
721
+ return media;
722
+ }
490
723
  function projectVisionFields(args) {
491
724
  const { type, label, handlerNames, state, flatStyle } = args;
492
725
  const fields = {
493
726
  interactable: deriveInteractable(type, handlerNames)
494
727
  };
495
- const text = deriveText(state, label);
496
- if (text !== void 0) fields.text = text;
728
+ if (type === "image") {
729
+ fields.media = projectMedia({
730
+ layout: state.layout,
731
+ props: args.props,
732
+ flatStyle,
733
+ accessibilityLabel: args.accessibilityLabel,
734
+ inViewport: args.inViewport
735
+ });
736
+ } else {
737
+ const text = deriveText(state, label);
738
+ if (text !== void 0) fields.text = text;
739
+ }
497
740
  const role = roleForType(type);
498
741
  if (role !== void 0) fields.role = role;
499
742
  if (flatStyle) {
@@ -587,11 +830,17 @@ function computeVisibility(state, clip) {
587
830
  if (!rect) {
588
831
  return { visibility: "visible" };
589
832
  }
590
- if (isEmptyRect(clip)) {
591
- return { visibility: "hidden", visibilityReason: "off-screen" };
592
- }
593
- const onScreen = spansOverlap(rect.left, rect.right, clip.left, clip.right) && spansOverlap(rect.top, rect.bottom, clip.top, clip.bottom);
594
- return onScreen ? { visibility: "visible" } : { visibility: "hidden", visibilityReason: "off-screen" };
833
+ return rectReachesClip(rect, clip) ? { visibility: "visible" } : { visibility: "hidden", visibilityReason: "off-screen" };
834
+ }
835
+ function rectReachesClip(rect, clip) {
836
+ if (isEmptyRect(clip)) return false;
837
+ return spansOverlap(rect.left, rect.right, clip.left, clip.right) && spansOverlap(rect.top, rect.bottom, clip.top, clip.bottom);
838
+ }
839
+ function computeInViewport(state, clip) {
840
+ if (!state.visible || !clip) return void 0;
841
+ const rect = pageRectOf(state);
842
+ if (!rect) return void 0;
843
+ return rectReachesClip(rect, clip);
595
844
  }
596
845
 
597
846
  // src/design/design-inspector-native.ts
@@ -1266,7 +1515,7 @@ var NATIVE_PAGE_HEALTH_PRODUCER_ID = "sdk-native/page-health";
1266
1515
  function defaultProducer() {
1267
1516
  return {
1268
1517
  id: NATIVE_PAGE_HEALTH_PRODUCER_ID,
1269
- version: "0.7.0"
1518
+ version: "0.8.0"
1270
1519
  };
1271
1520
  }
1272
1521
  function pageHealthUnknown(code, detail, options = {}) {
@@ -1308,14 +1557,7 @@ var LOADING_PHRASES = [
1308
1557
  "initializing",
1309
1558
  "fetching"
1310
1559
  ];
1311
- var EMPTY_PHRASES = [
1312
- "no data",
1313
- "no results",
1314
- "nothing here",
1315
- "empty",
1316
- "no items",
1317
- "get started"
1318
- ];
1560
+ var EMPTY_PHRASES = ["no data", "no results", "nothing here", "empty", "no items", "get started"];
1319
1561
  function severityRank(s) {
1320
1562
  if (s === "CRITICAL") return 3;
1321
1563
  if (s === "WARNING") return 2;
@@ -1390,20 +1632,44 @@ function observe(input, producer, evaluatedAt) {
1390
1632
  }
1391
1633
  return analyze(elements, viewport, base);
1392
1634
  }
1635
+ function nothingOnScreenDetail(elements, visibleCount, visibilityUnstated) {
1636
+ if (visibilityUnstated === 0 && visibleCount > 0) {
1637
+ return `all ${visibleCount} visible element(s) have no measured layout yet, so spatial coverage, layout regions and visual anomalies cannot be measured`;
1638
+ }
1639
+ if (visibilityUnstated > 0) {
1640
+ const geometry = visibleCount > 0 ? ` and none of the ${visibleCount} visible one(s) has a measured layout` : "";
1641
+ return `${visibilityUnstated} of ${elements.length} element(s) carry no boolean \`state.visible\`${geometry}, so no element can be placed on screen; spatial coverage, layout regions and visual anomalies cannot be measured`;
1642
+ }
1643
+ const unmounted = elements.filter((el) => el.state?.mounted === false).length;
1644
+ return `all ${elements.length} registered element(s) report visible: false (unmounted=${unmounted}, off-screen route or otherwise=${elements.length - unmounted}), so whatever is on screen is not a registered element and there is nothing on screen to measure; this says nothing about whether the screen is blank`;
1645
+ }
1393
1646
  function analyze(elements, viewport, base) {
1394
1647
  const findings = [];
1395
- const considered = elements.filter((el) => el.state.visible);
1396
- const visible = considered.flatMap((el) => {
1648
+ const onScreen = elements.filter((el) => el.state?.visible === true);
1649
+ const visibilityUnstated = elements.filter(
1650
+ (el) => typeof el.state?.visible !== "boolean"
1651
+ ).length;
1652
+ const visible = onScreen.flatMap((el) => {
1397
1653
  const rect = normalizeRect(el.state, viewport);
1398
1654
  return rect ? [{ el, rect }] : [];
1399
1655
  });
1400
- const withoutGeometry = considered.length - visible.length;
1401
- const unmeasured = withoutGeometry > 0 ? [{ dimension: "geometry", count: withoutGeometry, code: "input_missing" }] : [];
1402
- const coverage = { considered: considered.length, measured: visible.length, unmeasured };
1403
- if (considered.length > 0 && visible.length === 0) {
1656
+ const withoutGeometry = onScreen.length - visible.length;
1657
+ const unmeasured = [];
1658
+ if (withoutGeometry > 0) {
1659
+ unmeasured.push({ dimension: "geometry", count: withoutGeometry, code: "input_missing" });
1660
+ }
1661
+ if (visibilityUnstated > 0) {
1662
+ unmeasured.push({ dimension: "visibility", count: visibilityUnstated, code: "input_missing" });
1663
+ }
1664
+ const coverage = {
1665
+ considered: onScreen.length + visibilityUnstated,
1666
+ measured: visible.length,
1667
+ unmeasured
1668
+ };
1669
+ if (elements.length > 0 && visible.length === 0) {
1404
1670
  return Observation.unknown(
1405
1671
  "input_missing",
1406
- `all ${considered.length} visible element(s) have no measured layout yet, so spatial coverage, layout regions and visual anomalies cannot be measured`,
1672
+ nothingOnScreenDetail(elements, onScreen.length, visibilityUnstated),
1407
1673
  Observation.provenance({ ...base, coverage })
1408
1674
  );
1409
1675
  }
@@ -1493,7 +1759,7 @@ function analyze(elements, viewport, base) {
1493
1759
  for (const el of elements) {
1494
1760
  const elType = el.type ?? "";
1495
1761
  if (SKIP_TEXT_TYPES.has(elType)) continue;
1496
- const text = (el.state.textContent ?? "").toString();
1762
+ const text = (el.state?.textContent ?? "").toString();
1497
1763
  if (!text) continue;
1498
1764
  const textLower = text.toLowerCase();
1499
1765
  for (const p of ERROR_PHRASES) {
@@ -1534,7 +1800,7 @@ function analyze(elements, viewport, base) {
1534
1800
  for (const el of elements) {
1535
1801
  if (!INTERACTIVE_TYPES2.has(el.type)) continue;
1536
1802
  interactiveTotal++;
1537
- if (el.state.enabled === false) interactiveDisabled++;
1803
+ if (el.state?.enabled === false) interactiveDisabled++;
1538
1804
  }
1539
1805
  const interactiveSeverity = interactiveTotal > 0 && interactiveDisabled / interactiveTotal > 0.5 ? "WARNING" : "OK";
1540
1806
  findings.push({
@@ -1558,9 +1824,7 @@ function analyze(elements, viewport, base) {
1558
1824
  detail: `zero_size=${zeroSize}, outside_viewport=${outsideViewport} (horizontal-overflow only; below/above-fold scrollable content is not an anomaly)`,
1559
1825
  data: { zero_size: zeroSize, outside_viewport: outsideViewport }
1560
1826
  });
1561
- const heatmap = grid.map(
1562
- (row) => row.map((cell) => cell ? "#" : ".").join("")
1563
- );
1827
+ const heatmap = grid.map((row) => row.map((cell) => cell ? "#" : ".").join(""));
1564
1828
  return Observation.measured(
1565
1829
  {
1566
1830
  summary: worstSeverity(findings),
@@ -1820,10 +2084,9 @@ function createServerHandlers(registry, executor, config) {
1820
2084
  const elements = allElements.map((e) => {
1821
2085
  const handlers = extractHandlerNames(e.props);
1822
2086
  const state = e.getState();
1823
- const { visibility, visibilityReason } = computeVisibility(
1824
- state,
1825
- registry.getClipRectFor(e, viewport)
1826
- );
2087
+ const identifier = e.getIdentifier();
2088
+ const clip = registry.getClipRectFor(e, viewport);
2089
+ const { visibility, visibilityReason } = computeVisibility(state, clip);
1827
2090
  const bbox = projectBbox(state, visibility, registry.getPixelRatio());
1828
2091
  const vision = projectVisionFields({
1829
2092
  type: e.type,
@@ -1832,13 +2095,16 @@ function createServerHandlers(registry, executor, config) {
1832
2095
  // synthesize press/click onto every element. See projectVisionFields.
1833
2096
  handlerNames: handlers,
1834
2097
  state,
1835
- flatStyle: e.flatStyle
2098
+ flatStyle: e.flatStyle,
2099
+ props: e.props,
2100
+ accessibilityLabel: identifier.accessibilityLabel,
2101
+ inViewport: computeInViewport(state, clip)
1836
2102
  });
1837
2103
  return {
1838
2104
  id: e.id,
1839
2105
  type: e.type,
1840
2106
  label: e.label,
1841
- identifier: e.getIdentifier(),
2107
+ identifier,
1842
2108
  state,
1843
2109
  actions: e.actions,
1844
2110
  customActions: serializeElementCustomActions(e.customActions),
@@ -2546,7 +2812,7 @@ function createServerHandlers(registry, executor, config) {
2546
2812
  };
2547
2813
  if (config?.appInfo) {
2548
2814
  response.uiBridge = {
2549
- version: "0.7.0",
2815
+ version: "0.8.0",
2550
2816
  ...config.appInfo,
2551
2817
  capabilities: ["elements", "components", "actions", "design"],
2552
2818
  elementCount: stats.elements,