@riddledc/riddle-proof 0.7.42 → 0.7.44

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
@@ -148,6 +148,7 @@ or as a stronger proof base before a change loop.
148
148
  { "type": "selector_absent", "selector": "[data-testid='loading-spinner']" },
149
149
  { "type": "selector_count_equals", "selector": "[data-testid='pricing-card']", "expected_count": 3 },
150
150
  { "type": "text_visible", "text": "Start building" },
151
+ { "type": "text_visible", "text": "Compare plans", "viewports": ["desktop"] },
151
152
  { "type": "no_mobile_horizontal_overflow" },
152
153
  { "type": "no_fatal_console_errors" }
153
154
  ],
@@ -179,6 +180,11 @@ The package includes a generic starter profile at
179
180
  profile directory and replace the selector/text checks with app-specific
180
181
  invariants.
181
182
 
183
+ Checks normally apply to every captured viewport. Add `viewports` (or
184
+ `viewport_names`) to a check when responsive UI intentionally exposes an
185
+ invariant only on named viewports, such as desktop-only helper copy while phone
186
+ layouts keep the same route, link, and overflow contracts.
187
+
182
188
  `target.network_mocks` is optional. The Riddle runner registers these mocks
183
189
  before navigation, records each hit, and adds an implicit
184
190
  `network_mocks_succeeded` check when mocks are present. A mock supports
@@ -252,7 +258,10 @@ known browser console error, such as a mocked `503` that the app recovers from:
252
258
  Allowed console events and page errors are still counted in check evidence, but
253
259
  only unallowed `error` / `assert` console events and page errors fail the check.
254
260
  Use `allowed_page_error_patterns`, `allowed_console_texts`, or
255
- `allowed_page_error_texts` for narrower matching when needed.
261
+ `allowed_page_error_texts` for narrower matching when needed. Console allowlists
262
+ match both the console text and the event location URL, which is useful for
263
+ expected resource probes where the browser message is generic but the URL is
264
+ specific.
256
265
 
257
266
  Use `selector_absent` when a forbidden element must not render, and
258
267
  `selector_count_equals` / `selector_count_equal` / `selector_count_eq` when a
@@ -402,6 +402,7 @@ function normalizeCheck(input, index) {
402
402
  text: stringValue(input.text),
403
403
  pattern: stringValue(input.pattern),
404
404
  flags: stringValue(input.flags),
405
+ viewports: normalizeStringList(input.viewports ?? input.viewport_names ?? input.viewportNames, `checks[${index}] viewports`),
405
406
  allowed_console_texts: normalizeStringList(input.allowed_console_texts ?? input.allowedConsoleTexts ?? input.allow_console_texts ?? input.allowConsoleTexts, `checks[${index}] allowed_console_texts`),
406
407
  allowed_console_patterns: normalizeStringList(input.allowed_console_patterns ?? input.allowedConsolePatterns ?? input.allow_console_patterns ?? input.allowConsolePatterns, `checks[${index}] allowed_console_patterns`),
407
408
  allowed_page_error_texts: normalizeStringList(input.allowed_page_error_texts ?? input.allowedPageErrorTexts ?? input.allow_page_error_texts ?? input.allowPageErrorTexts, `checks[${index}] allowed_page_error_texts`),
@@ -570,8 +571,17 @@ function matchText(sample, check) {
570
571
  }
571
572
  return sample.includes(check.text || "");
572
573
  }
573
- function matchesAllowedMessage(message, texts, patterns) {
574
- const sample = String(message || "");
574
+ function allowedMessageSample(input) {
575
+ if (!isRecord(input)) return String(input || "");
576
+ const parts = [
577
+ input.text,
578
+ input.message,
579
+ isRecord(input.location) ? input.location.url : void 0
580
+ ];
581
+ return parts.map((part) => String(part || "")).filter(Boolean).join(" ");
582
+ }
583
+ function matchesAllowedMessage(input, texts, patterns) {
584
+ const sample = allowedMessageSample(input);
575
585
  if (texts?.some((text) => sample.includes(text))) return true;
576
586
  for (const pattern of patterns || []) {
577
587
  try {
@@ -631,15 +641,22 @@ function successfulRoute(route, targetUrl) {
631
641
  const matched = route.matched || routePathMatches(route.observed, route.expected_path, targetUrl);
632
642
  return matched && !route.error && (route.http_status === null || route.http_status === void 0 || route.http_status < 400);
633
643
  }
644
+ function viewportsForCheck(check, viewports) {
645
+ if (!check.viewports?.length) return viewports;
646
+ const names = new Set(check.viewports);
647
+ return viewports.filter((viewport) => names.has(viewport.name));
648
+ }
634
649
  function assessCheckFromEvidence(check, evidence) {
635
- const viewports = evidence.viewports || [];
650
+ const viewports = viewportsForCheck(check, evidence.viewports || []);
636
651
  if (!viewports.length) {
637
652
  return {
638
653
  type: check.type,
639
654
  label: checkLabel(check),
640
655
  status: "failed",
641
- evidence: {},
642
- message: "No viewport evidence was captured."
656
+ evidence: {
657
+ expected_viewports: check.viewports || []
658
+ },
659
+ message: check.viewports?.length ? `No matching viewport evidence was captured for ${check.viewports.join(", ")}.` : "No viewport evidence was captured."
643
660
  };
644
661
  }
645
662
  if (check.type === "route_loaded") {
@@ -899,11 +916,11 @@ function assessCheckFromEvidence(check, evidence) {
899
916
  }
900
917
  if (check.type === "no_fatal_console_errors") {
901
918
  const fatalConsoleEvents = (evidence.console?.events || []).filter((event) => event.type === "error" || event.type === "assert");
902
- const allowedConsoleEvents = fatalConsoleEvents.filter((event) => matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
903
- const unallowedConsoleEvents = fatalConsoleEvents.filter((event) => !matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
919
+ const allowedConsoleEvents = fatalConsoleEvents.filter((event) => matchesAllowedMessage(event, check.allowed_console_texts, check.allowed_console_patterns));
920
+ const unallowedConsoleEvents = fatalConsoleEvents.filter((event) => !matchesAllowedMessage(event, check.allowed_console_texts, check.allowed_console_patterns));
904
921
  const pageErrors = evidence.page_errors || [];
905
- const allowedPageErrors = pageErrors.filter((error) => matchesAllowedMessage(error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
906
- const unallowedPageErrors = pageErrors.filter((error) => !matchesAllowedMessage(error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
922
+ const allowedPageErrors = pageErrors.filter((error) => matchesAllowedMessage(error, check.allowed_page_error_texts, check.allowed_page_error_patterns));
923
+ const unallowedPageErrors = pageErrors.filter((error) => !matchesAllowedMessage(error, check.allowed_page_error_texts, check.allowed_page_error_patterns));
907
924
  const fatalCount = unallowedConsoleEvents.length + unallowedPageErrors.length;
908
925
  return {
909
926
  type: check.type,
@@ -1188,8 +1205,17 @@ function textMatches(sample, check) {
1188
1205
  }
1189
1206
  return String(sample || "").includes(check.text || "");
1190
1207
  }
1191
- function matchesAllowedMessage(message, texts, patterns) {
1192
- const sample = String(message || "");
1208
+ function allowedMessageSample(input) {
1209
+ if (!input || typeof input !== "object" || Array.isArray(input)) return String(input || "");
1210
+ const parts = [
1211
+ input.text,
1212
+ input.message,
1213
+ input.location && typeof input.location === "object" ? input.location.url : undefined,
1214
+ ];
1215
+ return parts.map((part) => String(part || "")).filter(Boolean).join(" ");
1216
+ }
1217
+ function matchesAllowedMessage(input, texts, patterns) {
1218
+ const sample = allowedMessageSample(input);
1193
1219
  if ((texts || []).some((text) => sample.includes(text))) return true;
1194
1220
  for (const pattern of patterns || []) {
1195
1221
  try {
@@ -1235,6 +1261,11 @@ function frameTextSample(frame) {
1235
1261
  frame && frame.text,
1236
1262
  ].map((part) => String(part || "")).filter(Boolean).join(" ");
1237
1263
  }
1264
+ function viewportsForCheck(check, viewports) {
1265
+ if (!Array.isArray(check.viewports) || !check.viewports.length) return viewports;
1266
+ const names = new Set(check.viewports);
1267
+ return viewports.filter((viewport) => names.has(viewport.name));
1268
+ }
1238
1269
  function summarizeRouteInventory(viewport, inventory) {
1239
1270
  const directRoutes = Array.isArray(inventory.direct_routes) ? inventory.direct_routes : [];
1240
1271
  const clickthroughs = Array.isArray(inventory.clickthroughs) ? inventory.clickthroughs : [];
@@ -1425,9 +1456,22 @@ function assessProfile(profile, evidence) {
1425
1456
  });
1426
1457
  }
1427
1458
  for (const check of profile.checks || []) {
1459
+ const checkViewports = viewportsForCheck(check, viewports);
1460
+ if (!checkViewports.length) {
1461
+ checks.push({
1462
+ type: check.type,
1463
+ label: check.label || check.type,
1464
+ status: "failed",
1465
+ evidence: { expected_viewports: check.viewports || [] },
1466
+ message: Array.isArray(check.viewports) && check.viewports.length
1467
+ ? "No matching viewport evidence was captured for " + check.viewports.join(", ") + "."
1468
+ : "No viewport evidence was captured.",
1469
+ });
1470
+ continue;
1471
+ }
1428
1472
  if (check.type === "route_loaded") {
1429
1473
  const expectedPath = check.expected_path || new URL(evidence.target_url).pathname || "/";
1430
- const failed = viewports.filter((viewport) => {
1474
+ const failed = checkViewports.filter((viewport) => {
1431
1475
  const route = { ...(viewport.route || {}), expected_path: expectedPath };
1432
1476
  route.matched = routePathMatches(route.observed, expectedPath, evidence.target_url) || route.matched;
1433
1477
  return !routeOk(route, evidence.target_url);
@@ -1438,8 +1482,8 @@ function assessProfile(profile, evidence) {
1438
1482
  status: failed.length ? "failed" : "passed",
1439
1483
  evidence: {
1440
1484
  expected_path: expectedPath,
1441
- observed_paths: viewports.map((viewport) => viewport.route && viewport.route.observed),
1442
- http_statuses: viewports.map((viewport) => viewport.route ? viewport.route.http_status ?? null : null),
1485
+ observed_paths: checkViewports.map((viewport) => viewport.route && viewport.route.observed),
1486
+ http_statuses: checkViewports.map((viewport) => viewport.route ? viewport.route.http_status ?? null : null),
1443
1487
  },
1444
1488
  message: failed.length ? "Route did not load as " + expectedPath + " in " + failed.length + " viewport(s)." : undefined,
1445
1489
  });
@@ -1447,24 +1491,24 @@ function assessProfile(profile, evidence) {
1447
1491
  }
1448
1492
  if (check.type === "selector_visible") {
1449
1493
  const selector = check.selector || "";
1450
- const failed = viewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].visible_count < 1);
1494
+ const failed = checkViewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].visible_count < 1);
1451
1495
  checks.push({
1452
1496
  type: check.type,
1453
1497
  label: check.label || check.type,
1454
1498
  status: failed.length ? "failed" : "passed",
1455
- evidence: { selector, visible_counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].visible_count : 0) },
1499
+ evidence: { selector, visible_counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].visible_count : 0) },
1456
1500
  message: failed.length ? "Selector " + selector + " was not visible in " + failed.length + " viewport(s)." : undefined,
1457
1501
  });
1458
1502
  continue;
1459
1503
  }
1460
1504
  if (check.type === "selector_absent") {
1461
1505
  const selector = check.selector || "";
1462
- const failed = viewports.filter((viewport) => viewport.selectors && viewport.selectors[selector] && viewport.selectors[selector].count > 0);
1506
+ const failed = checkViewports.filter((viewport) => viewport.selectors && viewport.selectors[selector] && viewport.selectors[selector].count > 0);
1463
1507
  checks.push({
1464
1508
  type: check.type,
1465
1509
  label: check.label || check.type,
1466
1510
  status: failed.length ? "failed" : "passed",
1467
- evidence: { selector, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
1511
+ evidence: { selector, counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
1468
1512
  message: failed.length ? "Selector " + selector + " was present in " + failed.length + " viewport(s)." : undefined,
1469
1513
  });
1470
1514
  continue;
@@ -1472,12 +1516,12 @@ function assessProfile(profile, evidence) {
1472
1516
  if (check.type === "selector_count_at_least") {
1473
1517
  const selector = check.selector || "";
1474
1518
  const minCount = check.min_count == null ? 1 : check.min_count;
1475
- const failed = viewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].count < minCount);
1519
+ const failed = checkViewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].count < minCount);
1476
1520
  checks.push({
1477
1521
  type: check.type,
1478
1522
  label: check.label || check.type,
1479
1523
  status: failed.length ? "failed" : "passed",
1480
- evidence: { selector, min_count: minCount, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
1524
+ evidence: { selector, min_count: minCount, counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
1481
1525
  message: failed.length ? "Selector " + selector + " count was below " + minCount + " in " + failed.length + " viewport(s)." : undefined,
1482
1526
  });
1483
1527
  continue;
@@ -1485,12 +1529,12 @@ function assessProfile(profile, evidence) {
1485
1529
  if (check.type === "selector_count_equals" || check.type === "selector_count_equal" || check.type === "selector_count_eq") {
1486
1530
  const selector = check.selector || "";
1487
1531
  const expectedCount = check.expected_count == null ? 0 : check.expected_count;
1488
- const failed = viewports.filter((viewport) => (viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) !== expectedCount);
1532
+ const failed = checkViewports.filter((viewport) => (viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) !== expectedCount);
1489
1533
  checks.push({
1490
1534
  type: check.type,
1491
1535
  label: check.label || check.type,
1492
1536
  status: failed.length ? "failed" : "passed",
1493
- evidence: { selector, expected_count: expectedCount, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
1537
+ evidence: { selector, expected_count: expectedCount, counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
1494
1538
  message: failed.length ? "Selector " + selector + " count did not equal " + expectedCount + " in " + failed.length + " viewport(s)." : undefined,
1495
1539
  });
1496
1540
  continue;
@@ -1498,7 +1542,7 @@ function assessProfile(profile, evidence) {
1498
1542
  if (check.type === "selector_text_order") {
1499
1543
  const selector = check.selector || "";
1500
1544
  const expectedTexts = check.expected_texts || [];
1501
- const results = viewports.map((viewport) => {
1545
+ const results = checkViewports.map((viewport) => {
1502
1546
  const texts = textSequenceForCheck(viewport, check);
1503
1547
  const match = textOrderMatch(texts, expectedTexts);
1504
1548
  return {
@@ -1520,7 +1564,7 @@ function assessProfile(profile, evidence) {
1520
1564
  }
1521
1565
  if (check.type === "frame_text_visible") {
1522
1566
  const selector = check.selector || "";
1523
- const results = viewports.map((viewport) => {
1567
+ const results = checkViewports.map((viewport) => {
1524
1568
  const frames = frameEvidenceForSelector(viewport, selector);
1525
1569
  const matches = frames
1526
1570
  .map((frame, index) => ({ index, frame, matched: textMatches(frameTextSample(frame), check) }))
@@ -1547,7 +1591,7 @@ function assessProfile(profile, evidence) {
1547
1591
  if (check.type === "frame_no_horizontal_overflow") {
1548
1592
  const selector = check.selector || "";
1549
1593
  const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
1550
- const results = viewports.map((viewport) => {
1594
+ const results = checkViewports.map((viewport) => {
1551
1595
  const frames = frameEvidenceForSelector(viewport, selector);
1552
1596
  const frameOverflows = frames.map((frame, index) => ({
1553
1597
  index,
@@ -1577,7 +1621,7 @@ function assessProfile(profile, evidence) {
1577
1621
  if (check.type === "text_visible" || check.type === "text_absent") {
1578
1622
  const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
1579
1623
  const expectedVisible = check.type === "text_visible";
1580
- const matches = viewports.map((viewport) => viewport.text_matches && typeof viewport.text_matches[key] === "boolean" ? viewport.text_matches[key] : textMatches(viewport.body_text_sample || "", check));
1624
+ const matches = checkViewports.map((viewport) => viewport.text_matches && typeof viewport.text_matches[key] === "boolean" ? viewport.text_matches[key] : textMatches(viewport.body_text_sample || "", check));
1581
1625
  const failed = matches.filter((matched) => matched !== expectedVisible).length;
1582
1626
  checks.push({
1583
1627
  type: check.type,
@@ -1589,7 +1633,7 @@ function assessProfile(profile, evidence) {
1589
1633
  continue;
1590
1634
  }
1591
1635
  if (check.type === "route_inventory") {
1592
- const inventories = viewports
1636
+ const inventories = checkViewports
1593
1637
  .map((viewport) => ({ viewport: viewport.name, inventory: viewport.route_inventory }))
1594
1638
  .filter((item) => item.inventory && typeof item.inventory === "object");
1595
1639
  if (!inventories.length) {
@@ -1641,7 +1685,7 @@ function assessProfile(profile, evidence) {
1641
1685
  }
1642
1686
  if (check.type === "no_horizontal_overflow" || check.type === "no_mobile_horizontal_overflow") {
1643
1687
  const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
1644
- const applicable = check.type === "no_mobile_horizontal_overflow" ? viewports.filter((viewport) => viewport.width <= 820) : viewports;
1688
+ const applicable = check.type === "no_mobile_horizontal_overflow" ? checkViewports.filter((viewport) => viewport.width <= 820) : checkViewports;
1645
1689
  if (!applicable.length) {
1646
1690
  checks.push({
1647
1691
  type: check.type,
@@ -1670,11 +1714,11 @@ function assessProfile(profile, evidence) {
1670
1714
  }
1671
1715
  if (check.type === "no_fatal_console_errors") {
1672
1716
  const fatalConsoleEvents = ((evidence.console && evidence.console.events) || []).filter((event) => event && (event.type === "error" || event.type === "assert"));
1673
- const allowedConsoleEvents = fatalConsoleEvents.filter((event) => matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
1674
- const unallowedConsoleEvents = fatalConsoleEvents.filter((event) => !matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
1717
+ const allowedConsoleEvents = fatalConsoleEvents.filter((event) => matchesAllowedMessage(event, check.allowed_console_texts, check.allowed_console_patterns));
1718
+ const unallowedConsoleEvents = fatalConsoleEvents.filter((event) => !matchesAllowedMessage(event, check.allowed_console_texts, check.allowed_console_patterns));
1675
1719
  const pageErrors = evidence.page_errors || [];
1676
- const allowedPageErrors = pageErrors.filter((error) => matchesAllowedMessage(error && error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
1677
- const unallowedPageErrors = pageErrors.filter((error) => !matchesAllowedMessage(error && error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
1720
+ const allowedPageErrors = pageErrors.filter((error) => matchesAllowedMessage(error, check.allowed_page_error_texts, check.allowed_page_error_patterns));
1721
+ const unallowedPageErrors = pageErrors.filter((error) => !matchesAllowedMessage(error, check.allowed_page_error_texts, check.allowed_page_error_patterns));
1678
1722
  const fatalCount = unallowedConsoleEvents.length + unallowedPageErrors.length;
1679
1723
  checks.push({
1680
1724
  type: check.type,
package/dist/cli.cjs CHANGED
@@ -7275,6 +7275,7 @@ function normalizeCheck(input, index) {
7275
7275
  text: stringValue2(input.text),
7276
7276
  pattern: stringValue2(input.pattern),
7277
7277
  flags: stringValue2(input.flags),
7278
+ viewports: normalizeStringList(input.viewports ?? input.viewport_names ?? input.viewportNames, `checks[${index}] viewports`),
7278
7279
  allowed_console_texts: normalizeStringList(input.allowed_console_texts ?? input.allowedConsoleTexts ?? input.allow_console_texts ?? input.allowConsoleTexts, `checks[${index}] allowed_console_texts`),
7279
7280
  allowed_console_patterns: normalizeStringList(input.allowed_console_patterns ?? input.allowedConsolePatterns ?? input.allow_console_patterns ?? input.allowConsolePatterns, `checks[${index}] allowed_console_patterns`),
7280
7281
  allowed_page_error_texts: normalizeStringList(input.allowed_page_error_texts ?? input.allowedPageErrorTexts ?? input.allow_page_error_texts ?? input.allowPageErrorTexts, `checks[${index}] allowed_page_error_texts`),
@@ -7443,8 +7444,17 @@ function matchText(sample, check) {
7443
7444
  }
7444
7445
  return sample.includes(check.text || "");
7445
7446
  }
7446
- function matchesAllowedMessage(message, texts, patterns) {
7447
- const sample = String(message || "");
7447
+ function allowedMessageSample(input) {
7448
+ if (!isRecord(input)) return String(input || "");
7449
+ const parts = [
7450
+ input.text,
7451
+ input.message,
7452
+ isRecord(input.location) ? input.location.url : void 0
7453
+ ];
7454
+ return parts.map((part) => String(part || "")).filter(Boolean).join(" ");
7455
+ }
7456
+ function matchesAllowedMessage(input, texts, patterns) {
7457
+ const sample = allowedMessageSample(input);
7448
7458
  if (texts?.some((text) => sample.includes(text))) return true;
7449
7459
  for (const pattern of patterns || []) {
7450
7460
  try {
@@ -7504,15 +7514,22 @@ function successfulRoute(route, targetUrl) {
7504
7514
  const matched = route.matched || routePathMatches(route.observed, route.expected_path, targetUrl);
7505
7515
  return matched && !route.error && (route.http_status === null || route.http_status === void 0 || route.http_status < 400);
7506
7516
  }
7517
+ function viewportsForCheck(check, viewports) {
7518
+ if (!check.viewports?.length) return viewports;
7519
+ const names = new Set(check.viewports);
7520
+ return viewports.filter((viewport) => names.has(viewport.name));
7521
+ }
7507
7522
  function assessCheckFromEvidence(check, evidence) {
7508
- const viewports = evidence.viewports || [];
7523
+ const viewports = viewportsForCheck(check, evidence.viewports || []);
7509
7524
  if (!viewports.length) {
7510
7525
  return {
7511
7526
  type: check.type,
7512
7527
  label: checkLabel(check),
7513
7528
  status: "failed",
7514
- evidence: {},
7515
- message: "No viewport evidence was captured."
7529
+ evidence: {
7530
+ expected_viewports: check.viewports || []
7531
+ },
7532
+ message: check.viewports?.length ? `No matching viewport evidence was captured for ${check.viewports.join(", ")}.` : "No viewport evidence was captured."
7516
7533
  };
7517
7534
  }
7518
7535
  if (check.type === "route_loaded") {
@@ -7772,11 +7789,11 @@ function assessCheckFromEvidence(check, evidence) {
7772
7789
  }
7773
7790
  if (check.type === "no_fatal_console_errors") {
7774
7791
  const fatalConsoleEvents = (evidence.console?.events || []).filter((event) => event.type === "error" || event.type === "assert");
7775
- const allowedConsoleEvents = fatalConsoleEvents.filter((event) => matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
7776
- const unallowedConsoleEvents = fatalConsoleEvents.filter((event) => !matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
7792
+ const allowedConsoleEvents = fatalConsoleEvents.filter((event) => matchesAllowedMessage(event, check.allowed_console_texts, check.allowed_console_patterns));
7793
+ const unallowedConsoleEvents = fatalConsoleEvents.filter((event) => !matchesAllowedMessage(event, check.allowed_console_texts, check.allowed_console_patterns));
7777
7794
  const pageErrors = evidence.page_errors || [];
7778
- const allowedPageErrors = pageErrors.filter((error) => matchesAllowedMessage(error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
7779
- const unallowedPageErrors = pageErrors.filter((error) => !matchesAllowedMessage(error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
7795
+ const allowedPageErrors = pageErrors.filter((error) => matchesAllowedMessage(error, check.allowed_page_error_texts, check.allowed_page_error_patterns));
7796
+ const unallowedPageErrors = pageErrors.filter((error) => !matchesAllowedMessage(error, check.allowed_page_error_texts, check.allowed_page_error_patterns));
7780
7797
  const fatalCount = unallowedConsoleEvents.length + unallowedPageErrors.length;
7781
7798
  return {
7782
7799
  type: check.type,
@@ -8045,8 +8062,17 @@ function textMatches(sample, check) {
8045
8062
  }
8046
8063
  return String(sample || "").includes(check.text || "");
8047
8064
  }
8048
- function matchesAllowedMessage(message, texts, patterns) {
8049
- const sample = String(message || "");
8065
+ function allowedMessageSample(input) {
8066
+ if (!input || typeof input !== "object" || Array.isArray(input)) return String(input || "");
8067
+ const parts = [
8068
+ input.text,
8069
+ input.message,
8070
+ input.location && typeof input.location === "object" ? input.location.url : undefined,
8071
+ ];
8072
+ return parts.map((part) => String(part || "")).filter(Boolean).join(" ");
8073
+ }
8074
+ function matchesAllowedMessage(input, texts, patterns) {
8075
+ const sample = allowedMessageSample(input);
8050
8076
  if ((texts || []).some((text) => sample.includes(text))) return true;
8051
8077
  for (const pattern of patterns || []) {
8052
8078
  try {
@@ -8092,6 +8118,11 @@ function frameTextSample(frame) {
8092
8118
  frame && frame.text,
8093
8119
  ].map((part) => String(part || "")).filter(Boolean).join(" ");
8094
8120
  }
8121
+ function viewportsForCheck(check, viewports) {
8122
+ if (!Array.isArray(check.viewports) || !check.viewports.length) return viewports;
8123
+ const names = new Set(check.viewports);
8124
+ return viewports.filter((viewport) => names.has(viewport.name));
8125
+ }
8095
8126
  function summarizeRouteInventory(viewport, inventory) {
8096
8127
  const directRoutes = Array.isArray(inventory.direct_routes) ? inventory.direct_routes : [];
8097
8128
  const clickthroughs = Array.isArray(inventory.clickthroughs) ? inventory.clickthroughs : [];
@@ -8282,9 +8313,22 @@ function assessProfile(profile, evidence) {
8282
8313
  });
8283
8314
  }
8284
8315
  for (const check of profile.checks || []) {
8316
+ const checkViewports = viewportsForCheck(check, viewports);
8317
+ if (!checkViewports.length) {
8318
+ checks.push({
8319
+ type: check.type,
8320
+ label: check.label || check.type,
8321
+ status: "failed",
8322
+ evidence: { expected_viewports: check.viewports || [] },
8323
+ message: Array.isArray(check.viewports) && check.viewports.length
8324
+ ? "No matching viewport evidence was captured for " + check.viewports.join(", ") + "."
8325
+ : "No viewport evidence was captured.",
8326
+ });
8327
+ continue;
8328
+ }
8285
8329
  if (check.type === "route_loaded") {
8286
8330
  const expectedPath = check.expected_path || new URL(evidence.target_url).pathname || "/";
8287
- const failed = viewports.filter((viewport) => {
8331
+ const failed = checkViewports.filter((viewport) => {
8288
8332
  const route = { ...(viewport.route || {}), expected_path: expectedPath };
8289
8333
  route.matched = routePathMatches(route.observed, expectedPath, evidence.target_url) || route.matched;
8290
8334
  return !routeOk(route, evidence.target_url);
@@ -8295,8 +8339,8 @@ function assessProfile(profile, evidence) {
8295
8339
  status: failed.length ? "failed" : "passed",
8296
8340
  evidence: {
8297
8341
  expected_path: expectedPath,
8298
- observed_paths: viewports.map((viewport) => viewport.route && viewport.route.observed),
8299
- http_statuses: viewports.map((viewport) => viewport.route ? viewport.route.http_status ?? null : null),
8342
+ observed_paths: checkViewports.map((viewport) => viewport.route && viewport.route.observed),
8343
+ http_statuses: checkViewports.map((viewport) => viewport.route ? viewport.route.http_status ?? null : null),
8300
8344
  },
8301
8345
  message: failed.length ? "Route did not load as " + expectedPath + " in " + failed.length + " viewport(s)." : undefined,
8302
8346
  });
@@ -8304,24 +8348,24 @@ function assessProfile(profile, evidence) {
8304
8348
  }
8305
8349
  if (check.type === "selector_visible") {
8306
8350
  const selector = check.selector || "";
8307
- const failed = viewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].visible_count < 1);
8351
+ const failed = checkViewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].visible_count < 1);
8308
8352
  checks.push({
8309
8353
  type: check.type,
8310
8354
  label: check.label || check.type,
8311
8355
  status: failed.length ? "failed" : "passed",
8312
- evidence: { selector, visible_counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].visible_count : 0) },
8356
+ evidence: { selector, visible_counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].visible_count : 0) },
8313
8357
  message: failed.length ? "Selector " + selector + " was not visible in " + failed.length + " viewport(s)." : undefined,
8314
8358
  });
8315
8359
  continue;
8316
8360
  }
8317
8361
  if (check.type === "selector_absent") {
8318
8362
  const selector = check.selector || "";
8319
- const failed = viewports.filter((viewport) => viewport.selectors && viewport.selectors[selector] && viewport.selectors[selector].count > 0);
8363
+ const failed = checkViewports.filter((viewport) => viewport.selectors && viewport.selectors[selector] && viewport.selectors[selector].count > 0);
8320
8364
  checks.push({
8321
8365
  type: check.type,
8322
8366
  label: check.label || check.type,
8323
8367
  status: failed.length ? "failed" : "passed",
8324
- evidence: { selector, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
8368
+ evidence: { selector, counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
8325
8369
  message: failed.length ? "Selector " + selector + " was present in " + failed.length + " viewport(s)." : undefined,
8326
8370
  });
8327
8371
  continue;
@@ -8329,12 +8373,12 @@ function assessProfile(profile, evidence) {
8329
8373
  if (check.type === "selector_count_at_least") {
8330
8374
  const selector = check.selector || "";
8331
8375
  const minCount = check.min_count == null ? 1 : check.min_count;
8332
- const failed = viewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].count < minCount);
8376
+ const failed = checkViewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].count < minCount);
8333
8377
  checks.push({
8334
8378
  type: check.type,
8335
8379
  label: check.label || check.type,
8336
8380
  status: failed.length ? "failed" : "passed",
8337
- evidence: { selector, min_count: minCount, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
8381
+ evidence: { selector, min_count: minCount, counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
8338
8382
  message: failed.length ? "Selector " + selector + " count was below " + minCount + " in " + failed.length + " viewport(s)." : undefined,
8339
8383
  });
8340
8384
  continue;
@@ -8342,12 +8386,12 @@ function assessProfile(profile, evidence) {
8342
8386
  if (check.type === "selector_count_equals" || check.type === "selector_count_equal" || check.type === "selector_count_eq") {
8343
8387
  const selector = check.selector || "";
8344
8388
  const expectedCount = check.expected_count == null ? 0 : check.expected_count;
8345
- const failed = viewports.filter((viewport) => (viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) !== expectedCount);
8389
+ const failed = checkViewports.filter((viewport) => (viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) !== expectedCount);
8346
8390
  checks.push({
8347
8391
  type: check.type,
8348
8392
  label: check.label || check.type,
8349
8393
  status: failed.length ? "failed" : "passed",
8350
- evidence: { selector, expected_count: expectedCount, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
8394
+ evidence: { selector, expected_count: expectedCount, counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
8351
8395
  message: failed.length ? "Selector " + selector + " count did not equal " + expectedCount + " in " + failed.length + " viewport(s)." : undefined,
8352
8396
  });
8353
8397
  continue;
@@ -8355,7 +8399,7 @@ function assessProfile(profile, evidence) {
8355
8399
  if (check.type === "selector_text_order") {
8356
8400
  const selector = check.selector || "";
8357
8401
  const expectedTexts = check.expected_texts || [];
8358
- const results = viewports.map((viewport) => {
8402
+ const results = checkViewports.map((viewport) => {
8359
8403
  const texts = textSequenceForCheck(viewport, check);
8360
8404
  const match = textOrderMatch(texts, expectedTexts);
8361
8405
  return {
@@ -8377,7 +8421,7 @@ function assessProfile(profile, evidence) {
8377
8421
  }
8378
8422
  if (check.type === "frame_text_visible") {
8379
8423
  const selector = check.selector || "";
8380
- const results = viewports.map((viewport) => {
8424
+ const results = checkViewports.map((viewport) => {
8381
8425
  const frames = frameEvidenceForSelector(viewport, selector);
8382
8426
  const matches = frames
8383
8427
  .map((frame, index) => ({ index, frame, matched: textMatches(frameTextSample(frame), check) }))
@@ -8404,7 +8448,7 @@ function assessProfile(profile, evidence) {
8404
8448
  if (check.type === "frame_no_horizontal_overflow") {
8405
8449
  const selector = check.selector || "";
8406
8450
  const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
8407
- const results = viewports.map((viewport) => {
8451
+ const results = checkViewports.map((viewport) => {
8408
8452
  const frames = frameEvidenceForSelector(viewport, selector);
8409
8453
  const frameOverflows = frames.map((frame, index) => ({
8410
8454
  index,
@@ -8434,7 +8478,7 @@ function assessProfile(profile, evidence) {
8434
8478
  if (check.type === "text_visible" || check.type === "text_absent") {
8435
8479
  const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
8436
8480
  const expectedVisible = check.type === "text_visible";
8437
- const matches = viewports.map((viewport) => viewport.text_matches && typeof viewport.text_matches[key] === "boolean" ? viewport.text_matches[key] : textMatches(viewport.body_text_sample || "", check));
8481
+ const matches = checkViewports.map((viewport) => viewport.text_matches && typeof viewport.text_matches[key] === "boolean" ? viewport.text_matches[key] : textMatches(viewport.body_text_sample || "", check));
8438
8482
  const failed = matches.filter((matched) => matched !== expectedVisible).length;
8439
8483
  checks.push({
8440
8484
  type: check.type,
@@ -8446,7 +8490,7 @@ function assessProfile(profile, evidence) {
8446
8490
  continue;
8447
8491
  }
8448
8492
  if (check.type === "route_inventory") {
8449
- const inventories = viewports
8493
+ const inventories = checkViewports
8450
8494
  .map((viewport) => ({ viewport: viewport.name, inventory: viewport.route_inventory }))
8451
8495
  .filter((item) => item.inventory && typeof item.inventory === "object");
8452
8496
  if (!inventories.length) {
@@ -8498,7 +8542,7 @@ function assessProfile(profile, evidence) {
8498
8542
  }
8499
8543
  if (check.type === "no_horizontal_overflow" || check.type === "no_mobile_horizontal_overflow") {
8500
8544
  const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
8501
- const applicable = check.type === "no_mobile_horizontal_overflow" ? viewports.filter((viewport) => viewport.width <= 820) : viewports;
8545
+ const applicable = check.type === "no_mobile_horizontal_overflow" ? checkViewports.filter((viewport) => viewport.width <= 820) : checkViewports;
8502
8546
  if (!applicable.length) {
8503
8547
  checks.push({
8504
8548
  type: check.type,
@@ -8527,11 +8571,11 @@ function assessProfile(profile, evidence) {
8527
8571
  }
8528
8572
  if (check.type === "no_fatal_console_errors") {
8529
8573
  const fatalConsoleEvents = ((evidence.console && evidence.console.events) || []).filter((event) => event && (event.type === "error" || event.type === "assert"));
8530
- const allowedConsoleEvents = fatalConsoleEvents.filter((event) => matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
8531
- const unallowedConsoleEvents = fatalConsoleEvents.filter((event) => !matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
8574
+ const allowedConsoleEvents = fatalConsoleEvents.filter((event) => matchesAllowedMessage(event, check.allowed_console_texts, check.allowed_console_patterns));
8575
+ const unallowedConsoleEvents = fatalConsoleEvents.filter((event) => !matchesAllowedMessage(event, check.allowed_console_texts, check.allowed_console_patterns));
8532
8576
  const pageErrors = evidence.page_errors || [];
8533
- const allowedPageErrors = pageErrors.filter((error) => matchesAllowedMessage(error && error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
8534
- const unallowedPageErrors = pageErrors.filter((error) => !matchesAllowedMessage(error && error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
8577
+ const allowedPageErrors = pageErrors.filter((error) => matchesAllowedMessage(error, check.allowed_page_error_texts, check.allowed_page_error_patterns));
8578
+ const unallowedPageErrors = pageErrors.filter((error) => !matchesAllowedMessage(error, check.allowed_page_error_texts, check.allowed_page_error_patterns));
8535
8579
  const fatalCount = unallowedConsoleEvents.length + unallowedPageErrors.length;
8536
8580
  checks.push({
8537
8581
  type: check.type,
package/dist/cli.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  profileStatusExitCode,
11
11
  resolveRiddleProofProfileTargetUrl,
12
12
  resolveRiddleProofProfileTimeoutSec
13
- } from "./chunk-MT2OZCCP.js";
13
+ } from "./chunk-G673RKAC.js";
14
14
  import {
15
15
  createRiddleApiClient,
16
16
  parseRiddleViewport
package/dist/index.cjs CHANGED
@@ -9116,6 +9116,7 @@ function normalizeCheck(input, index) {
9116
9116
  text: stringValue5(input.text),
9117
9117
  pattern: stringValue5(input.pattern),
9118
9118
  flags: stringValue5(input.flags),
9119
+ viewports: normalizeStringList(input.viewports ?? input.viewport_names ?? input.viewportNames, `checks[${index}] viewports`),
9119
9120
  allowed_console_texts: normalizeStringList(input.allowed_console_texts ?? input.allowedConsoleTexts ?? input.allow_console_texts ?? input.allowConsoleTexts, `checks[${index}] allowed_console_texts`),
9120
9121
  allowed_console_patterns: normalizeStringList(input.allowed_console_patterns ?? input.allowedConsolePatterns ?? input.allow_console_patterns ?? input.allowConsolePatterns, `checks[${index}] allowed_console_patterns`),
9121
9122
  allowed_page_error_texts: normalizeStringList(input.allowed_page_error_texts ?? input.allowedPageErrorTexts ?? input.allow_page_error_texts ?? input.allowPageErrorTexts, `checks[${index}] allowed_page_error_texts`),
@@ -9284,8 +9285,17 @@ function matchText(sample, check) {
9284
9285
  }
9285
9286
  return sample.includes(check.text || "");
9286
9287
  }
9287
- function matchesAllowedMessage(message, texts, patterns) {
9288
- const sample = String(message || "");
9288
+ function allowedMessageSample(input) {
9289
+ if (!isRecord2(input)) return String(input || "");
9290
+ const parts = [
9291
+ input.text,
9292
+ input.message,
9293
+ isRecord2(input.location) ? input.location.url : void 0
9294
+ ];
9295
+ return parts.map((part) => String(part || "")).filter(Boolean).join(" ");
9296
+ }
9297
+ function matchesAllowedMessage(input, texts, patterns) {
9298
+ const sample = allowedMessageSample(input);
9289
9299
  if (texts?.some((text) => sample.includes(text))) return true;
9290
9300
  for (const pattern of patterns || []) {
9291
9301
  try {
@@ -9345,15 +9355,22 @@ function successfulRoute(route, targetUrl) {
9345
9355
  const matched = route.matched || routePathMatches(route.observed, route.expected_path, targetUrl);
9346
9356
  return matched && !route.error && (route.http_status === null || route.http_status === void 0 || route.http_status < 400);
9347
9357
  }
9358
+ function viewportsForCheck(check, viewports) {
9359
+ if (!check.viewports?.length) return viewports;
9360
+ const names = new Set(check.viewports);
9361
+ return viewports.filter((viewport) => names.has(viewport.name));
9362
+ }
9348
9363
  function assessCheckFromEvidence(check, evidence) {
9349
- const viewports = evidence.viewports || [];
9364
+ const viewports = viewportsForCheck(check, evidence.viewports || []);
9350
9365
  if (!viewports.length) {
9351
9366
  return {
9352
9367
  type: check.type,
9353
9368
  label: checkLabel(check),
9354
9369
  status: "failed",
9355
- evidence: {},
9356
- message: "No viewport evidence was captured."
9370
+ evidence: {
9371
+ expected_viewports: check.viewports || []
9372
+ },
9373
+ message: check.viewports?.length ? `No matching viewport evidence was captured for ${check.viewports.join(", ")}.` : "No viewport evidence was captured."
9357
9374
  };
9358
9375
  }
9359
9376
  if (check.type === "route_loaded") {
@@ -9613,11 +9630,11 @@ function assessCheckFromEvidence(check, evidence) {
9613
9630
  }
9614
9631
  if (check.type === "no_fatal_console_errors") {
9615
9632
  const fatalConsoleEvents = (evidence.console?.events || []).filter((event) => event.type === "error" || event.type === "assert");
9616
- const allowedConsoleEvents = fatalConsoleEvents.filter((event) => matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
9617
- const unallowedConsoleEvents = fatalConsoleEvents.filter((event) => !matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
9633
+ const allowedConsoleEvents = fatalConsoleEvents.filter((event) => matchesAllowedMessage(event, check.allowed_console_texts, check.allowed_console_patterns));
9634
+ const unallowedConsoleEvents = fatalConsoleEvents.filter((event) => !matchesAllowedMessage(event, check.allowed_console_texts, check.allowed_console_patterns));
9618
9635
  const pageErrors = evidence.page_errors || [];
9619
- const allowedPageErrors = pageErrors.filter((error) => matchesAllowedMessage(error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
9620
- const unallowedPageErrors = pageErrors.filter((error) => !matchesAllowedMessage(error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
9636
+ const allowedPageErrors = pageErrors.filter((error) => matchesAllowedMessage(error, check.allowed_page_error_texts, check.allowed_page_error_patterns));
9637
+ const unallowedPageErrors = pageErrors.filter((error) => !matchesAllowedMessage(error, check.allowed_page_error_texts, check.allowed_page_error_patterns));
9621
9638
  const fatalCount = unallowedConsoleEvents.length + unallowedPageErrors.length;
9622
9639
  return {
9623
9640
  type: check.type,
@@ -9902,8 +9919,17 @@ function textMatches(sample, check) {
9902
9919
  }
9903
9920
  return String(sample || "").includes(check.text || "");
9904
9921
  }
9905
- function matchesAllowedMessage(message, texts, patterns) {
9906
- const sample = String(message || "");
9922
+ function allowedMessageSample(input) {
9923
+ if (!input || typeof input !== "object" || Array.isArray(input)) return String(input || "");
9924
+ const parts = [
9925
+ input.text,
9926
+ input.message,
9927
+ input.location && typeof input.location === "object" ? input.location.url : undefined,
9928
+ ];
9929
+ return parts.map((part) => String(part || "")).filter(Boolean).join(" ");
9930
+ }
9931
+ function matchesAllowedMessage(input, texts, patterns) {
9932
+ const sample = allowedMessageSample(input);
9907
9933
  if ((texts || []).some((text) => sample.includes(text))) return true;
9908
9934
  for (const pattern of patterns || []) {
9909
9935
  try {
@@ -9949,6 +9975,11 @@ function frameTextSample(frame) {
9949
9975
  frame && frame.text,
9950
9976
  ].map((part) => String(part || "")).filter(Boolean).join(" ");
9951
9977
  }
9978
+ function viewportsForCheck(check, viewports) {
9979
+ if (!Array.isArray(check.viewports) || !check.viewports.length) return viewports;
9980
+ const names = new Set(check.viewports);
9981
+ return viewports.filter((viewport) => names.has(viewport.name));
9982
+ }
9952
9983
  function summarizeRouteInventory(viewport, inventory) {
9953
9984
  const directRoutes = Array.isArray(inventory.direct_routes) ? inventory.direct_routes : [];
9954
9985
  const clickthroughs = Array.isArray(inventory.clickthroughs) ? inventory.clickthroughs : [];
@@ -10139,9 +10170,22 @@ function assessProfile(profile, evidence) {
10139
10170
  });
10140
10171
  }
10141
10172
  for (const check of profile.checks || []) {
10173
+ const checkViewports = viewportsForCheck(check, viewports);
10174
+ if (!checkViewports.length) {
10175
+ checks.push({
10176
+ type: check.type,
10177
+ label: check.label || check.type,
10178
+ status: "failed",
10179
+ evidence: { expected_viewports: check.viewports || [] },
10180
+ message: Array.isArray(check.viewports) && check.viewports.length
10181
+ ? "No matching viewport evidence was captured for " + check.viewports.join(", ") + "."
10182
+ : "No viewport evidence was captured.",
10183
+ });
10184
+ continue;
10185
+ }
10142
10186
  if (check.type === "route_loaded") {
10143
10187
  const expectedPath = check.expected_path || new URL(evidence.target_url).pathname || "/";
10144
- const failed = viewports.filter((viewport) => {
10188
+ const failed = checkViewports.filter((viewport) => {
10145
10189
  const route = { ...(viewport.route || {}), expected_path: expectedPath };
10146
10190
  route.matched = routePathMatches(route.observed, expectedPath, evidence.target_url) || route.matched;
10147
10191
  return !routeOk(route, evidence.target_url);
@@ -10152,8 +10196,8 @@ function assessProfile(profile, evidence) {
10152
10196
  status: failed.length ? "failed" : "passed",
10153
10197
  evidence: {
10154
10198
  expected_path: expectedPath,
10155
- observed_paths: viewports.map((viewport) => viewport.route && viewport.route.observed),
10156
- http_statuses: viewports.map((viewport) => viewport.route ? viewport.route.http_status ?? null : null),
10199
+ observed_paths: checkViewports.map((viewport) => viewport.route && viewport.route.observed),
10200
+ http_statuses: checkViewports.map((viewport) => viewport.route ? viewport.route.http_status ?? null : null),
10157
10201
  },
10158
10202
  message: failed.length ? "Route did not load as " + expectedPath + " in " + failed.length + " viewport(s)." : undefined,
10159
10203
  });
@@ -10161,24 +10205,24 @@ function assessProfile(profile, evidence) {
10161
10205
  }
10162
10206
  if (check.type === "selector_visible") {
10163
10207
  const selector = check.selector || "";
10164
- const failed = viewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].visible_count < 1);
10208
+ const failed = checkViewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].visible_count < 1);
10165
10209
  checks.push({
10166
10210
  type: check.type,
10167
10211
  label: check.label || check.type,
10168
10212
  status: failed.length ? "failed" : "passed",
10169
- evidence: { selector, visible_counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].visible_count : 0) },
10213
+ evidence: { selector, visible_counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].visible_count : 0) },
10170
10214
  message: failed.length ? "Selector " + selector + " was not visible in " + failed.length + " viewport(s)." : undefined,
10171
10215
  });
10172
10216
  continue;
10173
10217
  }
10174
10218
  if (check.type === "selector_absent") {
10175
10219
  const selector = check.selector || "";
10176
- const failed = viewports.filter((viewport) => viewport.selectors && viewport.selectors[selector] && viewport.selectors[selector].count > 0);
10220
+ const failed = checkViewports.filter((viewport) => viewport.selectors && viewport.selectors[selector] && viewport.selectors[selector].count > 0);
10177
10221
  checks.push({
10178
10222
  type: check.type,
10179
10223
  label: check.label || check.type,
10180
10224
  status: failed.length ? "failed" : "passed",
10181
- evidence: { selector, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
10225
+ evidence: { selector, counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
10182
10226
  message: failed.length ? "Selector " + selector + " was present in " + failed.length + " viewport(s)." : undefined,
10183
10227
  });
10184
10228
  continue;
@@ -10186,12 +10230,12 @@ function assessProfile(profile, evidence) {
10186
10230
  if (check.type === "selector_count_at_least") {
10187
10231
  const selector = check.selector || "";
10188
10232
  const minCount = check.min_count == null ? 1 : check.min_count;
10189
- const failed = viewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].count < minCount);
10233
+ const failed = checkViewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].count < minCount);
10190
10234
  checks.push({
10191
10235
  type: check.type,
10192
10236
  label: check.label || check.type,
10193
10237
  status: failed.length ? "failed" : "passed",
10194
- evidence: { selector, min_count: minCount, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
10238
+ evidence: { selector, min_count: minCount, counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
10195
10239
  message: failed.length ? "Selector " + selector + " count was below " + minCount + " in " + failed.length + " viewport(s)." : undefined,
10196
10240
  });
10197
10241
  continue;
@@ -10199,12 +10243,12 @@ function assessProfile(profile, evidence) {
10199
10243
  if (check.type === "selector_count_equals" || check.type === "selector_count_equal" || check.type === "selector_count_eq") {
10200
10244
  const selector = check.selector || "";
10201
10245
  const expectedCount = check.expected_count == null ? 0 : check.expected_count;
10202
- const failed = viewports.filter((viewport) => (viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) !== expectedCount);
10246
+ const failed = checkViewports.filter((viewport) => (viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) !== expectedCount);
10203
10247
  checks.push({
10204
10248
  type: check.type,
10205
10249
  label: check.label || check.type,
10206
10250
  status: failed.length ? "failed" : "passed",
10207
- evidence: { selector, expected_count: expectedCount, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
10251
+ evidence: { selector, expected_count: expectedCount, counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
10208
10252
  message: failed.length ? "Selector " + selector + " count did not equal " + expectedCount + " in " + failed.length + " viewport(s)." : undefined,
10209
10253
  });
10210
10254
  continue;
@@ -10212,7 +10256,7 @@ function assessProfile(profile, evidence) {
10212
10256
  if (check.type === "selector_text_order") {
10213
10257
  const selector = check.selector || "";
10214
10258
  const expectedTexts = check.expected_texts || [];
10215
- const results = viewports.map((viewport) => {
10259
+ const results = checkViewports.map((viewport) => {
10216
10260
  const texts = textSequenceForCheck(viewport, check);
10217
10261
  const match = textOrderMatch(texts, expectedTexts);
10218
10262
  return {
@@ -10234,7 +10278,7 @@ function assessProfile(profile, evidence) {
10234
10278
  }
10235
10279
  if (check.type === "frame_text_visible") {
10236
10280
  const selector = check.selector || "";
10237
- const results = viewports.map((viewport) => {
10281
+ const results = checkViewports.map((viewport) => {
10238
10282
  const frames = frameEvidenceForSelector(viewport, selector);
10239
10283
  const matches = frames
10240
10284
  .map((frame, index) => ({ index, frame, matched: textMatches(frameTextSample(frame), check) }))
@@ -10261,7 +10305,7 @@ function assessProfile(profile, evidence) {
10261
10305
  if (check.type === "frame_no_horizontal_overflow") {
10262
10306
  const selector = check.selector || "";
10263
10307
  const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
10264
- const results = viewports.map((viewport) => {
10308
+ const results = checkViewports.map((viewport) => {
10265
10309
  const frames = frameEvidenceForSelector(viewport, selector);
10266
10310
  const frameOverflows = frames.map((frame, index) => ({
10267
10311
  index,
@@ -10291,7 +10335,7 @@ function assessProfile(profile, evidence) {
10291
10335
  if (check.type === "text_visible" || check.type === "text_absent") {
10292
10336
  const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
10293
10337
  const expectedVisible = check.type === "text_visible";
10294
- const matches = viewports.map((viewport) => viewport.text_matches && typeof viewport.text_matches[key] === "boolean" ? viewport.text_matches[key] : textMatches(viewport.body_text_sample || "", check));
10338
+ const matches = checkViewports.map((viewport) => viewport.text_matches && typeof viewport.text_matches[key] === "boolean" ? viewport.text_matches[key] : textMatches(viewport.body_text_sample || "", check));
10295
10339
  const failed = matches.filter((matched) => matched !== expectedVisible).length;
10296
10340
  checks.push({
10297
10341
  type: check.type,
@@ -10303,7 +10347,7 @@ function assessProfile(profile, evidence) {
10303
10347
  continue;
10304
10348
  }
10305
10349
  if (check.type === "route_inventory") {
10306
- const inventories = viewports
10350
+ const inventories = checkViewports
10307
10351
  .map((viewport) => ({ viewport: viewport.name, inventory: viewport.route_inventory }))
10308
10352
  .filter((item) => item.inventory && typeof item.inventory === "object");
10309
10353
  if (!inventories.length) {
@@ -10355,7 +10399,7 @@ function assessProfile(profile, evidence) {
10355
10399
  }
10356
10400
  if (check.type === "no_horizontal_overflow" || check.type === "no_mobile_horizontal_overflow") {
10357
10401
  const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
10358
- const applicable = check.type === "no_mobile_horizontal_overflow" ? viewports.filter((viewport) => viewport.width <= 820) : viewports;
10402
+ const applicable = check.type === "no_mobile_horizontal_overflow" ? checkViewports.filter((viewport) => viewport.width <= 820) : checkViewports;
10359
10403
  if (!applicable.length) {
10360
10404
  checks.push({
10361
10405
  type: check.type,
@@ -10384,11 +10428,11 @@ function assessProfile(profile, evidence) {
10384
10428
  }
10385
10429
  if (check.type === "no_fatal_console_errors") {
10386
10430
  const fatalConsoleEvents = ((evidence.console && evidence.console.events) || []).filter((event) => event && (event.type === "error" || event.type === "assert"));
10387
- const allowedConsoleEvents = fatalConsoleEvents.filter((event) => matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
10388
- const unallowedConsoleEvents = fatalConsoleEvents.filter((event) => !matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
10431
+ const allowedConsoleEvents = fatalConsoleEvents.filter((event) => matchesAllowedMessage(event, check.allowed_console_texts, check.allowed_console_patterns));
10432
+ const unallowedConsoleEvents = fatalConsoleEvents.filter((event) => !matchesAllowedMessage(event, check.allowed_console_texts, check.allowed_console_patterns));
10389
10433
  const pageErrors = evidence.page_errors || [];
10390
- const allowedPageErrors = pageErrors.filter((error) => matchesAllowedMessage(error && error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
10391
- const unallowedPageErrors = pageErrors.filter((error) => !matchesAllowedMessage(error && error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
10434
+ const allowedPageErrors = pageErrors.filter((error) => matchesAllowedMessage(error, check.allowed_page_error_texts, check.allowed_page_error_patterns));
10435
+ const unallowedPageErrors = pageErrors.filter((error) => !matchesAllowedMessage(error, check.allowed_page_error_texts, check.allowed_page_error_patterns));
10392
10436
  const fatalCount = unallowedConsoleEvents.length + unallowedPageErrors.length;
10393
10437
  checks.push({
10394
10438
  type: check.type,
package/dist/index.js CHANGED
@@ -58,7 +58,7 @@ import {
58
58
  resolveRiddleProofProfileTimeoutSec,
59
59
  slugifyRiddleProofProfileName,
60
60
  summarizeRiddleProofProfileResult
61
- } from "./chunk-MT2OZCCP.js";
61
+ } from "./chunk-G673RKAC.js";
62
62
  import {
63
63
  DEFAULT_RIDDLE_API_BASE_URL,
64
64
  DEFAULT_RIDDLE_API_KEY_FILE,
package/dist/profile.cjs CHANGED
@@ -445,6 +445,7 @@ function normalizeCheck(input, index) {
445
445
  text: stringValue(input.text),
446
446
  pattern: stringValue(input.pattern),
447
447
  flags: stringValue(input.flags),
448
+ viewports: normalizeStringList(input.viewports ?? input.viewport_names ?? input.viewportNames, `checks[${index}] viewports`),
448
449
  allowed_console_texts: normalizeStringList(input.allowed_console_texts ?? input.allowedConsoleTexts ?? input.allow_console_texts ?? input.allowConsoleTexts, `checks[${index}] allowed_console_texts`),
449
450
  allowed_console_patterns: normalizeStringList(input.allowed_console_patterns ?? input.allowedConsolePatterns ?? input.allow_console_patterns ?? input.allowConsolePatterns, `checks[${index}] allowed_console_patterns`),
450
451
  allowed_page_error_texts: normalizeStringList(input.allowed_page_error_texts ?? input.allowedPageErrorTexts ?? input.allow_page_error_texts ?? input.allowPageErrorTexts, `checks[${index}] allowed_page_error_texts`),
@@ -613,8 +614,17 @@ function matchText(sample, check) {
613
614
  }
614
615
  return sample.includes(check.text || "");
615
616
  }
616
- function matchesAllowedMessage(message, texts, patterns) {
617
- const sample = String(message || "");
617
+ function allowedMessageSample(input) {
618
+ if (!isRecord(input)) return String(input || "");
619
+ const parts = [
620
+ input.text,
621
+ input.message,
622
+ isRecord(input.location) ? input.location.url : void 0
623
+ ];
624
+ return parts.map((part) => String(part || "")).filter(Boolean).join(" ");
625
+ }
626
+ function matchesAllowedMessage(input, texts, patterns) {
627
+ const sample = allowedMessageSample(input);
618
628
  if (texts?.some((text) => sample.includes(text))) return true;
619
629
  for (const pattern of patterns || []) {
620
630
  try {
@@ -674,15 +684,22 @@ function successfulRoute(route, targetUrl) {
674
684
  const matched = route.matched || routePathMatches(route.observed, route.expected_path, targetUrl);
675
685
  return matched && !route.error && (route.http_status === null || route.http_status === void 0 || route.http_status < 400);
676
686
  }
687
+ function viewportsForCheck(check, viewports) {
688
+ if (!check.viewports?.length) return viewports;
689
+ const names = new Set(check.viewports);
690
+ return viewports.filter((viewport) => names.has(viewport.name));
691
+ }
677
692
  function assessCheckFromEvidence(check, evidence) {
678
- const viewports = evidence.viewports || [];
693
+ const viewports = viewportsForCheck(check, evidence.viewports || []);
679
694
  if (!viewports.length) {
680
695
  return {
681
696
  type: check.type,
682
697
  label: checkLabel(check),
683
698
  status: "failed",
684
- evidence: {},
685
- message: "No viewport evidence was captured."
699
+ evidence: {
700
+ expected_viewports: check.viewports || []
701
+ },
702
+ message: check.viewports?.length ? `No matching viewport evidence was captured for ${check.viewports.join(", ")}.` : "No viewport evidence was captured."
686
703
  };
687
704
  }
688
705
  if (check.type === "route_loaded") {
@@ -942,11 +959,11 @@ function assessCheckFromEvidence(check, evidence) {
942
959
  }
943
960
  if (check.type === "no_fatal_console_errors") {
944
961
  const fatalConsoleEvents = (evidence.console?.events || []).filter((event) => event.type === "error" || event.type === "assert");
945
- const allowedConsoleEvents = fatalConsoleEvents.filter((event) => matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
946
- const unallowedConsoleEvents = fatalConsoleEvents.filter((event) => !matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
962
+ const allowedConsoleEvents = fatalConsoleEvents.filter((event) => matchesAllowedMessage(event, check.allowed_console_texts, check.allowed_console_patterns));
963
+ const unallowedConsoleEvents = fatalConsoleEvents.filter((event) => !matchesAllowedMessage(event, check.allowed_console_texts, check.allowed_console_patterns));
947
964
  const pageErrors = evidence.page_errors || [];
948
- const allowedPageErrors = pageErrors.filter((error) => matchesAllowedMessage(error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
949
- const unallowedPageErrors = pageErrors.filter((error) => !matchesAllowedMessage(error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
965
+ const allowedPageErrors = pageErrors.filter((error) => matchesAllowedMessage(error, check.allowed_page_error_texts, check.allowed_page_error_patterns));
966
+ const unallowedPageErrors = pageErrors.filter((error) => !matchesAllowedMessage(error, check.allowed_page_error_texts, check.allowed_page_error_patterns));
950
967
  const fatalCount = unallowedConsoleEvents.length + unallowedPageErrors.length;
951
968
  return {
952
969
  type: check.type,
@@ -1231,8 +1248,17 @@ function textMatches(sample, check) {
1231
1248
  }
1232
1249
  return String(sample || "").includes(check.text || "");
1233
1250
  }
1234
- function matchesAllowedMessage(message, texts, patterns) {
1235
- const sample = String(message || "");
1251
+ function allowedMessageSample(input) {
1252
+ if (!input || typeof input !== "object" || Array.isArray(input)) return String(input || "");
1253
+ const parts = [
1254
+ input.text,
1255
+ input.message,
1256
+ input.location && typeof input.location === "object" ? input.location.url : undefined,
1257
+ ];
1258
+ return parts.map((part) => String(part || "")).filter(Boolean).join(" ");
1259
+ }
1260
+ function matchesAllowedMessage(input, texts, patterns) {
1261
+ const sample = allowedMessageSample(input);
1236
1262
  if ((texts || []).some((text) => sample.includes(text))) return true;
1237
1263
  for (const pattern of patterns || []) {
1238
1264
  try {
@@ -1278,6 +1304,11 @@ function frameTextSample(frame) {
1278
1304
  frame && frame.text,
1279
1305
  ].map((part) => String(part || "")).filter(Boolean).join(" ");
1280
1306
  }
1307
+ function viewportsForCheck(check, viewports) {
1308
+ if (!Array.isArray(check.viewports) || !check.viewports.length) return viewports;
1309
+ const names = new Set(check.viewports);
1310
+ return viewports.filter((viewport) => names.has(viewport.name));
1311
+ }
1281
1312
  function summarizeRouteInventory(viewport, inventory) {
1282
1313
  const directRoutes = Array.isArray(inventory.direct_routes) ? inventory.direct_routes : [];
1283
1314
  const clickthroughs = Array.isArray(inventory.clickthroughs) ? inventory.clickthroughs : [];
@@ -1468,9 +1499,22 @@ function assessProfile(profile, evidence) {
1468
1499
  });
1469
1500
  }
1470
1501
  for (const check of profile.checks || []) {
1502
+ const checkViewports = viewportsForCheck(check, viewports);
1503
+ if (!checkViewports.length) {
1504
+ checks.push({
1505
+ type: check.type,
1506
+ label: check.label || check.type,
1507
+ status: "failed",
1508
+ evidence: { expected_viewports: check.viewports || [] },
1509
+ message: Array.isArray(check.viewports) && check.viewports.length
1510
+ ? "No matching viewport evidence was captured for " + check.viewports.join(", ") + "."
1511
+ : "No viewport evidence was captured.",
1512
+ });
1513
+ continue;
1514
+ }
1471
1515
  if (check.type === "route_loaded") {
1472
1516
  const expectedPath = check.expected_path || new URL(evidence.target_url).pathname || "/";
1473
- const failed = viewports.filter((viewport) => {
1517
+ const failed = checkViewports.filter((viewport) => {
1474
1518
  const route = { ...(viewport.route || {}), expected_path: expectedPath };
1475
1519
  route.matched = routePathMatches(route.observed, expectedPath, evidence.target_url) || route.matched;
1476
1520
  return !routeOk(route, evidence.target_url);
@@ -1481,8 +1525,8 @@ function assessProfile(profile, evidence) {
1481
1525
  status: failed.length ? "failed" : "passed",
1482
1526
  evidence: {
1483
1527
  expected_path: expectedPath,
1484
- observed_paths: viewports.map((viewport) => viewport.route && viewport.route.observed),
1485
- http_statuses: viewports.map((viewport) => viewport.route ? viewport.route.http_status ?? null : null),
1528
+ observed_paths: checkViewports.map((viewport) => viewport.route && viewport.route.observed),
1529
+ http_statuses: checkViewports.map((viewport) => viewport.route ? viewport.route.http_status ?? null : null),
1486
1530
  },
1487
1531
  message: failed.length ? "Route did not load as " + expectedPath + " in " + failed.length + " viewport(s)." : undefined,
1488
1532
  });
@@ -1490,24 +1534,24 @@ function assessProfile(profile, evidence) {
1490
1534
  }
1491
1535
  if (check.type === "selector_visible") {
1492
1536
  const selector = check.selector || "";
1493
- const failed = viewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].visible_count < 1);
1537
+ const failed = checkViewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].visible_count < 1);
1494
1538
  checks.push({
1495
1539
  type: check.type,
1496
1540
  label: check.label || check.type,
1497
1541
  status: failed.length ? "failed" : "passed",
1498
- evidence: { selector, visible_counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].visible_count : 0) },
1542
+ evidence: { selector, visible_counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].visible_count : 0) },
1499
1543
  message: failed.length ? "Selector " + selector + " was not visible in " + failed.length + " viewport(s)." : undefined,
1500
1544
  });
1501
1545
  continue;
1502
1546
  }
1503
1547
  if (check.type === "selector_absent") {
1504
1548
  const selector = check.selector || "";
1505
- const failed = viewports.filter((viewport) => viewport.selectors && viewport.selectors[selector] && viewport.selectors[selector].count > 0);
1549
+ const failed = checkViewports.filter((viewport) => viewport.selectors && viewport.selectors[selector] && viewport.selectors[selector].count > 0);
1506
1550
  checks.push({
1507
1551
  type: check.type,
1508
1552
  label: check.label || check.type,
1509
1553
  status: failed.length ? "failed" : "passed",
1510
- evidence: { selector, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
1554
+ evidence: { selector, counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
1511
1555
  message: failed.length ? "Selector " + selector + " was present in " + failed.length + " viewport(s)." : undefined,
1512
1556
  });
1513
1557
  continue;
@@ -1515,12 +1559,12 @@ function assessProfile(profile, evidence) {
1515
1559
  if (check.type === "selector_count_at_least") {
1516
1560
  const selector = check.selector || "";
1517
1561
  const minCount = check.min_count == null ? 1 : check.min_count;
1518
- const failed = viewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].count < minCount);
1562
+ const failed = checkViewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].count < minCount);
1519
1563
  checks.push({
1520
1564
  type: check.type,
1521
1565
  label: check.label || check.type,
1522
1566
  status: failed.length ? "failed" : "passed",
1523
- evidence: { selector, min_count: minCount, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
1567
+ evidence: { selector, min_count: minCount, counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
1524
1568
  message: failed.length ? "Selector " + selector + " count was below " + minCount + " in " + failed.length + " viewport(s)." : undefined,
1525
1569
  });
1526
1570
  continue;
@@ -1528,12 +1572,12 @@ function assessProfile(profile, evidence) {
1528
1572
  if (check.type === "selector_count_equals" || check.type === "selector_count_equal" || check.type === "selector_count_eq") {
1529
1573
  const selector = check.selector || "";
1530
1574
  const expectedCount = check.expected_count == null ? 0 : check.expected_count;
1531
- const failed = viewports.filter((viewport) => (viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) !== expectedCount);
1575
+ const failed = checkViewports.filter((viewport) => (viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) !== expectedCount);
1532
1576
  checks.push({
1533
1577
  type: check.type,
1534
1578
  label: check.label || check.type,
1535
1579
  status: failed.length ? "failed" : "passed",
1536
- evidence: { selector, expected_count: expectedCount, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
1580
+ evidence: { selector, expected_count: expectedCount, counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
1537
1581
  message: failed.length ? "Selector " + selector + " count did not equal " + expectedCount + " in " + failed.length + " viewport(s)." : undefined,
1538
1582
  });
1539
1583
  continue;
@@ -1541,7 +1585,7 @@ function assessProfile(profile, evidence) {
1541
1585
  if (check.type === "selector_text_order") {
1542
1586
  const selector = check.selector || "";
1543
1587
  const expectedTexts = check.expected_texts || [];
1544
- const results = viewports.map((viewport) => {
1588
+ const results = checkViewports.map((viewport) => {
1545
1589
  const texts = textSequenceForCheck(viewport, check);
1546
1590
  const match = textOrderMatch(texts, expectedTexts);
1547
1591
  return {
@@ -1563,7 +1607,7 @@ function assessProfile(profile, evidence) {
1563
1607
  }
1564
1608
  if (check.type === "frame_text_visible") {
1565
1609
  const selector = check.selector || "";
1566
- const results = viewports.map((viewport) => {
1610
+ const results = checkViewports.map((viewport) => {
1567
1611
  const frames = frameEvidenceForSelector(viewport, selector);
1568
1612
  const matches = frames
1569
1613
  .map((frame, index) => ({ index, frame, matched: textMatches(frameTextSample(frame), check) }))
@@ -1590,7 +1634,7 @@ function assessProfile(profile, evidence) {
1590
1634
  if (check.type === "frame_no_horizontal_overflow") {
1591
1635
  const selector = check.selector || "";
1592
1636
  const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
1593
- const results = viewports.map((viewport) => {
1637
+ const results = checkViewports.map((viewport) => {
1594
1638
  const frames = frameEvidenceForSelector(viewport, selector);
1595
1639
  const frameOverflows = frames.map((frame, index) => ({
1596
1640
  index,
@@ -1620,7 +1664,7 @@ function assessProfile(profile, evidence) {
1620
1664
  if (check.type === "text_visible" || check.type === "text_absent") {
1621
1665
  const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
1622
1666
  const expectedVisible = check.type === "text_visible";
1623
- const matches = viewports.map((viewport) => viewport.text_matches && typeof viewport.text_matches[key] === "boolean" ? viewport.text_matches[key] : textMatches(viewport.body_text_sample || "", check));
1667
+ const matches = checkViewports.map((viewport) => viewport.text_matches && typeof viewport.text_matches[key] === "boolean" ? viewport.text_matches[key] : textMatches(viewport.body_text_sample || "", check));
1624
1668
  const failed = matches.filter((matched) => matched !== expectedVisible).length;
1625
1669
  checks.push({
1626
1670
  type: check.type,
@@ -1632,7 +1676,7 @@ function assessProfile(profile, evidence) {
1632
1676
  continue;
1633
1677
  }
1634
1678
  if (check.type === "route_inventory") {
1635
- const inventories = viewports
1679
+ const inventories = checkViewports
1636
1680
  .map((viewport) => ({ viewport: viewport.name, inventory: viewport.route_inventory }))
1637
1681
  .filter((item) => item.inventory && typeof item.inventory === "object");
1638
1682
  if (!inventories.length) {
@@ -1684,7 +1728,7 @@ function assessProfile(profile, evidence) {
1684
1728
  }
1685
1729
  if (check.type === "no_horizontal_overflow" || check.type === "no_mobile_horizontal_overflow") {
1686
1730
  const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
1687
- const applicable = check.type === "no_mobile_horizontal_overflow" ? viewports.filter((viewport) => viewport.width <= 820) : viewports;
1731
+ const applicable = check.type === "no_mobile_horizontal_overflow" ? checkViewports.filter((viewport) => viewport.width <= 820) : checkViewports;
1688
1732
  if (!applicable.length) {
1689
1733
  checks.push({
1690
1734
  type: check.type,
@@ -1713,11 +1757,11 @@ function assessProfile(profile, evidence) {
1713
1757
  }
1714
1758
  if (check.type === "no_fatal_console_errors") {
1715
1759
  const fatalConsoleEvents = ((evidence.console && evidence.console.events) || []).filter((event) => event && (event.type === "error" || event.type === "assert"));
1716
- const allowedConsoleEvents = fatalConsoleEvents.filter((event) => matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
1717
- const unallowedConsoleEvents = fatalConsoleEvents.filter((event) => !matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
1760
+ const allowedConsoleEvents = fatalConsoleEvents.filter((event) => matchesAllowedMessage(event, check.allowed_console_texts, check.allowed_console_patterns));
1761
+ const unallowedConsoleEvents = fatalConsoleEvents.filter((event) => !matchesAllowedMessage(event, check.allowed_console_texts, check.allowed_console_patterns));
1718
1762
  const pageErrors = evidence.page_errors || [];
1719
- const allowedPageErrors = pageErrors.filter((error) => matchesAllowedMessage(error && error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
1720
- const unallowedPageErrors = pageErrors.filter((error) => !matchesAllowedMessage(error && error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
1763
+ const allowedPageErrors = pageErrors.filter((error) => matchesAllowedMessage(error, check.allowed_page_error_texts, check.allowed_page_error_patterns));
1764
+ const unallowedPageErrors = pageErrors.filter((error) => !matchesAllowedMessage(error, check.allowed_page_error_texts, check.allowed_page_error_patterns));
1721
1765
  const fatalCount = unallowedConsoleEvents.length + unallowedPageErrors.length;
1722
1766
  checks.push({
1723
1767
  type: check.type,
@@ -83,6 +83,7 @@ interface RiddleProofProfileCheck {
83
83
  text?: string;
84
84
  pattern?: string;
85
85
  flags?: string;
86
+ viewports?: string[];
86
87
  allowed_console_texts?: string[];
87
88
  allowed_console_patterns?: string[];
88
89
  allowed_page_error_texts?: string[];
package/dist/profile.d.ts CHANGED
@@ -83,6 +83,7 @@ interface RiddleProofProfileCheck {
83
83
  text?: string;
84
84
  pattern?: string;
85
85
  flags?: string;
86
+ viewports?: string[];
86
87
  allowed_console_texts?: string[];
87
88
  allowed_console_patterns?: string[];
88
89
  allowed_page_error_texts?: string[];
package/dist/profile.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  resolveRiddleProofProfileTimeoutSec,
20
20
  slugifyRiddleProofProfileName,
21
21
  summarizeRiddleProofProfileResult
22
- } from "./chunk-MT2OZCCP.js";
22
+ } from "./chunk-G673RKAC.js";
23
23
  export {
24
24
  RIDDLE_PROOF_PROFILE_CHECK_TYPES,
25
25
  RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,
@@ -292,7 +292,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
292
292
  blocking?: boolean;
293
293
  details?: Record<string, unknown>;
294
294
  ok: boolean;
295
- action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
295
+ action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
296
296
  state_path: string;
297
297
  stage: any;
298
298
  summary: string;
@@ -382,7 +382,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
382
382
  continueWithStage?: WorkflowStage | null;
383
383
  blocking?: boolean;
384
384
  details?: Record<string, unknown>;
385
- action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
385
+ action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
386
386
  state_path: string;
387
387
  stage: any;
388
388
  checkpoint: string;
@@ -659,7 +659,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
659
659
  error?: undefined;
660
660
  } | {
661
661
  ok: boolean;
662
- action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
662
+ action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
663
663
  state_path: string;
664
664
  stage: any;
665
665
  summary: string;
@@ -292,7 +292,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
292
292
  blocking?: boolean;
293
293
  details?: Record<string, unknown>;
294
294
  ok: boolean;
295
- action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
295
+ action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
296
296
  state_path: string;
297
297
  stage: any;
298
298
  summary: string;
@@ -382,7 +382,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
382
382
  continueWithStage?: WorkflowStage | null;
383
383
  blocking?: boolean;
384
384
  details?: Record<string, unknown>;
385
- action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
385
+ action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
386
386
  state_path: string;
387
387
  stage: any;
388
388
  checkpoint: string;
@@ -659,7 +659,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
659
659
  error?: undefined;
660
660
  } | {
661
661
  ok: boolean;
662
- action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
662
+ action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
663
663
  state_path: string;
664
664
  stage: any;
665
665
  summary: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riddledc/riddle-proof",
3
- "version": "0.7.42",
3
+ "version": "0.7.44",
4
4
  "description": "Reusable Riddle Proof contracts and helpers for evidence-backed agent changes.",
5
5
  "license": "MIT",
6
6
  "author": "RiddleDC",