@riddledc/riddle-proof 0.7.36 → 0.7.38

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/dist/profile.cjs CHANGED
@@ -58,6 +58,8 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
58
58
  "selector_visible",
59
59
  "selector_count_at_least",
60
60
  "selector_text_order",
61
+ "frame_text_visible",
62
+ "frame_no_horizontal_overflow",
61
63
  "text_visible",
62
64
  "text_absent",
63
65
  "route_inventory",
@@ -356,6 +358,12 @@ function normalizeCheck(input, index) {
356
358
  if ((type === "selector_visible" || type === "selector_count_at_least") && !stringValue(input.selector)) {
357
359
  throw new Error(`checks[${index}] ${type} requires selector.`);
358
360
  }
361
+ if ((type === "frame_text_visible" || type === "frame_no_horizontal_overflow") && !stringValue(input.selector)) {
362
+ throw new Error(`checks[${index}] ${type} requires selector.`);
363
+ }
364
+ if (type === "frame_text_visible" && !stringValue(input.text) && !stringValue(input.pattern)) {
365
+ throw new Error(`checks[${index}] frame_text_visible requires text or pattern.`);
366
+ }
359
367
  if ((type === "text_visible" || type === "text_absent") && !stringValue(input.text) && !stringValue(input.pattern)) {
360
368
  throw new Error(`checks[${index}] ${type} requires text or pattern.`);
361
369
  }
@@ -506,6 +514,22 @@ function textOrderMatch(texts, expectedTexts) {
506
514
  }
507
515
  return { matched: true, positions };
508
516
  }
517
+ function frameEvidenceForSelector(viewport, selector) {
518
+ const container = viewport.frames?.[selector];
519
+ if (!isRecord(container)) return [];
520
+ const frames = Array.isArray(container.frames) ? container.frames : [];
521
+ return frames.filter((frame) => isRecord(frame));
522
+ }
523
+ function frameTextSample(frame) {
524
+ const parts = [
525
+ frame.title,
526
+ frame.text_sample,
527
+ frame.body_text_sample,
528
+ frame.body_text,
529
+ frame.text
530
+ ];
531
+ return parts.map((part) => String(part || "")).filter(Boolean).join(" ");
532
+ }
509
533
  function summarizeRouteInventory(viewport, inventory) {
510
534
  const directRoutes = Array.isArray(inventory.direct_routes) ? inventory.direct_routes : [];
511
535
  const clickthroughs = Array.isArray(inventory.clickthroughs) ? inventory.clickthroughs : [];
@@ -670,6 +694,67 @@ function assessCheckFromEvidence(check, evidence) {
670
694
  message: failed ? `Selector ${key} text order failed in ${failed} viewport(s).` : void 0
671
695
  };
672
696
  }
697
+ if (check.type === "frame_text_visible") {
698
+ const key = selectorKey(check);
699
+ const results = viewports.map((viewport) => {
700
+ const frames = frameEvidenceForSelector(viewport, key);
701
+ const matches = frames.map((frame, index) => ({ index, frame, matched: matchText(frameTextSample(frame), check) })).filter((item) => item.matched);
702
+ return {
703
+ viewport: viewport.name,
704
+ frame_count: frames.length,
705
+ matched_count: matches.length,
706
+ matched: matches.length > 0,
707
+ urls: frames.map((frame) => String(frame.url || "")).filter(Boolean).slice(0, 10),
708
+ samples: matches.map((item) => frameTextSample(item.frame).replace(/\s+/g, " ").trim().slice(0, 240)).slice(0, 3)
709
+ };
710
+ });
711
+ const failed = results.filter((result) => !result.matched).length;
712
+ return {
713
+ type: check.type,
714
+ label: checkLabel(check),
715
+ status: failed ? "failed" : "passed",
716
+ evidence: {
717
+ selector: key,
718
+ text: check.text || null,
719
+ pattern: check.pattern || null,
720
+ viewports: results.map((result) => toJsonValue(result))
721
+ },
722
+ message: failed ? `Frame selector ${key} did not contain expected text in ${failed} viewport(s).` : void 0
723
+ };
724
+ }
725
+ if (check.type === "frame_no_horizontal_overflow") {
726
+ const key = selectorKey(check);
727
+ const maxOverflow = check.max_overflow_px ?? 4;
728
+ const results = viewports.map((viewport) => {
729
+ const frames = frameEvidenceForSelector(viewport, key);
730
+ const frameOverflows = frames.map((frame, index) => ({
731
+ index,
732
+ url: String(frame.url || ""),
733
+ overflow_px: horizontalBoundsOverflowPx(frame),
734
+ offender_count: boundsOffendersForEvidence(frame).length
735
+ }));
736
+ const maxFrameOverflow = frameOverflows.reduce((max, frame) => Math.max(max, frame.overflow_px), 0);
737
+ return {
738
+ viewport: viewport.name,
739
+ frame_count: frames.length,
740
+ max_overflow_px: roundPixels(maxFrameOverflow),
741
+ failed_frame_count: frameOverflows.filter((frame) => frame.overflow_px > maxOverflow).length,
742
+ frames: frameOverflows.map((frame) => toJsonValue(frame))
743
+ };
744
+ });
745
+ const failed = results.filter((result) => result.frame_count < 1 || result.failed_frame_count > 0).length;
746
+ return {
747
+ type: check.type,
748
+ label: checkLabel(check),
749
+ status: failed ? "failed" : "passed",
750
+ evidence: {
751
+ selector: key,
752
+ max_overflow_px: maxOverflow,
753
+ viewports: results.map((result) => toJsonValue(result))
754
+ },
755
+ message: failed ? `Frame selector ${key} overflow exceeded ${maxOverflow}px or was missing in ${failed} viewport(s).` : void 0
756
+ };
757
+ }
673
758
  if (check.type === "text_visible" || check.type === "text_absent") {
674
759
  const key = textKey(check);
675
760
  const expectedVisible = check.type === "text_visible";
@@ -1045,6 +1130,21 @@ function textOrderMatch(texts, expectedTexts) {
1045
1130
  }
1046
1131
  return { matched: true, positions };
1047
1132
  }
1133
+ function frameEvidenceForSelector(viewport, selector) {
1134
+ const container = viewport.frames && viewport.frames[selector || ""];
1135
+ if (!container || typeof container !== "object" || Array.isArray(container)) return [];
1136
+ const frames = Array.isArray(container.frames) ? container.frames : [];
1137
+ return frames.filter((frame) => frame && typeof frame === "object" && !Array.isArray(frame));
1138
+ }
1139
+ function frameTextSample(frame) {
1140
+ return [
1141
+ frame && frame.title,
1142
+ frame && frame.text_sample,
1143
+ frame && frame.body_text_sample,
1144
+ frame && frame.body_text,
1145
+ frame && frame.text,
1146
+ ].map((part) => String(part || "")).filter(Boolean).join(" ");
1147
+ }
1048
1148
  function summarizeRouteInventory(viewport, inventory) {
1049
1149
  const directRoutes = Array.isArray(inventory.direct_routes) ? inventory.direct_routes : [];
1050
1150
  const clickthroughs = Array.isArray(inventory.clickthroughs) ? inventory.clickthroughs : [];
@@ -1291,6 +1391,62 @@ function assessProfile(profile, evidence) {
1291
1391
  });
1292
1392
  continue;
1293
1393
  }
1394
+ if (check.type === "frame_text_visible") {
1395
+ const selector = check.selector || "";
1396
+ const results = viewports.map((viewport) => {
1397
+ const frames = frameEvidenceForSelector(viewport, selector);
1398
+ const matches = frames
1399
+ .map((frame, index) => ({ index, frame, matched: textMatches(frameTextSample(frame), check) }))
1400
+ .filter((item) => item.matched);
1401
+ return {
1402
+ viewport: viewport.name,
1403
+ frame_count: frames.length,
1404
+ matched_count: matches.length,
1405
+ matched: matches.length > 0,
1406
+ urls: frames.map((frame) => String(frame.url || "")).filter(Boolean).slice(0, 10),
1407
+ samples: matches.map((item) => frameTextSample(item.frame).replace(/\s+/g, " ").trim().slice(0, 240)).slice(0, 3),
1408
+ };
1409
+ });
1410
+ const failed = results.filter((result) => !result.matched).length;
1411
+ checks.push({
1412
+ type: check.type,
1413
+ label: check.label || check.type,
1414
+ status: failed ? "failed" : "passed",
1415
+ evidence: { selector, text: check.text || null, pattern: check.pattern || null, viewports: results },
1416
+ message: failed ? "Frame selector " + selector + " did not contain expected text in " + failed + " viewport(s)." : undefined,
1417
+ });
1418
+ continue;
1419
+ }
1420
+ if (check.type === "frame_no_horizontal_overflow") {
1421
+ const selector = check.selector || "";
1422
+ const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
1423
+ const results = viewports.map((viewport) => {
1424
+ const frames = frameEvidenceForSelector(viewport, selector);
1425
+ const frameOverflows = frames.map((frame, index) => ({
1426
+ index,
1427
+ url: String(frame.url || ""),
1428
+ overflow_px: horizontalBoundsOverflowPx(frame),
1429
+ offender_count: boundsOffendersForEvidence(frame).length,
1430
+ }));
1431
+ const maxFrameOverflow = frameOverflows.reduce((max, frame) => Math.max(max, frame.overflow_px), 0);
1432
+ return {
1433
+ viewport: viewport.name,
1434
+ frame_count: frames.length,
1435
+ max_overflow_px: roundPixels(maxFrameOverflow),
1436
+ failed_frame_count: frameOverflows.filter((frame) => frame.overflow_px > maxOverflow).length,
1437
+ frames: frameOverflows,
1438
+ };
1439
+ });
1440
+ const failed = results.filter((result) => result.frame_count < 1 || result.failed_frame_count > 0).length;
1441
+ checks.push({
1442
+ type: check.type,
1443
+ label: check.label || check.type,
1444
+ status: failed ? "failed" : "passed",
1445
+ evidence: { selector, max_overflow_px: maxOverflow, viewports: results },
1446
+ message: failed ? "Frame selector " + selector + " overflow exceeded " + maxOverflow + "px or was missing in " + failed + " viewport(s)." : undefined,
1447
+ });
1448
+ continue;
1449
+ }
1294
1450
  if (check.type === "text_visible" || check.type === "text_absent") {
1295
1451
  const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
1296
1452
  const expectedVisible = check.type === "text_visible";
@@ -1688,6 +1844,108 @@ async function selectorTextSequence(selector) {
1688
1844
  };
1689
1845
  }).catch((error) => ({ count: 0, visible_count: 0, texts: [], visible_texts: [], error: String(error && error.message ? error.message : error).slice(0, 500) }));
1690
1846
  }
1847
+ async function frameEvidence(selector) {
1848
+ const result = { selector, count: 0, frame_count: 0, frames: [], errors: [] };
1849
+ let handles = [];
1850
+ try {
1851
+ const locator = page.locator(selector);
1852
+ result.count = await locator.count();
1853
+ handles = await locator.elementHandles();
1854
+ } catch (error) {
1855
+ result.errors.push(String(error && error.message ? error.message : error).slice(0, 500));
1856
+ return result;
1857
+ }
1858
+ for (let index = 0; index < handles.length; index += 1) {
1859
+ const handle = handles[index];
1860
+ try {
1861
+ const frame = typeof handle.contentFrame === "function" ? await handle.contentFrame() : null;
1862
+ if (!frame) {
1863
+ result.frames.push({ index, attached: false, error: "content_frame_unavailable" });
1864
+ continue;
1865
+ }
1866
+ const snapshot = await frame.evaluate(() => {
1867
+ const body = document.body;
1868
+ const documentElement = document.documentElement;
1869
+ const text = (body ? body.innerText : "").replace(/\s+/g, " ").trim();
1870
+ const clientWidth = documentElement ? documentElement.clientWidth : window.innerWidth;
1871
+ const clientHeight = documentElement ? documentElement.clientHeight : window.innerHeight;
1872
+ const scrollWidth = documentElement ? documentElement.scrollWidth : 0;
1873
+ const viewportWidth = clientWidth || window.innerWidth;
1874
+ const overflowOffenders = [];
1875
+ function isContainedByHorizontalScroller(element) {
1876
+ let current = element.parentElement;
1877
+ while (current && current !== body && current !== documentElement) {
1878
+ const style = window.getComputedStyle(current);
1879
+ const overflowX = style.overflowX || style.overflow || "";
1880
+ if ((overflowX === "auto" || overflowX === "scroll") && current.scrollWidth > current.clientWidth + 1) {
1881
+ const currentRect = current.getBoundingClientRect();
1882
+ const contained = currentRect.left >= -0.5 && currentRect.right <= viewportWidth + 0.5;
1883
+ if (contained) return true;
1884
+ }
1885
+ current = current.parentElement;
1886
+ }
1887
+ return false;
1888
+ }
1889
+ for (const element of Array.from(body ? body.querySelectorAll("*") : [])) {
1890
+ const rect = element.getBoundingClientRect();
1891
+ if (!rect || rect.width < 1 || rect.height < 1) continue;
1892
+ const style = window.getComputedStyle(element);
1893
+ if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") continue;
1894
+ const leftOverflow = Math.max(0, -rect.left);
1895
+ const rightOverflow = Math.max(0, rect.right - viewportWidth);
1896
+ const overflow = Math.max(leftOverflow, rightOverflow);
1897
+ if (overflow <= 0.5) continue;
1898
+ if (isContainedByHorizontalScroller(element)) continue;
1899
+ const tag = element.tagName ? element.tagName.toLowerCase() : "element";
1900
+ const id = element.id ? "#" + element.id : "";
1901
+ const className = typeof element.className === "string"
1902
+ ? element.className.trim().split(/\s+/).filter(Boolean).slice(0, 2).join(".")
1903
+ : "";
1904
+ overflowOffenders.push({
1905
+ selector: tag + id + (className ? "." + className : ""),
1906
+ tag,
1907
+ text: (element.textContent || "").replace(/\s+/g, " ").trim().slice(0, 120),
1908
+ overflow,
1909
+ left_overflow_px: leftOverflow,
1910
+ right_overflow_px: rightOverflow,
1911
+ viewport_width: viewportWidth,
1912
+ rect: {
1913
+ left: rect.left,
1914
+ right: rect.right,
1915
+ width: rect.width,
1916
+ },
1917
+ });
1918
+ }
1919
+ overflowOffenders.sort((a, b) => b.overflow - a.overflow);
1920
+ const boundsOverflowPx = Math.max(
1921
+ 0,
1922
+ scrollWidth - (clientWidth || viewportWidth),
1923
+ ...overflowOffenders.map((offender) => offender.overflow),
1924
+ );
1925
+ return {
1926
+ url: location.href,
1927
+ title: document.title,
1928
+ text_length: text.length,
1929
+ text_sample: text.slice(0, 8000),
1930
+ body_text_sample: text.slice(0, 8000),
1931
+ viewport_width: viewportWidth,
1932
+ viewport_height: clientHeight || window.innerHeight,
1933
+ scroll_width: scrollWidth,
1934
+ client_width: clientWidth,
1935
+ overflow_px: Math.max(0, scrollWidth - (clientWidth || viewportWidth)),
1936
+ bounds_overflow_px: Math.round(boundsOverflowPx * 100) / 100,
1937
+ overflow_offender_count: overflowOffenders.length,
1938
+ overflow_offenders: overflowOffenders.slice(0, 10),
1939
+ };
1940
+ });
1941
+ result.frames.push({ index, attached: true, ...snapshot });
1942
+ } catch (error) {
1943
+ result.frames.push({ index, attached: false, error: String(error && error.message ? error.message : error).slice(0, 1000) });
1944
+ }
1945
+ }
1946
+ result.frame_count = result.frames.filter((frame) => frame && frame.attached !== false && !frame.error).length;
1947
+ return result;
1948
+ }
1691
1949
  function inventoryAppPathFromUrl(urlLike) {
1692
1950
  const url = new URL(String(urlLike), targetUrl);
1693
1951
  const mountPrefix = previewMountPrefix(new URL(targetUrl).pathname);
@@ -1709,6 +1967,13 @@ function inventoryRouteUrl(expectedPath) {
1709
1967
  function inventorySlugFromPath(path) {
1710
1968
  return String(path || "route").split("/").filter(Boolean).pop()?.replace(/[^a-z0-9]+/gi, "-").toLowerCase() || "route";
1711
1969
  }
1970
+ function inventorySlugFromViewport(viewport) {
1971
+ return String(viewport && viewport.name || "viewport").replace(/[^a-z0-9]+/gi, "-").replace(/^-+|-+$/g, "").toLowerCase() || "viewport";
1972
+ }
1973
+ function inventoryScreenshotLabel(check, viewport, phase, path) {
1974
+ const viewportPart = check.run_all_viewports ? "-" + inventorySlugFromViewport(viewport) : "";
1975
+ return profileSlug + viewportPart + "-" + phase + "-" + inventorySlugFromPath(path);
1976
+ }
1712
1977
  function inventoryCheckTimeout(check) {
1713
1978
  const timeout = Number(check && check.timeout_ms);
1714
1979
  return Number.isFinite(timeout) && timeout > 0 ? timeout : 45000;
@@ -1917,7 +2182,7 @@ async function collectRouteInventory(check, viewport) {
1917
2182
  const snapshot = await collectInventoryRouteSnapshot(expectedRoute, "direct", check, error);
1918
2183
  directRoutes.push(snapshot);
1919
2184
  if (check.save_route_screenshots) {
1920
- await saveScreenshot(profileSlug + "-direct-" + inventorySlugFromPath(expectedRoute.path)).catch(() => {});
2185
+ await saveScreenshot(inventoryScreenshotLabel(check, viewport, "direct", expectedRoute.path)).catch(() => {});
1921
2186
  }
1922
2187
  if (error) failures.push({ code: "direct_route_unhealthy", name: expectedRoute.name || null, path: expectedRoute.path, error });
1923
2188
  else if (snapshot.actual_app_path !== normalizeRoutePath(expectedRoute.path)) failures.push({ code: "direct_route_wrong_path", name: expectedRoute.name || null, path: expectedRoute.path, actual_app_path: snapshot.actual_app_path });
@@ -1951,7 +2216,7 @@ async function collectRouteInventory(check, viewport) {
1951
2216
  result.snapshot = await collectInventoryRouteSnapshot(expectedRoute, "clickthrough", check, result.error || "");
1952
2217
  clickthroughs.push(result);
1953
2218
  if (check.save_route_screenshots) {
1954
- await saveScreenshot(profileSlug + "-click-" + inventorySlugFromPath(expectedRoute.path)).catch(() => {});
2219
+ await saveScreenshot(inventoryScreenshotLabel(check, viewport, "click", expectedRoute.path)).catch(() => {});
1955
2220
  }
1956
2221
  if (result.error && result.error !== "source_link_not_found") failures.push({ code: "source_link_clickthrough_unhealthy", name: expectedRoute.name || null, path: expectedRoute.path, error: result.error });
1957
2222
  else if (result.snapshot.actual_app_path !== normalizeRoutePath(expectedRoute.path)) failures.push({ code: "source_link_clickthrough_wrong_path", name: expectedRoute.name || null, path: expectedRoute.path, actual_app_path: result.snapshot.actual_app_path });
@@ -2085,6 +2350,7 @@ async function captureViewport(viewport) {
2085
2350
  evaluation_error: String(error && error.message ? error.message : error).slice(0, 1000),
2086
2351
  }));
2087
2352
  const selectors = {};
2353
+ const frames = {};
2088
2354
  const text_sequences = {};
2089
2355
  const text_matches = {};
2090
2356
  for (const check of profile.checks || []) {
@@ -2098,6 +2364,10 @@ async function captureViewport(viewport) {
2098
2364
  if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
2099
2365
  text_matches[textKey(check)] = textMatches(dom.body_text || dom.body_text_sample || "", check);
2100
2366
  }
2367
+ if ((check.type === "frame_text_visible" || check.type === "frame_no_horizontal_overflow") && check.selector) {
2368
+ selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
2369
+ frames[check.selector] = frames[check.selector] || await frameEvidence(check.selector);
2370
+ }
2101
2371
  }
2102
2372
  const screenshotLabel = profileSlug + "-" + viewport.name;
2103
2373
  try {
@@ -2158,6 +2428,7 @@ async function captureViewport(viewport) {
2158
2428
  bounds_overflow_px: dom.bounds_overflow_px,
2159
2429
  overflow_offenders: dom.overflow_offenders || [],
2160
2430
  selectors,
2431
+ frames,
2161
2432
  text_sequences,
2162
2433
  text_matches,
2163
2434
  route_inventory: routeInventory,
@@ -2193,6 +2464,20 @@ function buildProfileEvidence(currentViewports) {
2193
2464
  overflow_px: currentViewports.map((viewport) => viewport.overflow_px),
2194
2465
  bounds_overflow_px: currentViewports.map((viewport) => viewport.bounds_overflow_px),
2195
2466
  overflow_offender_counts: currentViewports.map((viewport) => (viewport.overflow_offenders || []).length),
2467
+ frames: currentViewports
2468
+ .filter((viewport) => viewport.frames)
2469
+ .map((viewport) => ({
2470
+ viewport: viewport.name,
2471
+ selectors: Object.entries(viewport.frames || {}).map(([selector, frameSet]) => ({
2472
+ selector,
2473
+ count: frameSet && frameSet.count,
2474
+ frame_count: frameSet && frameSet.frame_count,
2475
+ max_bounds_overflow_px: Math.max(
2476
+ 0,
2477
+ ...((frameSet && Array.isArray(frameSet.frames) ? frameSet.frames : [])).map((frame) => horizontalBoundsOverflowPx(frame)),
2478
+ ),
2479
+ })),
2480
+ })),
2196
2481
  route_inventory: currentViewports
2197
2482
  .filter((viewport) => viewport.route_inventory)
2198
2483
  .map((viewport) => ({
@@ -4,7 +4,7 @@ declare const RIDDLE_PROOF_PROFILE_VERSION: "riddle-proof.profile.v1";
4
4
  declare const RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION: "riddle-proof.profile-evidence.v1";
5
5
  declare const RIDDLE_PROOF_PROFILE_RESULT_VERSION: "riddle-proof.profile-result.v1";
6
6
  declare const RIDDLE_PROOF_PROFILE_STATUSES: readonly ["passed", "product_regression", "proof_insufficient", "environment_blocked", "configuration_error", "needs_human_review"];
7
- declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "selector_visible", "selector_count_at_least", "selector_text_order", "text_visible", "text_absent", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
7
+ declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "selector_visible", "selector_count_at_least", "selector_text_order", "frame_text_visible", "frame_no_horizontal_overflow", "text_visible", "text_absent", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
8
8
  declare const RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES: readonly ["click", "fill", "set_input_value", "local_storage", "session_storage", "clear_storage", "wait", "wait_for_selector", "wait_for_text"];
9
9
  type RiddleProofProfileStatus = typeof RIDDLE_PROOF_PROFILE_STATUSES[number];
10
10
  type RiddleProofProfileCheckType = typeof RIDDLE_PROOF_PROFILE_CHECK_TYPES[number];
@@ -139,6 +139,7 @@ interface RiddleProofProfileViewportEvidence {
139
139
  count: number;
140
140
  visible_count: number;
141
141
  }>;
142
+ frames?: Record<string, Record<string, JsonValue>>;
142
143
  text_sequences?: Record<string, Record<string, JsonValue>>;
143
144
  text_matches?: Record<string, boolean>;
144
145
  route_inventory?: Record<string, JsonValue>;
package/dist/profile.d.ts CHANGED
@@ -4,7 +4,7 @@ declare const RIDDLE_PROOF_PROFILE_VERSION: "riddle-proof.profile.v1";
4
4
  declare const RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION: "riddle-proof.profile-evidence.v1";
5
5
  declare const RIDDLE_PROOF_PROFILE_RESULT_VERSION: "riddle-proof.profile-result.v1";
6
6
  declare const RIDDLE_PROOF_PROFILE_STATUSES: readonly ["passed", "product_regression", "proof_insufficient", "environment_blocked", "configuration_error", "needs_human_review"];
7
- declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "selector_visible", "selector_count_at_least", "selector_text_order", "text_visible", "text_absent", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
7
+ declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "selector_visible", "selector_count_at_least", "selector_text_order", "frame_text_visible", "frame_no_horizontal_overflow", "text_visible", "text_absent", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
8
8
  declare const RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES: readonly ["click", "fill", "set_input_value", "local_storage", "session_storage", "clear_storage", "wait", "wait_for_selector", "wait_for_text"];
9
9
  type RiddleProofProfileStatus = typeof RIDDLE_PROOF_PROFILE_STATUSES[number];
10
10
  type RiddleProofProfileCheckType = typeof RIDDLE_PROOF_PROFILE_CHECK_TYPES[number];
@@ -139,6 +139,7 @@ interface RiddleProofProfileViewportEvidence {
139
139
  count: number;
140
140
  visible_count: number;
141
141
  }>;
142
+ frames?: Record<string, Record<string, JsonValue>>;
142
143
  text_sequences?: Record<string, Record<string, JsonValue>>;
143
144
  text_matches?: Record<string, boolean>;
144
145
  route_inventory?: Record<string, JsonValue>;
package/dist/profile.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  resolveRiddleProofProfileTimeoutSec,
20
20
  slugifyRiddleProofProfileName,
21
21
  summarizeRiddleProofProfileResult
22
- } from "./chunk-VEK6QHPE.js";
22
+ } from "./chunk-ZB7AFRN3.js";
23
23
  export {
24
24
  RIDDLE_PROOF_PROFILE_CHECK_TYPES,
25
25
  RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riddledc/riddle-proof",
3
- "version": "0.7.36",
3
+ "version": "0.7.38",
4
4
  "description": "Reusable Riddle Proof contracts and helpers for evidence-backed agent changes.",
5
5
  "license": "MIT",
6
6
  "author": "RiddleDC",