@riddledc/riddle-proof 0.7.34 → 0.7.35

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
@@ -202,6 +202,21 @@ clears `local`, `session`, or `both` browser storage scopes, defaults to
202
202
  profile carries its own hosted Riddle worker budget; an explicit CLI `--timeout`
203
203
  still overrides the profile value for one-off runs.
204
204
 
205
+ Use `selector_text_order` when a table, list, or card group must show visible
206
+ items in a specific order after setup actions such as sorting or filtering:
207
+
208
+ ```json
209
+ {
210
+ "type": "selector_text_order",
211
+ "selector": ".game-table tbody tr",
212
+ "expected_texts": ["AAA Alpha", "MMM Middle", "ZZZ Omega"]
213
+ }
214
+ ```
215
+
216
+ The check records the visible text sequence for the selector and passes when
217
+ the expected texts appear in that order as a subsequence. This is less brittle
218
+ than matching one large body-text regex when only row or card order matters.
219
+
205
220
  Use the `route_inventory` check for source-page route coverage audits where a
206
221
  navigation surface must expose a known set of routes and each route must load
207
222
  both directly and through real link clicks:
@@ -14,6 +14,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
14
14
  "route_loaded",
15
15
  "selector_visible",
16
16
  "selector_count_at_least",
17
+ "selector_text_order",
17
18
  "text_visible",
18
19
  "text_absent",
19
20
  "route_inventory",
@@ -293,6 +294,15 @@ function normalizeRouteInventoryRoutes(value, index) {
293
294
  }
294
295
  return routes;
295
296
  }
297
+ function normalizeExpectedTexts(value, index) {
298
+ if (value === void 0) return void 0;
299
+ if (!Array.isArray(value) || value.length === 0) {
300
+ throw new Error(`checks[${index}] selector_text_order expected_texts must be a non-empty array.`);
301
+ }
302
+ const texts = value.map((item) => String(item).replace(/\s+/g, " ").trim()).filter(Boolean);
303
+ if (!texts.length) throw new Error(`checks[${index}] selector_text_order expected_texts must contain non-empty strings.`);
304
+ return texts;
305
+ }
296
306
  function normalizeCheck(input, index) {
297
307
  if (!isRecord(input)) throw new Error(`checks[${index}] must be an object.`);
298
308
  const type = stringValue(input.type);
@@ -309,6 +319,11 @@ function normalizeCheck(input, index) {
309
319
  if (type === "selector_count_at_least" && numberValue(input.min_count) === void 0) {
310
320
  throw new Error(`checks[${index}] selector_count_at_least requires min_count.`);
311
321
  }
322
+ const expectedTexts = normalizeExpectedTexts(input.expected_texts ?? input.expectedTexts, index);
323
+ if (type === "selector_text_order") {
324
+ if (!stringValue(input.selector)) throw new Error(`checks[${index}] selector_text_order requires selector.`);
325
+ if (!expectedTexts?.length) throw new Error(`checks[${index}] selector_text_order requires expected_texts.`);
326
+ }
312
327
  const expectedRoutes = normalizeRouteInventoryRoutes(input.expected_routes ?? input.expectedRoutes, index);
313
328
  if (type === "route_inventory" && !expectedRoutes?.length) {
314
329
  throw new Error(`checks[${index}] route_inventory requires expected_routes.`);
@@ -319,6 +334,7 @@ function normalizeCheck(input, index) {
319
334
  expected_path: stringValue(input.expected_path),
320
335
  expected_routes: expectedRoutes,
321
336
  selector: stringValue(input.selector),
337
+ expected_texts: expectedTexts,
322
338
  link_selector: stringValue(input.link_selector) || stringValue(input.linkSelector),
323
339
  source_selector: stringValue(input.source_selector) || stringValue(input.sourceSelector),
324
340
  route_path_prefix: stringValue(input.route_path_prefix) || stringValue(input.routePathPrefix),
@@ -424,6 +440,28 @@ function selectorKey(check) {
424
440
  function textKey(check) {
425
441
  return check.pattern ? `pattern:${check.pattern}/${check.flags || ""}` : `text:${check.text || ""}`;
426
442
  }
443
+ function textSequenceForCheck(viewport, check) {
444
+ const key = selectorKey(check);
445
+ const sequence = viewport.text_sequences?.[key];
446
+ if (isRecord(sequence)) {
447
+ const visibleTexts = Array.isArray(sequence.visible_texts) ? sequence.visible_texts : [];
448
+ const texts = Array.isArray(sequence.texts) ? sequence.texts : [];
449
+ const candidates = visibleTexts.length ? visibleTexts : texts;
450
+ return candidates.map((text) => String(text).replace(/\s+/g, " ").trim()).filter(Boolean);
451
+ }
452
+ return [];
453
+ }
454
+ function textOrderMatch(texts, expectedTexts) {
455
+ const positions = [];
456
+ let startAt = 0;
457
+ for (const expected of expectedTexts) {
458
+ const index = texts.findIndex((text, offset) => offset >= startAt && text.includes(expected));
459
+ if (index < 0) return { matched: false, positions };
460
+ positions.push(index);
461
+ startAt = index + 1;
462
+ }
463
+ return { matched: true, positions };
464
+ }
427
465
  function matchText(sample, check) {
428
466
  if (check.pattern) {
429
467
  try {
@@ -543,6 +581,32 @@ function assessCheckFromEvidence(check, evidence) {
543
581
  message: failed.length ? `Selector ${key} count was below ${minCount} in ${failed.length} viewport(s).` : void 0
544
582
  };
545
583
  }
584
+ if (check.type === "selector_text_order") {
585
+ const key = selectorKey(check);
586
+ const expectedTexts = check.expected_texts || [];
587
+ const results = viewports.map((viewport) => {
588
+ const texts = textSequenceForCheck(viewport, check);
589
+ const match = textOrderMatch(texts, expectedTexts);
590
+ return {
591
+ viewport: viewport.name,
592
+ matched: match.matched,
593
+ matched_positions: match.positions,
594
+ visible_texts: texts.slice(0, 20)
595
+ };
596
+ });
597
+ const failed = results.filter((result) => !result.matched).length;
598
+ return {
599
+ type: check.type,
600
+ label: checkLabel(check),
601
+ status: failed ? "failed" : "passed",
602
+ evidence: {
603
+ selector: key,
604
+ expected_texts: expectedTexts,
605
+ viewports: results.map((result) => toJsonValue(result))
606
+ },
607
+ message: failed ? `Selector ${key} text order failed in ${failed} viewport(s).` : void 0
608
+ };
609
+ }
546
610
  if (check.type === "text_visible" || check.type === "text_absent") {
547
611
  const key = textKey(check);
548
612
  const expectedVisible = check.type === "text_visible";
@@ -893,6 +957,28 @@ function textMatches(sample, check) {
893
957
  }
894
958
  return String(sample || "").includes(check.text || "");
895
959
  }
960
+ function textSequenceForCheck(viewport, check) {
961
+ const key = check.selector || "";
962
+ const sequence = viewport.text_sequences && viewport.text_sequences[key];
963
+ if (sequence && typeof sequence === "object") {
964
+ const visibleTexts = Array.isArray(sequence.visible_texts) ? sequence.visible_texts : [];
965
+ const texts = Array.isArray(sequence.texts) ? sequence.texts : [];
966
+ const candidates = visibleTexts.length ? visibleTexts : texts;
967
+ return candidates.map((text) => String(text || "").replace(/\s+/g, " ").trim()).filter(Boolean);
968
+ }
969
+ return [];
970
+ }
971
+ function textOrderMatch(texts, expectedTexts) {
972
+ const positions = [];
973
+ let startAt = 0;
974
+ for (const expected of expectedTexts || []) {
975
+ const index = texts.findIndex((text, offset) => offset >= startAt && text.includes(expected));
976
+ if (index < 0) return { matched: false, positions };
977
+ positions.push(index);
978
+ startAt = index + 1;
979
+ }
980
+ return { matched: true, positions };
981
+ }
896
982
  function numberValue(value) {
897
983
  return typeof value === "number" && Number.isFinite(value) ? value : undefined;
898
984
  }
@@ -1097,6 +1183,29 @@ function assessProfile(profile, evidence) {
1097
1183
  });
1098
1184
  continue;
1099
1185
  }
1186
+ if (check.type === "selector_text_order") {
1187
+ const selector = check.selector || "";
1188
+ const expectedTexts = check.expected_texts || [];
1189
+ const results = viewports.map((viewport) => {
1190
+ const texts = textSequenceForCheck(viewport, check);
1191
+ const match = textOrderMatch(texts, expectedTexts);
1192
+ return {
1193
+ viewport: viewport.name,
1194
+ matched: match.matched,
1195
+ matched_positions: match.positions,
1196
+ visible_texts: texts.slice(0, 20),
1197
+ };
1198
+ });
1199
+ const failed = results.filter((result) => !result.matched).length;
1200
+ checks.push({
1201
+ type: check.type,
1202
+ label: check.label || check.type,
1203
+ status: failed ? "failed" : "passed",
1204
+ evidence: { selector, expected_texts: expectedTexts, viewports: results },
1205
+ message: failed ? "Selector " + selector + " text order failed in " + failed + " viewport(s)." : undefined,
1206
+ });
1207
+ continue;
1208
+ }
1100
1209
  if (check.type === "text_visible" || check.type === "text_absent") {
1101
1210
  const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
1102
1211
  const expectedVisible = check.type === "text_visible";
@@ -1470,6 +1579,27 @@ async function selectorStats(selector) {
1470
1579
  return { count: elements.length, visible_count: elements.filter(isVisible).length };
1471
1580
  }).catch((error) => ({ count: 0, visible_count: 0, error: String(error && error.message ? error.message : error).slice(0, 500) }));
1472
1581
  }
1582
+ async function selectorTextSequence(selector) {
1583
+ return page.locator(selector).evaluateAll((elements) => {
1584
+ const compact = (value) => String(value || "").replace(/\s+/g, " ").trim().slice(0, 240);
1585
+ const isVisible = (element) => {
1586
+ const style = window.getComputedStyle(element);
1587
+ const rect = element.getBoundingClientRect();
1588
+ return style && style.visibility !== "hidden" && style.display !== "none" && rect.width > 0 && rect.height > 0;
1589
+ };
1590
+ const rows = elements.map((element, index) => ({
1591
+ index,
1592
+ text: compact(element.innerText || element.textContent || ""),
1593
+ visible: isVisible(element),
1594
+ }));
1595
+ return {
1596
+ count: rows.length,
1597
+ visible_count: rows.filter((row) => row.visible).length,
1598
+ texts: rows.map((row) => row.text).filter(Boolean).slice(0, 40),
1599
+ visible_texts: rows.filter((row) => row.visible).map((row) => row.text).filter(Boolean).slice(0, 40),
1600
+ };
1601
+ }).catch((error) => ({ count: 0, visible_count: 0, texts: [], visible_texts: [], error: String(error && error.message ? error.message : error).slice(0, 500) }));
1602
+ }
1473
1603
  function inventoryAppPathFromUrl(urlLike) {
1474
1604
  const url = new URL(String(urlLike), targetUrl);
1475
1605
  const mountPrefix = previewMountPrefix(new URL(targetUrl).pathname);
@@ -1867,11 +1997,16 @@ async function captureViewport(viewport) {
1867
1997
  evaluation_error: String(error && error.message ? error.message : error).slice(0, 1000),
1868
1998
  }));
1869
1999
  const selectors = {};
2000
+ const text_sequences = {};
1870
2001
  const text_matches = {};
1871
2002
  for (const check of profile.checks || []) {
1872
2003
  if ((check.type === "selector_visible" || check.type === "selector_count_at_least") && check.selector) {
1873
2004
  selectors[check.selector] = await selectorStats(check.selector);
1874
2005
  }
2006
+ if (check.type === "selector_text_order" && check.selector) {
2007
+ selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
2008
+ text_sequences[check.selector] = await selectorTextSequence(check.selector);
2009
+ }
1875
2010
  if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
1876
2011
  text_matches[textKey(check)] = textMatches(dom.body_text || dom.body_text_sample || "", check);
1877
2012
  }
@@ -1935,6 +2070,7 @@ async function captureViewport(viewport) {
1935
2070
  bounds_overflow_px: dom.bounds_overflow_px,
1936
2071
  overflow_offenders: dom.overflow_offenders || [],
1937
2072
  selectors,
2073
+ text_sequences,
1938
2074
  text_matches,
1939
2075
  route_inventory: routeInventory,
1940
2076
  setup_action_results: setupActionResults,
package/dist/cli.cjs CHANGED
@@ -6887,6 +6887,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
6887
6887
  "route_loaded",
6888
6888
  "selector_visible",
6889
6889
  "selector_count_at_least",
6890
+ "selector_text_order",
6890
6891
  "text_visible",
6891
6892
  "text_absent",
6892
6893
  "route_inventory",
@@ -7166,6 +7167,15 @@ function normalizeRouteInventoryRoutes(value, index) {
7166
7167
  }
7167
7168
  return routes;
7168
7169
  }
7170
+ function normalizeExpectedTexts(value, index) {
7171
+ if (value === void 0) return void 0;
7172
+ if (!Array.isArray(value) || value.length === 0) {
7173
+ throw new Error(`checks[${index}] selector_text_order expected_texts must be a non-empty array.`);
7174
+ }
7175
+ const texts = value.map((item) => String(item).replace(/\s+/g, " ").trim()).filter(Boolean);
7176
+ if (!texts.length) throw new Error(`checks[${index}] selector_text_order expected_texts must contain non-empty strings.`);
7177
+ return texts;
7178
+ }
7169
7179
  function normalizeCheck(input, index) {
7170
7180
  if (!isRecord(input)) throw new Error(`checks[${index}] must be an object.`);
7171
7181
  const type = stringValue2(input.type);
@@ -7182,6 +7192,11 @@ function normalizeCheck(input, index) {
7182
7192
  if (type === "selector_count_at_least" && numberValue(input.min_count) === void 0) {
7183
7193
  throw new Error(`checks[${index}] selector_count_at_least requires min_count.`);
7184
7194
  }
7195
+ const expectedTexts = normalizeExpectedTexts(input.expected_texts ?? input.expectedTexts, index);
7196
+ if (type === "selector_text_order") {
7197
+ if (!stringValue2(input.selector)) throw new Error(`checks[${index}] selector_text_order requires selector.`);
7198
+ if (!expectedTexts?.length) throw new Error(`checks[${index}] selector_text_order requires expected_texts.`);
7199
+ }
7185
7200
  const expectedRoutes = normalizeRouteInventoryRoutes(input.expected_routes ?? input.expectedRoutes, index);
7186
7201
  if (type === "route_inventory" && !expectedRoutes?.length) {
7187
7202
  throw new Error(`checks[${index}] route_inventory requires expected_routes.`);
@@ -7192,6 +7207,7 @@ function normalizeCheck(input, index) {
7192
7207
  expected_path: stringValue2(input.expected_path),
7193
7208
  expected_routes: expectedRoutes,
7194
7209
  selector: stringValue2(input.selector),
7210
+ expected_texts: expectedTexts,
7195
7211
  link_selector: stringValue2(input.link_selector) || stringValue2(input.linkSelector),
7196
7212
  source_selector: stringValue2(input.source_selector) || stringValue2(input.sourceSelector),
7197
7213
  route_path_prefix: stringValue2(input.route_path_prefix) || stringValue2(input.routePathPrefix),
@@ -7297,6 +7313,28 @@ function selectorKey(check) {
7297
7313
  function textKey(check) {
7298
7314
  return check.pattern ? `pattern:${check.pattern}/${check.flags || ""}` : `text:${check.text || ""}`;
7299
7315
  }
7316
+ function textSequenceForCheck(viewport, check) {
7317
+ const key = selectorKey(check);
7318
+ const sequence = viewport.text_sequences?.[key];
7319
+ if (isRecord(sequence)) {
7320
+ const visibleTexts = Array.isArray(sequence.visible_texts) ? sequence.visible_texts : [];
7321
+ const texts = Array.isArray(sequence.texts) ? sequence.texts : [];
7322
+ const candidates = visibleTexts.length ? visibleTexts : texts;
7323
+ return candidates.map((text) => String(text).replace(/\s+/g, " ").trim()).filter(Boolean);
7324
+ }
7325
+ return [];
7326
+ }
7327
+ function textOrderMatch(texts, expectedTexts) {
7328
+ const positions = [];
7329
+ let startAt = 0;
7330
+ for (const expected of expectedTexts) {
7331
+ const index = texts.findIndex((text, offset) => offset >= startAt && text.includes(expected));
7332
+ if (index < 0) return { matched: false, positions };
7333
+ positions.push(index);
7334
+ startAt = index + 1;
7335
+ }
7336
+ return { matched: true, positions };
7337
+ }
7300
7338
  function matchText(sample, check) {
7301
7339
  if (check.pattern) {
7302
7340
  try {
@@ -7416,6 +7454,32 @@ function assessCheckFromEvidence(check, evidence) {
7416
7454
  message: failed.length ? `Selector ${key} count was below ${minCount} in ${failed.length} viewport(s).` : void 0
7417
7455
  };
7418
7456
  }
7457
+ if (check.type === "selector_text_order") {
7458
+ const key = selectorKey(check);
7459
+ const expectedTexts = check.expected_texts || [];
7460
+ const results = viewports.map((viewport) => {
7461
+ const texts = textSequenceForCheck(viewport, check);
7462
+ const match = textOrderMatch(texts, expectedTexts);
7463
+ return {
7464
+ viewport: viewport.name,
7465
+ matched: match.matched,
7466
+ matched_positions: match.positions,
7467
+ visible_texts: texts.slice(0, 20)
7468
+ };
7469
+ });
7470
+ const failed = results.filter((result) => !result.matched).length;
7471
+ return {
7472
+ type: check.type,
7473
+ label: checkLabel(check),
7474
+ status: failed ? "failed" : "passed",
7475
+ evidence: {
7476
+ selector: key,
7477
+ expected_texts: expectedTexts,
7478
+ viewports: results.map((result) => toJsonValue(result))
7479
+ },
7480
+ message: failed ? `Selector ${key} text order failed in ${failed} viewport(s).` : void 0
7481
+ };
7482
+ }
7419
7483
  if (check.type === "text_visible" || check.type === "text_absent") {
7420
7484
  const key = textKey(check);
7421
7485
  const expectedVisible = check.type === "text_visible";
@@ -7750,6 +7814,28 @@ function textMatches(sample, check) {
7750
7814
  }
7751
7815
  return String(sample || "").includes(check.text || "");
7752
7816
  }
7817
+ function textSequenceForCheck(viewport, check) {
7818
+ const key = check.selector || "";
7819
+ const sequence = viewport.text_sequences && viewport.text_sequences[key];
7820
+ if (sequence && typeof sequence === "object") {
7821
+ const visibleTexts = Array.isArray(sequence.visible_texts) ? sequence.visible_texts : [];
7822
+ const texts = Array.isArray(sequence.texts) ? sequence.texts : [];
7823
+ const candidates = visibleTexts.length ? visibleTexts : texts;
7824
+ return candidates.map((text) => String(text || "").replace(/\s+/g, " ").trim()).filter(Boolean);
7825
+ }
7826
+ return [];
7827
+ }
7828
+ function textOrderMatch(texts, expectedTexts) {
7829
+ const positions = [];
7830
+ let startAt = 0;
7831
+ for (const expected of expectedTexts || []) {
7832
+ const index = texts.findIndex((text, offset) => offset >= startAt && text.includes(expected));
7833
+ if (index < 0) return { matched: false, positions };
7834
+ positions.push(index);
7835
+ startAt = index + 1;
7836
+ }
7837
+ return { matched: true, positions };
7838
+ }
7753
7839
  function numberValue(value) {
7754
7840
  return typeof value === "number" && Number.isFinite(value) ? value : undefined;
7755
7841
  }
@@ -7954,6 +8040,29 @@ function assessProfile(profile, evidence) {
7954
8040
  });
7955
8041
  continue;
7956
8042
  }
8043
+ if (check.type === "selector_text_order") {
8044
+ const selector = check.selector || "";
8045
+ const expectedTexts = check.expected_texts || [];
8046
+ const results = viewports.map((viewport) => {
8047
+ const texts = textSequenceForCheck(viewport, check);
8048
+ const match = textOrderMatch(texts, expectedTexts);
8049
+ return {
8050
+ viewport: viewport.name,
8051
+ matched: match.matched,
8052
+ matched_positions: match.positions,
8053
+ visible_texts: texts.slice(0, 20),
8054
+ };
8055
+ });
8056
+ const failed = results.filter((result) => !result.matched).length;
8057
+ checks.push({
8058
+ type: check.type,
8059
+ label: check.label || check.type,
8060
+ status: failed ? "failed" : "passed",
8061
+ evidence: { selector, expected_texts: expectedTexts, viewports: results },
8062
+ message: failed ? "Selector " + selector + " text order failed in " + failed + " viewport(s)." : undefined,
8063
+ });
8064
+ continue;
8065
+ }
7957
8066
  if (check.type === "text_visible" || check.type === "text_absent") {
7958
8067
  const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
7959
8068
  const expectedVisible = check.type === "text_visible";
@@ -8327,6 +8436,27 @@ async function selectorStats(selector) {
8327
8436
  return { count: elements.length, visible_count: elements.filter(isVisible).length };
8328
8437
  }).catch((error) => ({ count: 0, visible_count: 0, error: String(error && error.message ? error.message : error).slice(0, 500) }));
8329
8438
  }
8439
+ async function selectorTextSequence(selector) {
8440
+ return page.locator(selector).evaluateAll((elements) => {
8441
+ const compact = (value) => String(value || "").replace(/\s+/g, " ").trim().slice(0, 240);
8442
+ const isVisible = (element) => {
8443
+ const style = window.getComputedStyle(element);
8444
+ const rect = element.getBoundingClientRect();
8445
+ return style && style.visibility !== "hidden" && style.display !== "none" && rect.width > 0 && rect.height > 0;
8446
+ };
8447
+ const rows = elements.map((element, index) => ({
8448
+ index,
8449
+ text: compact(element.innerText || element.textContent || ""),
8450
+ visible: isVisible(element),
8451
+ }));
8452
+ return {
8453
+ count: rows.length,
8454
+ visible_count: rows.filter((row) => row.visible).length,
8455
+ texts: rows.map((row) => row.text).filter(Boolean).slice(0, 40),
8456
+ visible_texts: rows.filter((row) => row.visible).map((row) => row.text).filter(Boolean).slice(0, 40),
8457
+ };
8458
+ }).catch((error) => ({ count: 0, visible_count: 0, texts: [], visible_texts: [], error: String(error && error.message ? error.message : error).slice(0, 500) }));
8459
+ }
8330
8460
  function inventoryAppPathFromUrl(urlLike) {
8331
8461
  const url = new URL(String(urlLike), targetUrl);
8332
8462
  const mountPrefix = previewMountPrefix(new URL(targetUrl).pathname);
@@ -8724,11 +8854,16 @@ async function captureViewport(viewport) {
8724
8854
  evaluation_error: String(error && error.message ? error.message : error).slice(0, 1000),
8725
8855
  }));
8726
8856
  const selectors = {};
8857
+ const text_sequences = {};
8727
8858
  const text_matches = {};
8728
8859
  for (const check of profile.checks || []) {
8729
8860
  if ((check.type === "selector_visible" || check.type === "selector_count_at_least") && check.selector) {
8730
8861
  selectors[check.selector] = await selectorStats(check.selector);
8731
8862
  }
8863
+ if (check.type === "selector_text_order" && check.selector) {
8864
+ selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
8865
+ text_sequences[check.selector] = await selectorTextSequence(check.selector);
8866
+ }
8732
8867
  if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
8733
8868
  text_matches[textKey(check)] = textMatches(dom.body_text || dom.body_text_sample || "", check);
8734
8869
  }
@@ -8792,6 +8927,7 @@ async function captureViewport(viewport) {
8792
8927
  bounds_overflow_px: dom.bounds_overflow_px,
8793
8928
  overflow_offenders: dom.overflow_offenders || [],
8794
8929
  selectors,
8930
+ text_sequences,
8795
8931
  text_matches,
8796
8932
  route_inventory: routeInventory,
8797
8933
  setup_action_results: setupActionResults,
package/dist/cli.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  profileStatusExitCode,
11
11
  resolveRiddleProofProfileTargetUrl,
12
12
  resolveRiddleProofProfileTimeoutSec
13
- } from "./chunk-PWGJQLOS.js";
13
+ } from "./chunk-VCINQE5O.js";
14
14
  import {
15
15
  createRiddleApiClient,
16
16
  parseRiddleViewport
package/dist/index.cjs CHANGED
@@ -8728,6 +8728,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
8728
8728
  "route_loaded",
8729
8729
  "selector_visible",
8730
8730
  "selector_count_at_least",
8731
+ "selector_text_order",
8731
8732
  "text_visible",
8732
8733
  "text_absent",
8733
8734
  "route_inventory",
@@ -9007,6 +9008,15 @@ function normalizeRouteInventoryRoutes(value, index) {
9007
9008
  }
9008
9009
  return routes;
9009
9010
  }
9011
+ function normalizeExpectedTexts(value, index) {
9012
+ if (value === void 0) return void 0;
9013
+ if (!Array.isArray(value) || value.length === 0) {
9014
+ throw new Error(`checks[${index}] selector_text_order expected_texts must be a non-empty array.`);
9015
+ }
9016
+ const texts = value.map((item) => String(item).replace(/\s+/g, " ").trim()).filter(Boolean);
9017
+ if (!texts.length) throw new Error(`checks[${index}] selector_text_order expected_texts must contain non-empty strings.`);
9018
+ return texts;
9019
+ }
9010
9020
  function normalizeCheck(input, index) {
9011
9021
  if (!isRecord2(input)) throw new Error(`checks[${index}] must be an object.`);
9012
9022
  const type = stringValue5(input.type);
@@ -9023,6 +9033,11 @@ function normalizeCheck(input, index) {
9023
9033
  if (type === "selector_count_at_least" && numberValue3(input.min_count) === void 0) {
9024
9034
  throw new Error(`checks[${index}] selector_count_at_least requires min_count.`);
9025
9035
  }
9036
+ const expectedTexts = normalizeExpectedTexts(input.expected_texts ?? input.expectedTexts, index);
9037
+ if (type === "selector_text_order") {
9038
+ if (!stringValue5(input.selector)) throw new Error(`checks[${index}] selector_text_order requires selector.`);
9039
+ if (!expectedTexts?.length) throw new Error(`checks[${index}] selector_text_order requires expected_texts.`);
9040
+ }
9026
9041
  const expectedRoutes = normalizeRouteInventoryRoutes(input.expected_routes ?? input.expectedRoutes, index);
9027
9042
  if (type === "route_inventory" && !expectedRoutes?.length) {
9028
9043
  throw new Error(`checks[${index}] route_inventory requires expected_routes.`);
@@ -9033,6 +9048,7 @@ function normalizeCheck(input, index) {
9033
9048
  expected_path: stringValue5(input.expected_path),
9034
9049
  expected_routes: expectedRoutes,
9035
9050
  selector: stringValue5(input.selector),
9051
+ expected_texts: expectedTexts,
9036
9052
  link_selector: stringValue5(input.link_selector) || stringValue5(input.linkSelector),
9037
9053
  source_selector: stringValue5(input.source_selector) || stringValue5(input.sourceSelector),
9038
9054
  route_path_prefix: stringValue5(input.route_path_prefix) || stringValue5(input.routePathPrefix),
@@ -9138,6 +9154,28 @@ function selectorKey(check) {
9138
9154
  function textKey(check) {
9139
9155
  return check.pattern ? `pattern:${check.pattern}/${check.flags || ""}` : `text:${check.text || ""}`;
9140
9156
  }
9157
+ function textSequenceForCheck(viewport, check) {
9158
+ const key = selectorKey(check);
9159
+ const sequence = viewport.text_sequences?.[key];
9160
+ if (isRecord2(sequence)) {
9161
+ const visibleTexts = Array.isArray(sequence.visible_texts) ? sequence.visible_texts : [];
9162
+ const texts = Array.isArray(sequence.texts) ? sequence.texts : [];
9163
+ const candidates = visibleTexts.length ? visibleTexts : texts;
9164
+ return candidates.map((text) => String(text).replace(/\s+/g, " ").trim()).filter(Boolean);
9165
+ }
9166
+ return [];
9167
+ }
9168
+ function textOrderMatch(texts, expectedTexts) {
9169
+ const positions = [];
9170
+ let startAt = 0;
9171
+ for (const expected of expectedTexts) {
9172
+ const index = texts.findIndex((text, offset) => offset >= startAt && text.includes(expected));
9173
+ if (index < 0) return { matched: false, positions };
9174
+ positions.push(index);
9175
+ startAt = index + 1;
9176
+ }
9177
+ return { matched: true, positions };
9178
+ }
9141
9179
  function matchText(sample, check) {
9142
9180
  if (check.pattern) {
9143
9181
  try {
@@ -9257,6 +9295,32 @@ function assessCheckFromEvidence(check, evidence) {
9257
9295
  message: failed.length ? `Selector ${key} count was below ${minCount} in ${failed.length} viewport(s).` : void 0
9258
9296
  };
9259
9297
  }
9298
+ if (check.type === "selector_text_order") {
9299
+ const key = selectorKey(check);
9300
+ const expectedTexts = check.expected_texts || [];
9301
+ const results = viewports.map((viewport) => {
9302
+ const texts = textSequenceForCheck(viewport, check);
9303
+ const match = textOrderMatch(texts, expectedTexts);
9304
+ return {
9305
+ viewport: viewport.name,
9306
+ matched: match.matched,
9307
+ matched_positions: match.positions,
9308
+ visible_texts: texts.slice(0, 20)
9309
+ };
9310
+ });
9311
+ const failed = results.filter((result) => !result.matched).length;
9312
+ return {
9313
+ type: check.type,
9314
+ label: checkLabel(check),
9315
+ status: failed ? "failed" : "passed",
9316
+ evidence: {
9317
+ selector: key,
9318
+ expected_texts: expectedTexts,
9319
+ viewports: results.map((result) => toJsonValue(result))
9320
+ },
9321
+ message: failed ? `Selector ${key} text order failed in ${failed} viewport(s).` : void 0
9322
+ };
9323
+ }
9260
9324
  if (check.type === "text_visible" || check.type === "text_absent") {
9261
9325
  const key = textKey(check);
9262
9326
  const expectedVisible = check.type === "text_visible";
@@ -9607,6 +9671,28 @@ function textMatches(sample, check) {
9607
9671
  }
9608
9672
  return String(sample || "").includes(check.text || "");
9609
9673
  }
9674
+ function textSequenceForCheck(viewport, check) {
9675
+ const key = check.selector || "";
9676
+ const sequence = viewport.text_sequences && viewport.text_sequences[key];
9677
+ if (sequence && typeof sequence === "object") {
9678
+ const visibleTexts = Array.isArray(sequence.visible_texts) ? sequence.visible_texts : [];
9679
+ const texts = Array.isArray(sequence.texts) ? sequence.texts : [];
9680
+ const candidates = visibleTexts.length ? visibleTexts : texts;
9681
+ return candidates.map((text) => String(text || "").replace(/\s+/g, " ").trim()).filter(Boolean);
9682
+ }
9683
+ return [];
9684
+ }
9685
+ function textOrderMatch(texts, expectedTexts) {
9686
+ const positions = [];
9687
+ let startAt = 0;
9688
+ for (const expected of expectedTexts || []) {
9689
+ const index = texts.findIndex((text, offset) => offset >= startAt && text.includes(expected));
9690
+ if (index < 0) return { matched: false, positions };
9691
+ positions.push(index);
9692
+ startAt = index + 1;
9693
+ }
9694
+ return { matched: true, positions };
9695
+ }
9610
9696
  function numberValue(value) {
9611
9697
  return typeof value === "number" && Number.isFinite(value) ? value : undefined;
9612
9698
  }
@@ -9811,6 +9897,29 @@ function assessProfile(profile, evidence) {
9811
9897
  });
9812
9898
  continue;
9813
9899
  }
9900
+ if (check.type === "selector_text_order") {
9901
+ const selector = check.selector || "";
9902
+ const expectedTexts = check.expected_texts || [];
9903
+ const results = viewports.map((viewport) => {
9904
+ const texts = textSequenceForCheck(viewport, check);
9905
+ const match = textOrderMatch(texts, expectedTexts);
9906
+ return {
9907
+ viewport: viewport.name,
9908
+ matched: match.matched,
9909
+ matched_positions: match.positions,
9910
+ visible_texts: texts.slice(0, 20),
9911
+ };
9912
+ });
9913
+ const failed = results.filter((result) => !result.matched).length;
9914
+ checks.push({
9915
+ type: check.type,
9916
+ label: check.label || check.type,
9917
+ status: failed ? "failed" : "passed",
9918
+ evidence: { selector, expected_texts: expectedTexts, viewports: results },
9919
+ message: failed ? "Selector " + selector + " text order failed in " + failed + " viewport(s)." : undefined,
9920
+ });
9921
+ continue;
9922
+ }
9814
9923
  if (check.type === "text_visible" || check.type === "text_absent") {
9815
9924
  const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
9816
9925
  const expectedVisible = check.type === "text_visible";
@@ -10184,6 +10293,27 @@ async function selectorStats(selector) {
10184
10293
  return { count: elements.length, visible_count: elements.filter(isVisible).length };
10185
10294
  }).catch((error) => ({ count: 0, visible_count: 0, error: String(error && error.message ? error.message : error).slice(0, 500) }));
10186
10295
  }
10296
+ async function selectorTextSequence(selector) {
10297
+ return page.locator(selector).evaluateAll((elements) => {
10298
+ const compact = (value) => String(value || "").replace(/\s+/g, " ").trim().slice(0, 240);
10299
+ const isVisible = (element) => {
10300
+ const style = window.getComputedStyle(element);
10301
+ const rect = element.getBoundingClientRect();
10302
+ return style && style.visibility !== "hidden" && style.display !== "none" && rect.width > 0 && rect.height > 0;
10303
+ };
10304
+ const rows = elements.map((element, index) => ({
10305
+ index,
10306
+ text: compact(element.innerText || element.textContent || ""),
10307
+ visible: isVisible(element),
10308
+ }));
10309
+ return {
10310
+ count: rows.length,
10311
+ visible_count: rows.filter((row) => row.visible).length,
10312
+ texts: rows.map((row) => row.text).filter(Boolean).slice(0, 40),
10313
+ visible_texts: rows.filter((row) => row.visible).map((row) => row.text).filter(Boolean).slice(0, 40),
10314
+ };
10315
+ }).catch((error) => ({ count: 0, visible_count: 0, texts: [], visible_texts: [], error: String(error && error.message ? error.message : error).slice(0, 500) }));
10316
+ }
10187
10317
  function inventoryAppPathFromUrl(urlLike) {
10188
10318
  const url = new URL(String(urlLike), targetUrl);
10189
10319
  const mountPrefix = previewMountPrefix(new URL(targetUrl).pathname);
@@ -10581,11 +10711,16 @@ async function captureViewport(viewport) {
10581
10711
  evaluation_error: String(error && error.message ? error.message : error).slice(0, 1000),
10582
10712
  }));
10583
10713
  const selectors = {};
10714
+ const text_sequences = {};
10584
10715
  const text_matches = {};
10585
10716
  for (const check of profile.checks || []) {
10586
10717
  if ((check.type === "selector_visible" || check.type === "selector_count_at_least") && check.selector) {
10587
10718
  selectors[check.selector] = await selectorStats(check.selector);
10588
10719
  }
10720
+ if (check.type === "selector_text_order" && check.selector) {
10721
+ selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
10722
+ text_sequences[check.selector] = await selectorTextSequence(check.selector);
10723
+ }
10589
10724
  if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
10590
10725
  text_matches[textKey(check)] = textMatches(dom.body_text || dom.body_text_sample || "", check);
10591
10726
  }
@@ -10649,6 +10784,7 @@ async function captureViewport(viewport) {
10649
10784
  bounds_overflow_px: dom.bounds_overflow_px,
10650
10785
  overflow_offenders: dom.overflow_offenders || [],
10651
10786
  selectors,
10787
+ text_sequences,
10652
10788
  text_matches,
10653
10789
  route_inventory: routeInventory,
10654
10790
  setup_action_results: setupActionResults,
package/dist/index.js CHANGED
@@ -58,7 +58,7 @@ import {
58
58
  resolveRiddleProofProfileTimeoutSec,
59
59
  slugifyRiddleProofProfileName,
60
60
  summarizeRiddleProofProfileResult
61
- } from "./chunk-PWGJQLOS.js";
61
+ } from "./chunk-VCINQE5O.js";
62
62
  import {
63
63
  DEFAULT_RIDDLE_API_BASE_URL,
64
64
  DEFAULT_RIDDLE_API_KEY_FILE,
package/dist/profile.cjs CHANGED
@@ -57,6 +57,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
57
57
  "route_loaded",
58
58
  "selector_visible",
59
59
  "selector_count_at_least",
60
+ "selector_text_order",
60
61
  "text_visible",
61
62
  "text_absent",
62
63
  "route_inventory",
@@ -336,6 +337,15 @@ function normalizeRouteInventoryRoutes(value, index) {
336
337
  }
337
338
  return routes;
338
339
  }
340
+ function normalizeExpectedTexts(value, index) {
341
+ if (value === void 0) return void 0;
342
+ if (!Array.isArray(value) || value.length === 0) {
343
+ throw new Error(`checks[${index}] selector_text_order expected_texts must be a non-empty array.`);
344
+ }
345
+ const texts = value.map((item) => String(item).replace(/\s+/g, " ").trim()).filter(Boolean);
346
+ if (!texts.length) throw new Error(`checks[${index}] selector_text_order expected_texts must contain non-empty strings.`);
347
+ return texts;
348
+ }
339
349
  function normalizeCheck(input, index) {
340
350
  if (!isRecord(input)) throw new Error(`checks[${index}] must be an object.`);
341
351
  const type = stringValue(input.type);
@@ -352,6 +362,11 @@ function normalizeCheck(input, index) {
352
362
  if (type === "selector_count_at_least" && numberValue(input.min_count) === void 0) {
353
363
  throw new Error(`checks[${index}] selector_count_at_least requires min_count.`);
354
364
  }
365
+ const expectedTexts = normalizeExpectedTexts(input.expected_texts ?? input.expectedTexts, index);
366
+ if (type === "selector_text_order") {
367
+ if (!stringValue(input.selector)) throw new Error(`checks[${index}] selector_text_order requires selector.`);
368
+ if (!expectedTexts?.length) throw new Error(`checks[${index}] selector_text_order requires expected_texts.`);
369
+ }
355
370
  const expectedRoutes = normalizeRouteInventoryRoutes(input.expected_routes ?? input.expectedRoutes, index);
356
371
  if (type === "route_inventory" && !expectedRoutes?.length) {
357
372
  throw new Error(`checks[${index}] route_inventory requires expected_routes.`);
@@ -362,6 +377,7 @@ function normalizeCheck(input, index) {
362
377
  expected_path: stringValue(input.expected_path),
363
378
  expected_routes: expectedRoutes,
364
379
  selector: stringValue(input.selector),
380
+ expected_texts: expectedTexts,
365
381
  link_selector: stringValue(input.link_selector) || stringValue(input.linkSelector),
366
382
  source_selector: stringValue(input.source_selector) || stringValue(input.sourceSelector),
367
383
  route_path_prefix: stringValue(input.route_path_prefix) || stringValue(input.routePathPrefix),
@@ -467,6 +483,28 @@ function selectorKey(check) {
467
483
  function textKey(check) {
468
484
  return check.pattern ? `pattern:${check.pattern}/${check.flags || ""}` : `text:${check.text || ""}`;
469
485
  }
486
+ function textSequenceForCheck(viewport, check) {
487
+ const key = selectorKey(check);
488
+ const sequence = viewport.text_sequences?.[key];
489
+ if (isRecord(sequence)) {
490
+ const visibleTexts = Array.isArray(sequence.visible_texts) ? sequence.visible_texts : [];
491
+ const texts = Array.isArray(sequence.texts) ? sequence.texts : [];
492
+ const candidates = visibleTexts.length ? visibleTexts : texts;
493
+ return candidates.map((text) => String(text).replace(/\s+/g, " ").trim()).filter(Boolean);
494
+ }
495
+ return [];
496
+ }
497
+ function textOrderMatch(texts, expectedTexts) {
498
+ const positions = [];
499
+ let startAt = 0;
500
+ for (const expected of expectedTexts) {
501
+ const index = texts.findIndex((text, offset) => offset >= startAt && text.includes(expected));
502
+ if (index < 0) return { matched: false, positions };
503
+ positions.push(index);
504
+ startAt = index + 1;
505
+ }
506
+ return { matched: true, positions };
507
+ }
470
508
  function matchText(sample, check) {
471
509
  if (check.pattern) {
472
510
  try {
@@ -586,6 +624,32 @@ function assessCheckFromEvidence(check, evidence) {
586
624
  message: failed.length ? `Selector ${key} count was below ${minCount} in ${failed.length} viewport(s).` : void 0
587
625
  };
588
626
  }
627
+ if (check.type === "selector_text_order") {
628
+ const key = selectorKey(check);
629
+ const expectedTexts = check.expected_texts || [];
630
+ const results = viewports.map((viewport) => {
631
+ const texts = textSequenceForCheck(viewport, check);
632
+ const match = textOrderMatch(texts, expectedTexts);
633
+ return {
634
+ viewport: viewport.name,
635
+ matched: match.matched,
636
+ matched_positions: match.positions,
637
+ visible_texts: texts.slice(0, 20)
638
+ };
639
+ });
640
+ const failed = results.filter((result) => !result.matched).length;
641
+ return {
642
+ type: check.type,
643
+ label: checkLabel(check),
644
+ status: failed ? "failed" : "passed",
645
+ evidence: {
646
+ selector: key,
647
+ expected_texts: expectedTexts,
648
+ viewports: results.map((result) => toJsonValue(result))
649
+ },
650
+ message: failed ? `Selector ${key} text order failed in ${failed} viewport(s).` : void 0
651
+ };
652
+ }
589
653
  if (check.type === "text_visible" || check.type === "text_absent") {
590
654
  const key = textKey(check);
591
655
  const expectedVisible = check.type === "text_visible";
@@ -936,6 +1000,28 @@ function textMatches(sample, check) {
936
1000
  }
937
1001
  return String(sample || "").includes(check.text || "");
938
1002
  }
1003
+ function textSequenceForCheck(viewport, check) {
1004
+ const key = check.selector || "";
1005
+ const sequence = viewport.text_sequences && viewport.text_sequences[key];
1006
+ if (sequence && typeof sequence === "object") {
1007
+ const visibleTexts = Array.isArray(sequence.visible_texts) ? sequence.visible_texts : [];
1008
+ const texts = Array.isArray(sequence.texts) ? sequence.texts : [];
1009
+ const candidates = visibleTexts.length ? visibleTexts : texts;
1010
+ return candidates.map((text) => String(text || "").replace(/\s+/g, " ").trim()).filter(Boolean);
1011
+ }
1012
+ return [];
1013
+ }
1014
+ function textOrderMatch(texts, expectedTexts) {
1015
+ const positions = [];
1016
+ let startAt = 0;
1017
+ for (const expected of expectedTexts || []) {
1018
+ const index = texts.findIndex((text, offset) => offset >= startAt && text.includes(expected));
1019
+ if (index < 0) return { matched: false, positions };
1020
+ positions.push(index);
1021
+ startAt = index + 1;
1022
+ }
1023
+ return { matched: true, positions };
1024
+ }
939
1025
  function numberValue(value) {
940
1026
  return typeof value === "number" && Number.isFinite(value) ? value : undefined;
941
1027
  }
@@ -1140,6 +1226,29 @@ function assessProfile(profile, evidence) {
1140
1226
  });
1141
1227
  continue;
1142
1228
  }
1229
+ if (check.type === "selector_text_order") {
1230
+ const selector = check.selector || "";
1231
+ const expectedTexts = check.expected_texts || [];
1232
+ const results = viewports.map((viewport) => {
1233
+ const texts = textSequenceForCheck(viewport, check);
1234
+ const match = textOrderMatch(texts, expectedTexts);
1235
+ return {
1236
+ viewport: viewport.name,
1237
+ matched: match.matched,
1238
+ matched_positions: match.positions,
1239
+ visible_texts: texts.slice(0, 20),
1240
+ };
1241
+ });
1242
+ const failed = results.filter((result) => !result.matched).length;
1243
+ checks.push({
1244
+ type: check.type,
1245
+ label: check.label || check.type,
1246
+ status: failed ? "failed" : "passed",
1247
+ evidence: { selector, expected_texts: expectedTexts, viewports: results },
1248
+ message: failed ? "Selector " + selector + " text order failed in " + failed + " viewport(s)." : undefined,
1249
+ });
1250
+ continue;
1251
+ }
1143
1252
  if (check.type === "text_visible" || check.type === "text_absent") {
1144
1253
  const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
1145
1254
  const expectedVisible = check.type === "text_visible";
@@ -1513,6 +1622,27 @@ async function selectorStats(selector) {
1513
1622
  return { count: elements.length, visible_count: elements.filter(isVisible).length };
1514
1623
  }).catch((error) => ({ count: 0, visible_count: 0, error: String(error && error.message ? error.message : error).slice(0, 500) }));
1515
1624
  }
1625
+ async function selectorTextSequence(selector) {
1626
+ return page.locator(selector).evaluateAll((elements) => {
1627
+ const compact = (value) => String(value || "").replace(/\s+/g, " ").trim().slice(0, 240);
1628
+ const isVisible = (element) => {
1629
+ const style = window.getComputedStyle(element);
1630
+ const rect = element.getBoundingClientRect();
1631
+ return style && style.visibility !== "hidden" && style.display !== "none" && rect.width > 0 && rect.height > 0;
1632
+ };
1633
+ const rows = elements.map((element, index) => ({
1634
+ index,
1635
+ text: compact(element.innerText || element.textContent || ""),
1636
+ visible: isVisible(element),
1637
+ }));
1638
+ return {
1639
+ count: rows.length,
1640
+ visible_count: rows.filter((row) => row.visible).length,
1641
+ texts: rows.map((row) => row.text).filter(Boolean).slice(0, 40),
1642
+ visible_texts: rows.filter((row) => row.visible).map((row) => row.text).filter(Boolean).slice(0, 40),
1643
+ };
1644
+ }).catch((error) => ({ count: 0, visible_count: 0, texts: [], visible_texts: [], error: String(error && error.message ? error.message : error).slice(0, 500) }));
1645
+ }
1516
1646
  function inventoryAppPathFromUrl(urlLike) {
1517
1647
  const url = new URL(String(urlLike), targetUrl);
1518
1648
  const mountPrefix = previewMountPrefix(new URL(targetUrl).pathname);
@@ -1910,11 +2040,16 @@ async function captureViewport(viewport) {
1910
2040
  evaluation_error: String(error && error.message ? error.message : error).slice(0, 1000),
1911
2041
  }));
1912
2042
  const selectors = {};
2043
+ const text_sequences = {};
1913
2044
  const text_matches = {};
1914
2045
  for (const check of profile.checks || []) {
1915
2046
  if ((check.type === "selector_visible" || check.type === "selector_count_at_least") && check.selector) {
1916
2047
  selectors[check.selector] = await selectorStats(check.selector);
1917
2048
  }
2049
+ if (check.type === "selector_text_order" && check.selector) {
2050
+ selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
2051
+ text_sequences[check.selector] = await selectorTextSequence(check.selector);
2052
+ }
1918
2053
  if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
1919
2054
  text_matches[textKey(check)] = textMatches(dom.body_text || dom.body_text_sample || "", check);
1920
2055
  }
@@ -1978,6 +2113,7 @@ async function captureViewport(viewport) {
1978
2113
  bounds_overflow_px: dom.bounds_overflow_px,
1979
2114
  overflow_offenders: dom.overflow_offenders || [],
1980
2115
  selectors,
2116
+ text_sequences,
1981
2117
  text_matches,
1982
2118
  route_inventory: routeInventory,
1983
2119
  setup_action_results: setupActionResults,
@@ -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", "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", "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];
@@ -66,6 +66,7 @@ interface RiddleProofProfileCheck {
66
66
  expected_path?: string;
67
67
  expected_routes?: RiddleProofProfileRouteInventoryRoute[];
68
68
  selector?: string;
69
+ expected_texts?: string[];
69
70
  link_selector?: string;
70
71
  source_selector?: string;
71
72
  route_path_prefix?: string;
@@ -137,6 +138,7 @@ interface RiddleProofProfileViewportEvidence {
137
138
  count: number;
138
139
  visible_count: number;
139
140
  }>;
141
+ text_sequences?: Record<string, Record<string, JsonValue>>;
140
142
  text_matches?: Record<string, boolean>;
141
143
  route_inventory?: Record<string, JsonValue>;
142
144
  setup_action_results?: Array<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", "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", "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];
@@ -66,6 +66,7 @@ interface RiddleProofProfileCheck {
66
66
  expected_path?: string;
67
67
  expected_routes?: RiddleProofProfileRouteInventoryRoute[];
68
68
  selector?: string;
69
+ expected_texts?: string[];
69
70
  link_selector?: string;
70
71
  source_selector?: string;
71
72
  route_path_prefix?: string;
@@ -137,6 +138,7 @@ interface RiddleProofProfileViewportEvidence {
137
138
  count: number;
138
139
  visible_count: number;
139
140
  }>;
141
+ text_sequences?: Record<string, Record<string, JsonValue>>;
140
142
  text_matches?: Record<string, boolean>;
141
143
  route_inventory?: Record<string, JsonValue>;
142
144
  setup_action_results?: Array<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-PWGJQLOS.js";
22
+ } from "./chunk-VCINQE5O.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.34",
3
+ "version": "0.7.35",
4
4
  "description": "Reusable Riddle Proof contracts and helpers for evidence-backed agent changes.",
5
5
  "license": "MIT",
6
6
  "author": "RiddleDC",