cbrowser 19.2.2 → 19.3.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.
Files changed (95) hide show
  1. package/dist/analysis/accessibility-empathy.d.ts +48 -1
  2. package/dist/analysis/accessibility-empathy.d.ts.map +1 -1
  3. package/dist/analysis/accessibility-empathy.js +766 -92
  4. package/dist/analysis/accessibility-empathy.js.map +1 -1
  5. package/dist/analysis/agent-ready-audit.d.ts +28 -0
  6. package/dist/analysis/agent-ready-audit.d.ts.map +1 -1
  7. package/dist/analysis/agent-ready-audit.js +218 -21
  8. package/dist/analysis/agent-ready-audit.js.map +1 -1
  9. package/dist/analysis/bug-hunter.d.ts +45 -0
  10. package/dist/analysis/bug-hunter.d.ts.map +1 -1
  11. package/dist/analysis/bug-hunter.js +211 -64
  12. package/dist/analysis/bug-hunter.js.map +1 -1
  13. package/dist/analysis/competitive-benchmark.d.ts.map +1 -1
  14. package/dist/analysis/competitive-benchmark.js +4 -2
  15. package/dist/analysis/competitive-benchmark.js.map +1 -1
  16. package/dist/browser/session-manager.d.ts.map +1 -1
  17. package/dist/browser/session-manager.js +4 -2
  18. package/dist/browser/session-manager.js.map +1 -1
  19. package/dist/browser.d.ts +11 -2
  20. package/dist/browser.d.ts.map +1 -1
  21. package/dist/browser.js +64 -15
  22. package/dist/browser.js.map +1 -1
  23. package/dist/cli.js +2 -1
  24. package/dist/cli.js.map +1 -1
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +1 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/mcp-server.d.ts.map +1 -1
  30. package/dist/mcp-server.js +19 -19
  31. package/dist/mcp-server.js.map +1 -1
  32. package/dist/mcp-tools/base/artifact-tools.d.ts.map +1 -1
  33. package/dist/mcp-tools/base/artifact-tools.js +29 -2
  34. package/dist/mcp-tools/base/artifact-tools.js.map +1 -1
  35. package/dist/mcp-tools/base/audit-tools.d.ts.map +1 -1
  36. package/dist/mcp-tools/base/audit-tools.js +114 -58
  37. package/dist/mcp-tools/base/audit-tools.js.map +1 -1
  38. package/dist/mcp-tools/base/browser-state-tools.d.ts.map +1 -1
  39. package/dist/mcp-tools/base/browser-state-tools.js +3 -2
  40. package/dist/mcp-tools/base/browser-state-tools.js.map +1 -1
  41. package/dist/mcp-tools/base/bug-analysis-tools.d.ts.map +1 -1
  42. package/dist/mcp-tools/base/bug-analysis-tools.js +4 -67
  43. package/dist/mcp-tools/base/bug-analysis-tools.js.map +1 -1
  44. package/dist/mcp-tools/base/index.js +2 -2
  45. package/dist/mcp-tools/base/index.js.map +1 -1
  46. package/dist/mcp-tools/base/interaction-tools.d.ts.map +1 -1
  47. package/dist/mcp-tools/base/interaction-tools.js +4 -0
  48. package/dist/mcp-tools/base/interaction-tools.js.map +1 -1
  49. package/dist/mcp-tools/base/visual-testing-tools.d.ts +2 -2
  50. package/dist/mcp-tools/base/visual-testing-tools.d.ts.map +1 -1
  51. package/dist/mcp-tools/base/visual-testing-tools.js +156 -38
  52. package/dist/mcp-tools/base/visual-testing-tools.js.map +1 -1
  53. package/dist/mcp-tools/security-layer.d.ts +33 -0
  54. package/dist/mcp-tools/security-layer.d.ts.map +1 -1
  55. package/dist/mcp-tools/security-layer.js +191 -1
  56. package/dist/mcp-tools/security-layer.js.map +1 -1
  57. package/dist/navigation-errors.d.ts +21 -0
  58. package/dist/navigation-errors.d.ts.map +1 -0
  59. package/dist/navigation-errors.js +119 -0
  60. package/dist/navigation-errors.js.map +1 -0
  61. package/dist/remediation/llms-txt.d.ts.map +1 -1
  62. package/dist/remediation/llms-txt.js +3 -1
  63. package/dist/remediation/llms-txt.js.map +1 -1
  64. package/dist/remediation/structured-data.d.ts.map +1 -1
  65. package/dist/remediation/structured-data.js +3 -1
  66. package/dist/remediation/structured-data.js.map +1 -1
  67. package/dist/security/script-gate.d.ts +7 -0
  68. package/dist/security/script-gate.d.ts.map +1 -1
  69. package/dist/security/script-gate.js +7 -0
  70. package/dist/security/script-gate.js.map +1 -1
  71. package/dist/testing/flaky-detection.js +1 -1
  72. package/dist/testing/flaky-detection.js.map +1 -1
  73. package/dist/testing/nl-test-suite.d.ts +12 -0
  74. package/dist/testing/nl-test-suite.d.ts.map +1 -1
  75. package/dist/testing/nl-test-suite.js +92 -14
  76. package/dist/testing/nl-test-suite.js.map +1 -1
  77. package/dist/testing/test-repair.js +3 -3
  78. package/dist/testing/test-repair.js.map +1 -1
  79. package/dist/types.d.ts +51 -0
  80. package/dist/types.d.ts.map +1 -1
  81. package/dist/types.js.map +1 -1
  82. package/dist/visual/attention-transport.d.ts +89 -11
  83. package/dist/visual/attention-transport.d.ts.map +1 -1
  84. package/dist/visual/attention-transport.js +148 -53
  85. package/dist/visual/attention-transport.js.map +1 -1
  86. package/dist/visual/cross-browser.d.ts.map +1 -1
  87. package/dist/visual/cross-browser.js +3 -1
  88. package/dist/visual/cross-browser.js.map +1 -1
  89. package/dist/visual/distance-metrics.js +6 -6
  90. package/dist/visual/distance-metrics.js.map +1 -1
  91. package/dist/visual/perceptual-transport.d.ts +14 -3
  92. package/dist/visual/perceptual-transport.d.ts.map +1 -1
  93. package/dist/visual/perceptual-transport.js +51 -9
  94. package/dist/visual/perceptual-transport.js.map +1 -1
  95. package/package.json +1 -1
@@ -14,8 +14,22 @@ import { z } from "zod";
14
14
  import { readFileSync, existsSync, statSync } from "node:fs";
15
15
  import { join, basename, extname } from "node:path";
16
16
  import { artifactDir } from "../../artifact-store.js";
17
+ import { fitImageForInline } from "../inline-image.js";
18
+ import { MAX_RESPONSE_SIZE } from "../screenshot-utils.js";
17
19
  /** Largest artifact returned inline. Base64 adds a third on top of this. */
18
20
  const MAX_BYTES = 3_500_000;
21
+ /**
22
+ * Base64 budget for an image this tool returns by default.
23
+ *
24
+ * The comment above says it: hosts truncate a tool result near 150k chars. That
25
+ * holds for a view's callServerTool too. A 1280x800 attention heatmap PNG is
26
+ * ~666k chars of base64, so the attention view asked for it, the host cut it,
27
+ * and the panel showed no image -- while the tool result it rendered from
28
+ * carried a perfectly good inline preview it never reads (Alexa, 2026-10-09:
29
+ * "the attention analysis image doesn't load inline again"). Images over this
30
+ * are fitted (JPEG quality ladder, then downscale) unless `full: true`.
31
+ */
32
+ const VIEW_IMAGE_BUDGET = MAX_RESPONSE_SIZE - 10_000;
19
33
  const MIME = {
20
34
  ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg",
21
35
  ".gif": "image/gif", ".webp": "image/webp",
@@ -26,6 +40,7 @@ export function registerArtifactTools(server) {
26
40
  description: "Fetch a generated image artifact (heatmap, diff, capture frame) as inline image data. Called by interactive views to display images the widget sandbox cannot load by URL; not usually needed directly.",
27
41
  inputSchema: {
28
42
  file: z.string().describe("Artifact filename as returned by the producing tool, e.g. 'heatmap-abc123.png'"),
43
+ full: z.boolean().optional().describe("Return the original bytes even when they exceed the host's ~150k-char result cap (a host may then truncate the result). Default false: a larger still image comes back as a JPEG fitted under the cap."),
29
44
  },
30
45
  annotations: {
31
46
  title: "Fetch Artifact",
@@ -37,7 +52,7 @@ export function registerArtifactTools(server) {
37
52
  // Hidden from the model's tool list: it exists for views to call, and an
38
53
  // agent reaching for it directly would be fetching bytes it cannot use.
39
54
  _meta: { ui: { visibility: ["app"] } },
40
- }, async ({ file }) => {
55
+ }, async ({ file, full }) => {
41
56
  // basename strips any directory component, so a traversal attempt
42
57
  // resolves to a plain name inside the artifact dir rather than escaping it.
43
58
  // The name is then re-validated, because basename alone would happily
@@ -66,10 +81,22 @@ export function registerArtifactTools(server) {
66
81
  isError: true,
67
82
  };
68
83
  }
84
+ const bytes = readFileSync(path);
85
+ const base64 = bytes.toString("base64");
86
+ // Animated GIFs are never re-encoded: a JPEG would keep one frame.
87
+ if (full !== true && ext !== ".gif" && base64.length > VIEW_IMAGE_BUDGET) {
88
+ const fitted = await fitImageForInline(bytes, VIEW_IMAGE_BUDGET).catch(() => null);
89
+ if (fitted) {
90
+ return {
91
+ content: [{ type: "image", data: fitted.data, mimeType: fitted.mimeType }],
92
+ _meta: { fitted: { width: fitted.width, height: fitted.height, sourceWidth: fitted.sourceWidth, sourceHeight: fitted.sourceHeight, quality: fitted.quality, originalBase64Chars: base64.length } },
93
+ };
94
+ }
95
+ }
69
96
  return {
70
97
  content: [{
71
98
  type: "image",
72
- data: readFileSync(path).toString("base64"),
99
+ data: base64,
73
100
  mimeType: MIME[ext],
74
101
  }],
75
102
  };
@@ -1 +1 @@
1
- {"version":3,"file":"artifact-tools.js","sourceRoot":"","sources":["../../../src/mcp-tools/base/artifact-tools.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"artifact-tools.js","sourceRoot":"","sources":["../../../src/mcp-tools/base/artifact-tools.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"audit-tools.d.ts","sourceRoot":"","sources":["../../../src/mcp-tools/base/audit-tools.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,OAAO,KAAK,EAAE,SAAS,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAuDtE;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,IAAI,CA2lC7F;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAogBhE"}
1
+ {"version":3,"file":"audit-tools.d.ts","sourceRoot":"","sources":["../../../src/mcp-tools/base/audit-tools.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,OAAO,KAAK,EAAE,SAAS,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AA0DtE;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,IAAI,CAqkC7F;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA+kBhE"}
@@ -10,8 +10,9 @@ import { randomBytes } from "crypto";
10
10
  import { writeArtifact } from "../../artifact-store.js";
11
11
  import { buildContentWithPreviews } from "../inline-image.js";
12
12
  import { bridgeCapacityTraits } from "../../visual/cognitive-models.js";
13
- import { runAgentReadyAudit, runCompetitiveBenchmark, runEmpathyAudit, runWebMCPReadyAudit, } from "../../analysis/index.js";
13
+ import { runAgentReadyAudit, pageAgentReadyFindings, runCompetitiveBenchmark, runEmpathyAudit, runWebMCPReadyAudit, } from "../../analysis/index.js";
14
14
  import { listAccessibilityPersonas } from "../../personas.js";
15
+ import { barrierElementCount, countAffectedElements } from "../../analysis/accessibility-empathy.js";
15
16
  import { getDefaultConfig } from "../../config.js";
16
17
  import { writeFileSync, mkdirSync, existsSync } from "fs";
17
18
  import { join } from "path";
@@ -69,6 +70,8 @@ export function registerAuditTools(server, context) {
69
70
  inputSchema: {
70
71
  url: z.string().url().describe("URL to audit"),
71
72
  device: z.string().optional().describe("Device emulation: 'mobile', 'tablet', 'desktop', or specific device like 'iPhone 15'. Tests mobile AI-friendliness (responsive selectors, touch targets, viewport-specific layouts)."),
73
+ limit: z.number().optional().default(5).describe("How many issues (worst first) and recommendations (priority order) to return. Totals and severity counts always cover ALL findings."),
74
+ offset: z.number().optional().default(0).describe("Skip this many issues and recommendations before returning, for paging through a large result."),
72
75
  },
73
76
  annotations: {
74
77
  title: "Agent-Ready Audit",
@@ -77,43 +80,18 @@ export function registerAuditTools(server, context) {
77
80
  idempotentHint: false,
78
81
  openWorldHint: true,
79
82
  },
80
- }, async ({ url, device }) => {
83
+ }, async ({ url, device, limit, offset }) => {
81
84
  const result = await runAgentReadyAudit(url, { headless: true, ...(device ? { device } : {}) });
82
85
  const responseData = {
83
86
  url: result.url,
84
87
  score: result.score,
85
88
  grade: result.grade,
86
89
  summary: result.summary,
87
- // Worst-first, and the cut is reported.
88
- //
89
- // This was a plain slice(0,5) over the issues in detection order, so
90
- // severity had no bearing on what survived: a critical finding placed
91
- // sixth by the order detectors happen to run was dropped in favour of
92
- // five low ones, with nothing saying anything had been dropped. Found
93
- // when a new critical detector fired in the audit and was invisible in
94
- // the tool that wraps it. (2026-08-01)
95
- ...(() => {
96
- const rank = (sev) => ({ critical: 0, high: 1, medium: 2, low: 3, info: 4 }[String(sev).toLowerCase()] ?? 5);
97
- const sorted = [...result.issues].sort((a, b) => rank(a.severity) - rank(b.severity));
98
- const shown = sorted.slice(0, 5);
99
- const omitted = sorted.length - shown.length;
100
- return {
101
- topIssues: shown,
102
- issuesFound: result.issues.length,
103
- ...(omitted > 0
104
- ? {
105
- issuesOmitted: omitted,
106
- issuesNote: `Showing the 5 most severe of ${result.issues.length}. Severity counts across ALL findings: ` +
107
- Object.entries(result.issues.reduce((m, i) => {
108
- const k = String(i.severity);
109
- m[k] = (m[k] ?? 0) + 1;
110
- return m;
111
- }, {})).map(([k, v]) => `${k} ${v}`).join(", ") + ".",
112
- }
113
- : {}),
114
- };
115
- })(),
116
- topRecommendations: result.recommendations.slice(0, 5),
90
+ // Worst-first, paged, and the cut is reported (pageAgentReadyFindings).
91
+ // Worst-first since 2026-08-01, when a critical placed sixth in
92
+ // detection order was dropped for five lows; paged since 2026-10-09,
93
+ // when everything past the fifth finding was unreachable.
94
+ ...pageAgentReadyFindings(result, { limit, offset }),
117
95
  duration: result.duration,
118
96
  };
119
97
  // Auto-save handled by tier-gate wrapper
@@ -1246,8 +1224,8 @@ export function registerEmpathyAuditTool(server) {
1246
1224
  // the numbers on the map read 1,2,3 down the list instead of
1247
1225
  // whatever order the analyzer happened to emit.
1248
1226
  const rank = (b) => {
1249
- const { weight } = barrierWeightFor(testedPersona, String(b.type ?? ""), b.wcagCriteria);
1250
- const { severity } = weightedSeverity(String(b.severity ?? "minor"), weight);
1227
+ const { weight } = barrierWeightFor(testedPersona, String(b.type ?? ""), b.wcagCriteria, b.weightKey);
1228
+ const { severity } = weightedSeverity(String(b.severity ?? "minor"), weight, { aboveAuditLevel: b.aboveAuditLevel });
1251
1229
  const order = {
1252
1230
  critical: 0, blocker: 0, high: 1, major: 1, serious: 1,
1253
1231
  medium: 2, moderate: 2, low: 3, minor: 3, info: 4, notice: 4
@@ -1258,7 +1236,7 @@ export function registerEmpathyAuditTool(server) {
1258
1236
  })();
1259
1237
  // Group keys in list order; a rect's key looked up here gives its
1260
1238
  // finding number.
1261
- const findingKeys = chosenBarriers.map((b) => `${b.type}|${weightKeyFor(String(b.type ?? ""), b.wcagCriteria) ?? ""}`);
1239
+ const findingKeys = chosenBarriers.map((b) => `${b.type}|${weightKeyFor(String(b.type ?? ""), b.wcagCriteria, b.weightKey) ?? ""}`);
1262
1240
  // The name that was measured, when it differs from the one asked for.
1263
1241
  // "motor-tremor" measured motor-impairment-tremor while testedPersona
1264
1242
  // echoed "motor-tremor" and only resultsSummary showed the substitution.
@@ -1286,7 +1264,7 @@ export function registerEmpathyAuditTool(server) {
1286
1264
  ...(result.outOfViewportBarriersDropped
1287
1265
  ? {
1288
1266
  outOfViewportBarriersDropped: result.outOfViewportBarriersDropped,
1289
- scopeFilterNote: `${result.outOfViewportBarriersDropped} barrier(s) outside the viewport were excluded from scoring. Use scope='full_page' to include them.`,
1267
+ scopeFilterNote: `${result.outOfViewportBarriersDropped} barrier(s) outside the viewport or hidden (zero size, e.g. display:none) were excluded from scoring. Use scope='full_page' to include them.`,
1290
1268
  }
1291
1269
  : {}),
1292
1270
  resultsSummary: result.results.map((r) => {
@@ -1329,8 +1307,17 @@ export function registerEmpathyAuditTool(server) {
1329
1307
  } : undefined,
1330
1308
  barrierTypeCount: uniqueTypes.size,
1331
1309
  barrierTypes: Array.from(uniqueTypes),
1332
- affectedElements: r.barriers.length,
1310
+ // Barrier RECORDS and affected ELEMENTS are different numbers and
1311
+ // both ship. affectedElements was barriers.length, so it could not
1312
+ // be reconciled with topBarriers' per-entry counts. It is now the
1313
+ // same sum topBarriers is built from: for a one-persona audit,
1314
+ // sum(topBarriers[].affectedElementCount) +
1315
+ // (topBarriersOmitted?.affectedElements ?? 0). (B7, 2026-10-09)
1316
+ barrierCount: r.barriers.length,
1317
+ affectedElements: countAffectedElements(r.barriers),
1333
1318
  wcagViolationCount: r.wcagViolations.length,
1319
+ // Above-level findings, listed in `advisories` and never scored.
1320
+ ...(r.advisories?.length ? { advisoryCount: r.advisories.length } : {}),
1334
1321
  // v18.26.0: Perceptual transport metrics (Wasserstein-based)
1335
1322
  perceptualTransport: r.perceptualTransport || undefined,
1336
1323
  empathyScoreBarrierOnly: r.empathyScoreBarrierOnly || undefined,
@@ -1386,8 +1373,8 @@ export function registerEmpathyAuditTool(server) {
1386
1373
  // more or less violated depending on who reads it -- so both ship
1387
1374
  // and the weight that separates them ships with them.
1388
1375
  return chosen.map((b, i) => {
1389
- const { weight, key, defaulted } = barrierWeightFor(testedPersona, String(b.type ?? ""), b.wcagCriteria);
1390
- const { severity, shifted } = weightedSeverity(String(b.severity ?? "minor"), weight);
1376
+ const { weight, key, defaulted } = barrierWeightFor(testedPersona, String(b.type ?? ""), b.wcagCriteria, b.weightKey);
1377
+ const { severity, shifted } = weightedSeverity(String(b.severity ?? "minor"), weight, { aboveAuditLevel: b.aboveAuditLevel });
1391
1378
  return {
1392
1379
  ...b,
1393
1380
  // Same number the overlay draws on the matching boxes.
@@ -1404,6 +1391,35 @@ export function registerEmpathyAuditTool(server) {
1404
1391
  };
1405
1392
  });
1406
1393
  })(),
1394
+ // Findings above the audited WCAG level. Advisory by policy (Alexa,
1395
+ // 2026-10-09): reported here so nothing is hidden, never scored,
1396
+ // never escalated by persona weight, never "critical". (B6)
1397
+ ...(result.advisories?.length
1398
+ ? {
1399
+ advisories: result.advisories.map((a) => {
1400
+ const { weight, key } = barrierWeightFor(testedPersona, String(a.type ?? ""), a.wcagCriteria, a.weightKey);
1401
+ return {
1402
+ ...a,
1403
+ severityForPersona: weightedSeverity(String(a.severity ?? "minor"), weight, { aboveAuditLevel: true }).severity,
1404
+ personaWeightKey: key,
1405
+ };
1406
+ }),
1407
+ advisoriesNote: `Findings whose WCAG level is above the audited level (${wcagLevel}). Advisory: reported for information, not scored, not counted in affectedElements, never escalated by persona weight. Re-run with wcagLevel set to their level to score them.`,
1408
+ }
1409
+ : {}),
1410
+ // Groups not shown in topBarriers, so the shown entries plus this
1411
+ // add up to resultsSummary.affectedElements. Dropped groups used to
1412
+ // vanish from the sum without a trace. (B7)
1413
+ ...(() => {
1414
+ const shown = new Set(chosenBarriers);
1415
+ const omitted = result.topBarriers.filter((b) => !shown.has(b));
1416
+ return omitted.length > 0
1417
+ ? { topBarriersOmitted: {
1418
+ groups: omitted.length,
1419
+ affectedElements: omitted.reduce((n, b) => n + (b.affectedElementCount ?? 0), 0),
1420
+ } }
1421
+ : {};
1422
+ })(),
1407
1423
  topBarriersNote: "Ordered worst-first by severityForPersona — the persona-weighted grade — so an entry can sit above a higher WCAG severity when this persona is more susceptible to it. Any entries after the fifth are included because they carry a WCAG criterion the top five do not.",
1408
1424
  // Asserting full coverage was itself a defect: the claim held only
1409
1425
  // when a barrier existed for every criterion, and said nothing when
@@ -1466,23 +1482,63 @@ export function registerEmpathyAuditTool(server) {
1466
1482
  // is stated. Reported rather than papered over, the same way
1467
1483
  // cognitiveLoadReadings reports its disagreement.
1468
1484
  barrierRectCoverage: (() => {
1469
- // Counted the same way barrierRects is built below, from
1470
- // barriers that resolved to a rect. Reading r.barrierRects gave
1471
- // 0 every time: that field is this payload's OUTPUT name, not an
1472
- // input, so the coverage line reported drawn:0 beside ten
1473
- // populated rects.
1474
- // Zero-area rects draw nothing. They were counted as drawn, so
1475
- // coverage read 19 while only 16 boxes were visible -- an
1476
- // element that resolved to {0,0,0,0} (display:none, a collapsed
1477
- // menu item) is undrawn in every sense that matters.
1478
- const rects = (r.barriers ?? []).filter((b) => b.rect && b.rect.width > 0 && b.rect.height > 0).length;
1479
- const affected = (r.barriers ?? []).reduce((n, b) => n + (b.affectedElementCount ?? 1), 0);
1485
+ // Per BARRIER, with the reason each undrawn one has no box.
1486
+ //
1487
+ // This subtracted drawn rects from affected elements and gave
1488
+ // one hardcoded reason, "typically below the fold, hidden, or an
1489
+ // unresolvable selector". In a viewport audit that reason is
1490
+ // wrong by construction: located barriers below the fold were
1491
+ // already dropped (outOfViewportBarriersDropped), so every
1492
+ // undrawn one was a barrier that never had a rect at all. And
1493
+ // elements minus boxes is not a count of anything a reader can
1494
+ // list. (B7, 2026-10-09)
1495
+ //
1496
+ // Zero-area rects draw nothing and are undrawn: an element that
1497
+ // resolved to {0,0,0,0} (display:none, a collapsed menu item)
1498
+ // is undrawn in every sense that matters.
1499
+ const origin = scope === "full_page"
1500
+ ? { x: 0, y: 0 }
1501
+ : (r.captureScroll ?? { x: 0, y: 0 });
1502
+ const size = r.screenshotSize
1503
+ ?? { width: r.viewportSize?.width ?? Infinity,
1504
+ height: scope === "full_page" ? (r.documentHeight ?? Infinity) : (r.viewportSize?.height ?? Infinity) };
1505
+ const undrawnBarriers = [];
1506
+ let drawn = 0;
1507
+ for (const b of (r.barriers ?? [])) {
1508
+ let reason = null;
1509
+ if (!b.rect)
1510
+ reason = b.pageLevel ? "pageLevel" : "notLocated";
1511
+ else if (!(b.rect.width > 0 && b.rect.height > 0))
1512
+ reason = "zeroArea";
1513
+ else {
1514
+ const ix = b.rect.x - origin.x;
1515
+ const iy = b.rect.y - origin.y;
1516
+ if (iy + b.rect.height < 0 || ix + b.rect.width < 0 || iy > size.height || ix > size.width) {
1517
+ reason = "outsideCapture";
1518
+ }
1519
+ }
1520
+ if (reason === null) {
1521
+ drawn++;
1522
+ continue;
1523
+ }
1524
+ undrawnBarriers.push({ type: b.type, element: b.element, reason,
1525
+ affectedElementCount: barrierElementCount(b) });
1526
+ }
1527
+ const legend = {
1528
+ pageLevel: "A finding about the page as a whole, or a page-wide aggregate (its elements are in members), with no single location to outline.",
1529
+ zeroArea: "The element resolved to a zero-size box at capture time (display:none, collapsed), so there is nothing to outline.",
1530
+ outsideCapture: "The element's box lies outside the captured screenshot.",
1531
+ notLocated: "The detector recorded no location for this barrier. A defect in the detector, not a property of the page.",
1532
+ };
1533
+ const present = Array.from(new Set(undrawnBarriers.map((u) => u.reason)));
1480
1534
  return {
1481
- drawn: rects,
1482
- affectedElements: affected,
1483
- ...(affected > rects
1484
- ? { undrawn: affected - rects,
1485
- undrawnReason: "Barriers whose element did not resolve to a bounding box at capture time — typically below the fold, hidden, or an unresolvable selector. They are counted in the score and listed in barriers, but the overlay cannot outline them." }
1535
+ barriers: (r.barriers ?? []).length,
1536
+ drawn,
1537
+ affectedElements: countAffectedElements(r.barriers ?? []),
1538
+ ...(undrawnBarriers.length > 0
1539
+ ? { undrawn: undrawnBarriers.length,
1540
+ undrawnBarriers,
1541
+ undrawnReasons: Object.fromEntries(present.map((p) => [p, legend[p]])) }
1486
1542
  : {}),
1487
1543
  };
1488
1544
  })(),
@@ -1552,13 +1608,13 @@ export function registerEmpathyAuditTool(server) {
1552
1608
  // weighted audit one barrier was orange on the map and red in
1553
1609
  // the list -- two scales, no label saying which was which.
1554
1610
  // (2026-07-31)
1555
- const { weight } = barrierWeightFor(r.persona, String(b.type ?? ""), b.wcagCriteria);
1556
- const { severity: weighted } = weightedSeverity(String(b.severity ?? "minor"), weight);
1611
+ const { weight } = barrierWeightFor(r.persona, String(b.type ?? ""), b.wcagCriteria, b.weightKey);
1612
+ const { severity: weighted } = weightedSeverity(String(b.severity ?? "minor"), weight, { aboveAuditLevel: b.aboveAuditLevel });
1557
1613
  // Which findings-list entry this box belongs to. Boxes are
1558
1614
  // per-element and findings are grouped by type+criterion, so
1559
1615
  // ten boxes can map to five findings; without the link there is
1560
1616
  // no way to tell which box the list is talking about.
1561
- const gk = `${b.type}|${weightKeyFor(String(b.type ?? ""), b.wcagCriteria) ?? ""}`;
1617
+ const gk = `${b.type}|${weightKeyFor(String(b.type ?? ""), b.wcagCriteria, b.weightKey) ?? ""}`;
1562
1618
  const findingIndex = findingKeys.indexOf(gk);
1563
1619
  return {
1564
1620
  type: b.type, severity: b.severity, element: b.element,