From 66482a9808a01bf81b505e5fa0373083881680c9 Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Thu, 3 Sep 2026 12:10:20 -0700 Subject: [PATCH] Port: Fix flat type hierarchy false positives (#702) Upstream sha 84728e9ce43a3dba2a453b20130bbf836190d77c. The rule now reads rendered semantic roles and the dominant size per role instead of the raw set of font sizes on the page, and it fires only when every adjacent role step is under 1.25x. - crates/core checks::rules gains TYPE_HIERARCHY_SELECTOR / MIN_ROLES / MIN_STEP_RATIO, typeHierarchyRole, dominantTypeRoleSize and checkFlatTypeHierarchySamples, the shared half of checks.mjs. - crates/core browser::page_checks gets checkFlatTypeHierarchyFromDoc over the Dom trait, with the overlay skip selector checkTypography passes. - crates/html page.rs gets the same walk over StaticDocument. - crates/detect drops the source-only analyzer: flat-type-hierarchy leaves REGEX_ANALYZERS, the text-content analyzers shift to index 1, and analyzer_rule_id loses its first row. - crates/html cascade defaults gain contentVisibility, and crates/foundation registry carries the reworded description. Goldens re-recorded (the binary now matches origin/main's JS engine on every one of these fixtures, verified by scanning the shared corpus with both): glow, icon-tile-stack, layout, modern-color-borders, motion, named-color-borders, numbered-section-markers, oklch-neon-text, typography-should-flag, json and text. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY --- crates/core/src/browser/page_checks.rs | 150 +++++++++++++----- crates/core/src/checks/rules.rs | 107 +++++++++++++ crates/detect/src/detect_text.rs | 5 +- crates/detect/src/regex_matchers.rs | 110 +------------ crates/foundation/src/registry.rs | 2 +- crates/html/src/cascade/defaults.rs | 1 + crates/html/src/page.rs | 91 +++++++---- .../golden/detect-fixture-json-glow-html.json | 2 +- ...ect-fixture-json-icon-tile-stack-html.json | 2 +- .../detect-fixture-json-layout-html.json | 2 +- ...ixture-json-modern-color-borders-html.json | 2 +- .../detect-fixture-json-motion-html.json | 2 +- ...fixture-json-named-color-borders-html.json | 2 +- ...re-json-numbered-section-markers-html.json | 4 +- ...ect-fixture-json-oklch-neon-text-html.json | 2 +- ...ture-json-typography-should-flag-html.json | 2 +- .../golden/detect-fixture-text-glow-html.json | 2 +- ...ect-fixture-text-icon-tile-stack-html.json | 2 +- .../detect-fixture-text-layout-html.json | 2 +- ...ixture-text-modern-color-borders-html.json | 2 +- .../detect-fixture-text-motion-html.json | 2 +- ...fixture-text-named-color-borders-html.json | 2 +- ...re-text-numbered-section-markers-html.json | 4 +- ...ect-fixture-text-oklch-neon-text-html.json | 2 +- ...ture-text-typography-should-flag-html.json | 2 +- 25 files changed, 305 insertions(+), 201 deletions(-) diff --git a/crates/core/src/browser/page_checks.rs b/crates/core/src/browser/page_checks.rs index c738be1f0..df0040fbf 100644 --- a/crates/core/src/browser/page_checks.rs +++ b/crates/core/src/browser/page_checks.rs @@ -7,8 +7,8 @@ #![allow(unused_imports)] use super::dom::{ - class_attr, closest_or_none, direct_text, has_direct_text_longer_than, pf0, style_px, - tag_lower, Dom, ElId, ElStyle, Rect, + ancestors_inclusive, class_attr, closest_or_none, direct_text, has_direct_text_longer_than, pf0, + style_px, tag_lower, Dom, ElId, ElStyle, Rect, }; use super::element_checks::{class_selector, effective_opacity_dom, is_rendered_for_browser_rule}; use super::{BrowserFinding, ElFinding}; @@ -16,7 +16,10 @@ use crate::checks::measures::{ cream_from_class_list, is_cream_color, is_opaque_decorated_box, is_screen_reader_only_text_style, SrOnlyMetrics, StyleMap, }; -use crate::checks::rules::{is_card_like_from_props, RuleHit}; +use crate::checks::rules::{ + check_flat_type_hierarchy_samples, is_card_like_from_props, type_hierarchy_role, RuleHit, + TypeSample, TYPE_HIERARCHY_SELECTOR, +}; use crate::color::parse_any_color; use crate::constants::{is_brand_font_on_own_domain, CSS_GENERIC_FONTS, OVERUSED_FONTS, SAFE_TAGS}; use crate::js::{self, math_max, math_min, math_round, number_to_string, parse_float, to_fixed}; @@ -162,42 +165,72 @@ pub fn check_typography(dom: &dyn Dom) -> Vec { } } - let mut sizes: Vec = Vec::new(); - for el in dom - .query_all(None, "h1,h2,h3,h4,h5,h6,p,span,a,li,td,th,label,button,div") - .unwrap_or_default() - { - let fs = parse_float(&dom.style(el, "fontSize")); - if fs > 0.0 && fs < 200.0 { - let v = math_round(fs * 10.0) / 10.0; - if !sizes.iter().any(|s| *s == v) { - sizes.push(v); - } - } - } - if sizes.len() >= 3 { - let mut sorted = sizes.clone(); - sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)); - let ratio = sorted[sorted.len() - 1] / sorted[0]; - if ratio < 2.0 { - findings.push(BrowserFinding::new( - "flat-type-hierarchy", - format!( - "Sizes: {} (ratio {}:1)", - sorted - .iter() - .map(|s| format!("{}px", number_to_string(*s))) - .collect::>() - .join(", "), - to_fixed(ratio, 1) - ), - )); - } + for hit in check_flat_type_hierarchy_from_dom(dom, Some(TYPE_HIERARCHY_SKIP_SELECTOR)) { + findings.push(BrowserFinding::new(&hit.id, hit.snippet)); } findings } +/// The overlay chrome `checkTypography` hands `checkFlatTypeHierarchyFromDoc` +/// as its `skipElement` selector. +pub const TYPE_HIERARCHY_SKIP_SELECTOR: &str = + ".impeccable-overlay, .impeccable-label, .impeccable-banner, .impeccable-tooltip, [id^=\"impeccable-live-\"]"; + +/// JS: checks.mjs#isRenderedTypeElement over a live DOM. +fn is_rendered_type_element(dom: &dyn Dom, el: ElId) -> bool { + for current in ancestors_inclusive(dom, el) { + if dom.hidden_prop(current) || dom.attr(current, "hidden").is_some() { + return false; + } + let display = js::to_lower_case(&dom.style(current, "display")); + let visibility = js::to_lower_case(&dom.style(current, "visibility")); + let content_visibility = js::to_lower_case(&dom.style(current, "contentVisibility")); + if display == "none" + || visibility == "hidden" + || visibility == "collapse" + || content_visibility == "hidden" + { + return false; + } + let opacity = parse_float(&dom.style(current, "opacity")); + if opacity.is_finite() && opacity <= 0.01 { + return false; + } + } + true +} + +/// JS: checks.mjs#checkFlatTypeHierarchyFromDoc over a live DOM. +pub fn check_flat_type_hierarchy_from_dom( + dom: &dyn Dom, + skip_selector: Option<&str>, +) -> Vec { + let mut samples: Vec = Vec::new(); + for el in dom + .query_all(None, TYPE_HIERARCHY_SELECTOR) + .unwrap_or_default() + { + if let Some(sel) = skip_selector { + if closest_or_none(dom, el, sel).is_some() { + continue; + } + } + if js::trim(&dom.text_content(el)).is_empty() || !is_rendered_type_element(dom, el) { + continue; + } + let font_size = parse_float(&dom.style(el, "fontSize")); + if !font_size.is_finite() || font_size < 8.0 || font_size >= 200.0 { + continue; + } + samples.push(TypeSample { + role: type_hierarchy_role(&tag_lower(dom, el)), + size: font_size, + }); + } + check_flat_type_hierarchy_samples(&samples) +} + /// JS: checks.mjs#isCardLikeDOM(el) pub fn is_card_like_dom(dom: &dyn Dom, el: ElId) -> bool { let tag = tag_lower(dom, el); @@ -1404,11 +1437,54 @@ mod tests { d.set_style(s, "fontFamily", "Georgia"); d.set_style(s, "fontSize", "24px"); let f = check_typography(&d); - assert_eq!(f.len(), 2, "{f:?}"); + // One `body` role is under TYPE_HIERARCHY_MIN_ROLES, so the flat-type + // rule abstains and only the font finding stands (#702). + assert_eq!(f.len(), 1, "{f:?}"); assert_eq!(f[0].type_, "overused-font"); assert_eq!(f[0].detail, "Primary font: inter (95% of text)"); - assert_eq!(f[1].type_, "flat-type-hierarchy"); - assert_eq!(f[1].detail, "Sizes: 16px, 18px, 24px (ratio 1.5:1)"); + } + + #[test] + fn flat_type_hierarchy_roles() { + let mut d = FakeDom::new(); + let (_h, body) = d.with_page(); + for (tag, size) in [("p", "16px"), ("p", "16px"), ("h2", "17px"), ("h1", "18px")] { + let el = d.add(Some(body), tag); + d.add_text(el, "text"); + d.set_style(el, "fontSize", size); + } + let f = check_typography(&d); + assert_eq!(f.len(), 1, "{f:?}"); + assert_eq!(f[0].type_, "flat-type-hierarchy"); + assert_eq!( + f[0].detail, + "Role sizes: body 16px, h2 17px, h1 18px (largest adjacent step 1.06:1; target 1.25:1)" + ); + + // A clear step at any adjacent pair clears the rule. + let mut d = FakeDom::new(); + let (_h, body) = d.with_page(); + for (tag, size) in [("p", "16px"), ("h2", "24px"), ("h1", "40px")] { + let el = d.add(Some(body), tag); + d.add_text(el, "text"); + d.set_style(el, "fontSize", size); + } + assert!(check_typography(&d).is_empty()); + + // A hidden ancestor takes its text out of the sample. + let mut d = FakeDom::new(); + let (_h, body) = d.with_page(); + for (tag, size) in [("p", "16px"), ("p", "16px"), ("h2", "17px")] { + let el = d.add(Some(body), tag); + d.add_text(el, "text"); + d.set_style(el, "fontSize", size); + } + let wrap = d.add(Some(body), "div"); + d.set_style(wrap, "display", "none"); + let h1 = d.add(Some(wrap), "h1"); + d.add_text(h1, "text"); + d.set_style(h1, "fontSize", "18px"); + assert!(check_typography(&d).is_empty()); } #[test] diff --git a/crates/core/src/checks/rules.rs b/crates/core/src/checks/rules.rs index 39d25784c..a1dbfff40 100644 --- a/crates/core/src/checks/rules.rs +++ b/crates/core/src/checks/rules.rs @@ -794,6 +794,113 @@ pub fn check_glow(opts: &GlowOpts) -> Vec { } } +// ─── Section 6 shared: flat type hierarchy ────────────────────────────────── + +/// JS: checks.mjs#TYPE_HIERARCHY_SELECTOR +pub const TYPE_HIERARCHY_SELECTOR: &str = "h1,h2,h3,h4,h5,h6,p,li,td,th,dd,blockquote,figcaption"; +/// JS: checks.mjs#TYPE_HIERARCHY_MIN_ROLES +pub const TYPE_HIERARCHY_MIN_ROLES: usize = 3; +/// JS: checks.mjs#TYPE_HIERARCHY_MIN_STEP_RATIO +pub const TYPE_HIERARCHY_MIN_STEP_RATIO: f64 = 1.25; + +/// One `{ role, size }` entry the JS pushes into `samples`. +#[derive(Debug, Clone, PartialEq)] +pub struct TypeSample { + pub role: String, + pub size: f64, +} + +/// JS: checks.mjs#typeHierarchyRole +pub fn type_hierarchy_role(tag: &str) -> String { + let tag = js::to_lower_case(tag); + let b = tag.as_bytes(); + if b.len() == 2 && b[0] == b'h' && (b'1'..=b'6').contains(&b[1]) { + tag + } else { + "body".to_string() + } +} + +/// JS: checks.mjs#dominantTypeRoleSize +fn dominant_type_role_size(samples: &[f64]) -> Option { + // `new Map()` keeps insertion order; the JS sorts by count desc then size asc. + let mut counts: Vec<(f64, f64)> = Vec::new(); + for size in samples { + match counts + .iter_mut() + .find(|(k, _)| crate::js_ext_b::same_value_zero(*k, *size)) + { + Some(slot) => slot.1 += 1.0, + None => counts.push((*size, 1.0)), + } + } + let mut ranked = counts; + ranked.sort_by(|a, b| { + b.1.partial_cmp(&a.1) + .unwrap_or(std::cmp::Ordering::Equal) + .then(a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal)) + }); + if ranked.len() > 1 && ranked[0].1 == ranked[1].1 { + return None; + } + ranked.first().map(|(size, _)| *size) +} + +/// JS: checks.mjs#checkFlatTypeHierarchySamples +pub fn check_flat_type_hierarchy_samples(samples: &[TypeSample]) -> Vec { + // `new Map()` keyed by role, in first-seen order. + let mut by_role: Vec<(String, Vec)> = Vec::new(); + for sample in samples { + let size = math_round(sample.size * 10.0) / 10.0; + if sample.role.is_empty() || !size.is_finite() || size < 8.0 || size >= 200.0 { + continue; + } + match by_role.iter_mut().find(|(r, _)| *r == sample.role) { + Some(slot) => slot.1.push(size), + None => by_role.push((sample.role.clone(), vec![size])), + } + } + + let mut roles: Vec<(String, f64)> = by_role + .into_iter() + .filter_map(|(role, sizes)| dominant_type_role_size(&sizes).map(|size| (role, size))) + .collect(); + + if roles.len() < TYPE_HIERARCHY_MIN_ROLES { + return Vec::new(); + } + + roles.sort_by(|a, b| { + a.1.partial_cmp(&b.1) + .unwrap_or(std::cmp::Ordering::Equal) + // JS-PARITY: checks.mjs sorts ties with `a.role.localeCompare(b.role)`. + // Every role is `body` or `h1`..`h6`, lowercase ASCII, where the ICU + // root collation and byte order agree. + .then_with(|| a.0.cmp(&b.0)) + }); + let mut largest_step = 1.0f64; + for i in 1..roles.len() { + largest_step = math_max(largest_step, roles[i].1 / roles[i - 1].1); + } + if largest_step >= TYPE_HIERARCHY_MIN_STEP_RATIO { + return Vec::new(); + } + + let role_sizes: Vec = roles + .iter() + .map(|(role, size)| format!("{} {}px", role, number_to_string(*size))) + .collect(); + vec![RuleHit::new( + "flat-type-hierarchy", + format!( + "Role sizes: {} (largest adjacent step {}:1; target {}:1)", + role_sizes.join(", "), + to_fixed(largest_step, 2), + number_to_string(TYPE_HIERARCHY_MIN_STEP_RATIO) + ), + )] +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/detect/src/detect_text.rs b/crates/detect/src/detect_text.rs index b8a563f56..abcce7006 100644 --- a/crates/detect/src/detect_text.rs +++ b/crates/detect/src/detect_text.rs @@ -1367,8 +1367,11 @@ pub fn run_text_content_analyzers( return vec![]; } let mut findings = Vec::new(); + // JS: the 3 text-content analyzers sit at indices 1-3 of REGEX_ANALYZERS. + // flat-type-hierarchy left this source-only path in #702 because it needs + // rendered role and usage evidence. for (i, rule_id) in TEXT_CONTENT_ANALYZER_IDS.iter().enumerate() { - let analyzer = REGEX_ANALYZERS[2 + i]; + let analyzer = REGEX_ANALYZERS[1 + i]; let meta = ProfileMeta { engine: "regex", phase: "text-content", diff --git a/crates/detect/src/regex_matchers.rs b/crates/detect/src/regex_matchers.rs index 2cda16132..a30f6e6ff 100644 --- a/crates/detect/src/regex_matchers.rs +++ b/crates/detect/src/regex_matchers.rs @@ -9,7 +9,7 @@ use impeccable_core::constants::{EM_DASH_CHARS_PER_DASH, EM_DASH_FLOOR, OVERUSED use impeccable_core::findings::{finding, Finding}; use impeccable_core::fonts::extract_google_font_families; use impeccable_core::js::{ - self, ci, math_round, number_to_string, parse_float, string_to_number, to_fixed, + self, ci, math_round, number_to_string, parse_float, string_to_number, }; use impeccable_core::js_ext_a::{advance_utf16, slice_utf16_start, utf16_index, utf16_length}; use once_cell::sync::Lazy; @@ -787,33 +787,6 @@ fn utf16_slice(s: &str, start: usize, end: usize) -> String { pub type Analyzer = fn(&str, &str) -> Vec; -re!( - FONT_SIZE_RE, - format!( - "{}{WS}*:{WS}*([0-9.]+)({px}|{rem}|{em}){B}", - ci("font-size"), - px = ci("px"), - rem = ci("rem"), - em = ci("em") - ) -); -re!( - CLAMP_SIZE_RE, - format!( - "{fs}{WS}*:{WS}*{clamp}\\({WS}*([0-9.]+)({px}|{rem}|{em}){WS}*,{WS}*[^,]+,{WS}*([0-9.]+)({px}|{rem}|{em}){WS}*\\)", - fs = ci("font-size"), - clamp = ci("clamp"), - px = ci("px"), - rem = ci("rem"), - em = ci("em") - ) -); -re!(FONT_SIZE_WORD_RE, ci("font-size")); -re!( - TEXT_SIZE_CLASS_RE, - format!("{B}{}(?:xs|sm|base|lg|xl|[0-9])", ci("text-")) -); - fn same_value_zero(a: f64, b: f64) -> bool { (a.is_nan() && b.is_nan()) || a == b } @@ -824,85 +797,6 @@ fn set_add(set: &mut Vec, v: f64) { } } -fn analyze_flat_type_hierarchy(content: &str, file_path: &str) -> Vec { - let mut sizes: Vec = Vec::new(); - let rem = 16.0; - for m in FONT_SIZE_RE.captures_iter(content) { - let px = if &m[2] == "px" { - num(&m[1]) - } else { - num(&m[1]) * rem - }; - if px > 0.0 && px < 200.0 { - set_add(&mut sizes, math_round(px * 10.0) / 10.0); - } - } - for m in CLAMP_SIZE_RE.captures_iter(content) { - let a = if &m[2] == "px" { - num(&m[1]) - } else { - num(&m[1]) * rem - }; - set_add(&mut sizes, math_round(a * 10.0) / 10.0); - let b = if &m[4] == "px" { - num(&m[3]) - } else { - num(&m[3]) * rem - }; - set_add(&mut sizes, math_round(b * 10.0) / 10.0); - } - const TW: &[(&str, f64)] = &[ - ("text-xs", 12.0), - ("text-sm", 14.0), - ("text-base", 16.0), - ("text-lg", 18.0), - ("text-xl", 20.0), - ("text-2xl", 24.0), - ("text-3xl", 30.0), - ("text-4xl", 36.0), - ("text-5xl", 48.0), - ("text-6xl", 60.0), - ("text-7xl", 72.0), - ("text-8xl", 96.0), - ("text-9xl", 128.0), - ]; - for (cls, px) in TW { - let re = Regex::new(&format!("{B}{cls}{B}")).unwrap(); - if re.is_match(content) { - set_add(&mut sizes, *px); - } - } - if sizes.len() < 3 { - return vec![]; - } - let mut sorted = sizes.clone(); - sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)); - let ratio = sorted[sorted.len() - 1] / sorted[0]; - if ratio >= 2.0 { - return vec![]; - } - let mut line = 1; - for (i, l) in content.split('\n').enumerate() { - if FONT_SIZE_WORD_RE.is_match(l) || TEXT_SIZE_CLASS_RE.is_match(l) { - line = i + 1; - break; - } - } - let list: Vec = sorted - .iter() - .map(|s| format!("{}px", number_to_string(*s))) - .collect(); - vec![finding( - "flat-type-hierarchy", - file_path, - &format!( - "Sizes: {} (ratio {}:1)", - list.join(", "), - to_fixed(ratio, 1) - ), - line as f64, - )] -} re!( SPACING_PX_RE, @@ -1220,7 +1114,6 @@ fn analyze_marquee(content: &str, file_path: &str) -> Vec { /// JS `REGEX_ANALYZERS` in order. pub const REGEX_ANALYZERS: &[Analyzer] = &[ - analyze_flat_type_hierarchy, analyze_monotonous_spacing, analyze_em_dash_overuse, analyze_marketing_buzzword, @@ -1240,7 +1133,6 @@ pub const TEXT_CONTENT_ANALYZER_IDS: &[&str] = &[ /// The ruleIds the JS assigns to analyzers by index (`analyzerIds[i] || analyzer-${i+1}`). pub fn analyzer_rule_id(i: usize) -> String { const IDS: &[&str] = &[ - "flat-type-hierarchy", "monotonous-spacing", "em-dash-overuse", "marketing-buzzword", diff --git a/crates/foundation/src/registry.rs b/crates/foundation/src/registry.rs index 9120bbdc1..0535727df 100644 --- a/crates/foundation/src/registry.rs +++ b/crates/foundation/src/registry.rs @@ -73,7 +73,7 @@ pub static ANTIPATTERNS: &[Antipattern] = &[ severity: None, advisory: false, name: "Flat type hierarchy", - description: "Font sizes are too close together — no clear visual hierarchy. Use fewer sizes with more contrast (aim for at least a 1.25 ratio between steps).", + description: "Dominant heading and body roles are separated by less than 1.25× at every step, leaving the size hierarchy flat. Add at least one stronger size step.", skill_section: Some("Typography"), skill_guideline: Some("flat type hierarchy"), }, diff --git a/crates/html/src/cascade/defaults.rs b/crates/html/src/cascade/defaults.rs index ed8b2a42b..5abc3c5c1 100644 --- a/crates/html/src/cascade/defaults.rs +++ b/crates/html/src/cascade/defaults.rs @@ -109,6 +109,7 @@ pub const STATIC_DEFAULT_STYLE: &[(&str, &str)] = &[ ("marginLeft", "0px"), ("position", "static"), ("visibility", "visible"), + ("contentVisibility", "visible"), ("opacity", "1"), ("top", "auto"), ("right", "auto"), diff --git a/crates/html/src/page.rs b/crates/html/src/page.rs index 7224a64ca..871f294a4 100644 --- a/crates/html/src/page.rs +++ b/crates/html/src/page.rs @@ -8,12 +8,15 @@ use crate::background::{read_own_background_color, resolve_border_radius_px, sv} use crate::dom::{StaticDocument, StaticElement}; use crate::quality::{has_nonblank_direct_text, pf0}; use impeccable_core::checks::measures::{cream_from_class_list, is_cream_color}; -use impeccable_core::checks::rules::{is_card_like_from_props, RuleHit}; +use impeccable_core::checks::rules::{ + check_flat_type_hierarchy_samples, is_card_like_from_props, type_hierarchy_role, RuleHit, + TypeSample, TYPE_HIERARCHY_SELECTOR, +}; use impeccable_core::checks::text_rules::{ is_repeated_text_container, REPEATED_TEXT_CONTAINER_TAGS, REPEATED_TEXT_SKIP_SELECTOR, }; use impeccable_core::constants::{CSS_GENERIC_FONTS, OVERUSED_FONTS, SAFE_TAGS}; -use impeccable_core::js::{self, math_round, number_to_string, parse_float, to_fixed}; +use impeccable_core::js::{self, number_to_string, parse_float}; use impeccable_core::js_ext_b::{slice_utf16_prefix, utf16_len}; use once_cell::sync::Lazy; use regex::Regex; @@ -60,40 +63,62 @@ pub fn check_static_page_typography(doc: &StaticDocument) -> Vec { format!("Primary font: {}", font), )); } - let mut sizes: Vec = Vec::new(); - for el in - doc.query_selector_all("h1, h2, h3, h4, h5, h6, p, span, a, li, td, th, label, button, div") - { - let font_size = parse_float(sv(el.style(), "fontSize")); - if font_size >= 8.0 && font_size < 200.0 { - let v = math_round(font_size * 10.0) / 10.0; - if !sizes.contains(&v) { - sizes.push(v); - } - } - } - if sizes.len() >= 3 { - let mut sorted = sizes.clone(); - sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)); - let ratio = sorted[sorted.len() - 1] / sorted[0]; - if ratio < 2.0 { - let list: Vec = sorted - .iter() - .map(|s| format!("{}px", number_to_string(*s))) - .collect(); - findings.push(RuleHit::new( - "flat-type-hierarchy", - format!( - "Sizes: {} (ratio {}:1)", - list.join(", "), - to_fixed(ratio, 1) - ), - )); - } - } + findings.extend(check_flat_type_hierarchy_from_doc(doc)); findings } +/// JS: checks.mjs#isRenderedTypeElement over the static cascade. +/// +/// JS-PARITY: jsdom's `el.hidden` reflects the `hidden` attribute, which the +/// attribute test already covers. `contentVisibility` only ever reads its +/// `STATIC_DEFAULT_STYLE` default here: css-cascade.mjs#STATIC_PROP_MAP has no +/// `content-visibility` entry, so a declared `content-visibility: hidden` +/// never reaches the static computed style. +fn is_rendered_type_element(el: &StaticElement<'_>) -> bool { + let mut current = Some(el.clone()); + while let Some(node) = current { + if node.get_attribute("hidden").is_some() { + return false; + } + let style = node.style(); + let display = js::to_lower_case(sv(style, "display")); + let visibility = js::to_lower_case(sv(style, "visibility")); + let content_visibility = js::to_lower_case(sv(style, "contentVisibility")); + if display == "none" + || visibility == "hidden" + || visibility == "collapse" + || content_visibility == "hidden" + { + return false; + } + let opacity = parse_float(sv(style, "opacity")); + if opacity.is_finite() && opacity <= 0.01 { + return false; + } + current = node.parent_element(); + } + true +} + +/// JS: checks.mjs#checkFlatTypeHierarchyFromDoc over the static document. +pub fn check_flat_type_hierarchy_from_doc(doc: &StaticDocument) -> Vec { + let mut samples: Vec = Vec::new(); + for el in doc.query_selector_all(TYPE_HIERARCHY_SELECTOR) { + if js::trim(&el.text_content()).is_empty() || !is_rendered_type_element(&el) { + continue; + } + let font_size = parse_float(sv(el.style(), "fontSize")); + if !font_size.is_finite() || font_size < 8.0 || font_size >= 200.0 { + continue; + } + samples.push(TypeSample { + role: type_hierarchy_role(&el.tag_lower()), + size: font_size, + }); + } + check_flat_type_hierarchy_samples(&samples) +} + // ─── Nested cards ─────────────────────────────────────────────────────────── static SHADOW_CLASS_RE: Lazy = Lazy::new(|| { diff --git a/tests/oracle/golden/detect-fixture-json-glow-html.json b/tests/oracle/golden/detect-fixture-json-glow-html.json index bd931a04a..db8ea8c97 100644 --- a/tests/oracle/golden/detect-fixture-json-glow-html.json +++ b/tests/oracle/golden/detect-fixture-json-glow-html.json @@ -1,5 +1,5 @@ { - "stdout": "[\n {\n \"antipattern\": \"dark-glow\",\n \"name\": \"Glowing shadow accents\",\n \"description\": \"Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \\\"cool\\\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Zero-offset box-shadow glow (#3b82f6)\"\n },\n {\n \"antipattern\": \"dark-glow\",\n \"name\": \"Glowing shadow accents\",\n \"description\": \"Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \\\"cool\\\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Zero-offset box-shadow glow (#8b5cf6)\"\n },\n {\n \"antipattern\": \"dark-glow\",\n \"name\": \"Glowing shadow accents\",\n \"description\": \"Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \\\"cool\\\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Zero-offset box-shadow glow (#06b6d4)\"\n },\n {\n \"antipattern\": \"dark-glow\",\n \"name\": \"Glowing shadow accents\",\n \"description\": \"Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \\\"cool\\\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Zero-offset box-shadow glow (#a855f7)\"\n },\n {\n \"antipattern\": \"dark-glow\",\n \"name\": \"Glowing shadow accents\",\n \"description\": \"Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \\\"cool\\\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Zero-offset box-shadow glow (#ec4899)\"\n },\n {\n \"antipattern\": \"dark-glow\",\n \"name\": \"Glowing shadow accents\",\n \"description\": \"Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \\\"cool\\\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Zero-offset box-shadow glow (#00c0c2)\"\n },\n {\n \"antipattern\": \"dark-glow\",\n \"name\": \"Glowing shadow accents\",\n \"description\": \"Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \\\"cool\\\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Zero-offset box-shadow glow (#3b82f6)\"\n },\n {\n \"antipattern\": \"dark-glow\",\n \"name\": \"Glowing shadow accents\",\n \"description\": \"Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \\\"cool\\\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Zero-offset box-shadow glow (#b447eb)\"\n },\n {\n \"antipattern\": \"dark-glow\",\n \"name\": \"Glowing shadow accents\",\n \"description\": \"Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \\\"cool\\\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Zero-offset box-shadow glow (#63d18f)\"\n },\n {\n \"antipattern\": \"dark-glow\",\n \"name\": \"Glowing shadow accents\",\n \"description\": \"Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \\\"cool\\\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Zero-offset text-shadow glow (#22d3ee)\"\n },\n {\n \"antipattern\": \"dark-glow\",\n \"name\": \"Glowing shadow accents\",\n \"description\": \"Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \\\"cool\\\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Zero-offset box-shadow glow (#a46bf5)\"\n },\n {\n \"antipattern\": \"dark-glow\",\n \"name\": \"Glowing shadow accents\",\n \"description\": \"Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \\\"cool\\\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Zero-offset box-shadow glow (#d946ef)\"\n },\n {\n \"antipattern\": \"flat-type-hierarchy\",\n \"name\": \"Flat type hierarchy\",\n \"description\": \"Font sizes are too close together — no clear visual hierarchy. Use fewer sizes with more contrast (aim for at least a 1.25 ratio between steps).\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Sizes: 11px, 12px, 13px, 14px, 16px (ratio 1.5:1)\"\n },\n {\n \"antipattern\": \"buried-raster\",\n \"name\": \"Raster buried under a wash or opacity\",\n \"description\": \"A background image under a near-opaque gradient wash, or a raster on an element at near-zero opacity, never reaches the screen: the page shows the wash, and the produced texture or photo ships as a compliance token. Let the material show (a tint under 0.9 alpha, a blend mode, an opacity you can see) or remove the file.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"raster under a near-opaque gradient wash: linear-gradient(#111827, #0b1220), url('/fixtures/antipatterns/missing-photo.png')\"\n },\n {\n \"antipattern\": \"dark-glow\",\n \"name\": \"Glowing shadow accents\",\n \"description\": \"Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \\\"cool\\\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Zero-offset box-shadow glow (#3b82f6)\"\n }\n]\n", + "stdout": "[\n {\n \"antipattern\": \"dark-glow\",\n \"name\": \"Glowing shadow accents\",\n \"description\": \"Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \\\"cool\\\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Zero-offset box-shadow glow (#3b82f6)\"\n },\n {\n \"antipattern\": \"dark-glow\",\n \"name\": \"Glowing shadow accents\",\n \"description\": \"Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \\\"cool\\\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Zero-offset box-shadow glow (#8b5cf6)\"\n },\n {\n \"antipattern\": \"dark-glow\",\n \"name\": \"Glowing shadow accents\",\n \"description\": \"Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \\\"cool\\\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Zero-offset box-shadow glow (#06b6d4)\"\n },\n {\n \"antipattern\": \"dark-glow\",\n \"name\": \"Glowing shadow accents\",\n \"description\": \"Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \\\"cool\\\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Zero-offset box-shadow glow (#a855f7)\"\n },\n {\n \"antipattern\": \"dark-glow\",\n \"name\": \"Glowing shadow accents\",\n \"description\": \"Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \\\"cool\\\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Zero-offset box-shadow glow (#ec4899)\"\n },\n {\n \"antipattern\": \"dark-glow\",\n \"name\": \"Glowing shadow accents\",\n \"description\": \"Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \\\"cool\\\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Zero-offset box-shadow glow (#00c0c2)\"\n },\n {\n \"antipattern\": \"dark-glow\",\n \"name\": \"Glowing shadow accents\",\n \"description\": \"Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \\\"cool\\\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Zero-offset box-shadow glow (#3b82f6)\"\n },\n {\n \"antipattern\": \"dark-glow\",\n \"name\": \"Glowing shadow accents\",\n \"description\": \"Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \\\"cool\\\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Zero-offset box-shadow glow (#b447eb)\"\n },\n {\n \"antipattern\": \"dark-glow\",\n \"name\": \"Glowing shadow accents\",\n \"description\": \"Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \\\"cool\\\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Zero-offset box-shadow glow (#63d18f)\"\n },\n {\n \"antipattern\": \"dark-glow\",\n \"name\": \"Glowing shadow accents\",\n \"description\": \"Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \\\"cool\\\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Zero-offset text-shadow glow (#22d3ee)\"\n },\n {\n \"antipattern\": \"dark-glow\",\n \"name\": \"Glowing shadow accents\",\n \"description\": \"Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \\\"cool\\\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Zero-offset box-shadow glow (#a46bf5)\"\n },\n {\n \"antipattern\": \"dark-glow\",\n \"name\": \"Glowing shadow accents\",\n \"description\": \"Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \\\"cool\\\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Zero-offset box-shadow glow (#d946ef)\"\n },\n {\n \"antipattern\": \"flat-type-hierarchy\",\n \"name\": \"Flat type hierarchy\",\n \"description\": \"Dominant heading and body roles are separated by less than 1.25× at every step, leaving the size hierarchy flat. Add at least one stronger size step.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Role sizes: h3 11px, body 12px, h4 13px, h2 14px (largest adjacent step 1.09:1; target 1.25:1)\"\n },\n {\n \"antipattern\": \"buried-raster\",\n \"name\": \"Raster buried under a wash or opacity\",\n \"description\": \"A background image under a near-opaque gradient wash, or a raster on an element at near-zero opacity, never reaches the screen: the page shows the wash, and the produced texture or photo ships as a compliance token. Let the material show (a tint under 0.9 alpha, a blend mode, an opacity you can see) or remove the file.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"raster under a near-opaque gradient wash: linear-gradient(#111827, #0b1220), url('/fixtures/antipatterns/missing-photo.png')\"\n },\n {\n \"antipattern\": \"dark-glow\",\n \"name\": \"Glowing shadow accents\",\n \"description\": \"Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \\\"cool\\\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/glow.html\",\n \"line\": 0,\n \"snippet\": \"Zero-offset box-shadow glow (#3b82f6)\"\n }\n]\n", "stderr": "", "exit": 2, "signal": null, diff --git a/tests/oracle/golden/detect-fixture-json-icon-tile-stack-html.json b/tests/oracle/golden/detect-fixture-json-icon-tile-stack-html.json index 7a1a5dd26..aae8d6997 100644 --- a/tests/oracle/golden/detect-fixture-json-icon-tile-stack-html.json +++ b/tests/oracle/golden/detect-fixture-json-icon-tile-stack-html.json @@ -1,5 +1,5 @@ { - "stdout": "[\n {\n \"antipattern\": \"icon-tile-stack\",\n \"name\": \"Icon tile stacked above heading\",\n \"description\": \"A small rounded-square icon container above a heading is the universal AI feature-card template — every generator outputs this exact shape. Try a side-by-side icon and heading, or let the icon sit in flow without its own container.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/icon-tile-stack.html\",\n \"line\": 0,\n \"snippet\": \"80x80px icon tile above h3 \\\"Lightning Fast\\\"\"\n },\n {\n \"antipattern\": \"icon-tile-stack\",\n \"name\": \"Icon tile stacked above heading\",\n \"description\": \"A small rounded-square icon container above a heading is the universal AI feature-card template — every generator outputs this exact shape. Try a side-by-side icon and heading, or let the icon sit in flow without its own container.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/icon-tile-stack.html\",\n \"line\": 0,\n \"snippet\": \"64x64px icon tile above h3 \\\"Secure Storage\\\"\"\n },\n {\n \"antipattern\": \"icon-tile-stack\",\n \"name\": \"Icon tile stacked above heading\",\n \"description\": \"A small rounded-square icon container above a heading is the universal AI feature-card template — every generator outputs this exact shape. Try a side-by-side icon and heading, or let the icon sit in flow without its own container.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/icon-tile-stack.html\",\n \"line\": 0,\n \"snippet\": \"72x72px icon tile above h3 \\\"Easy Setup\\\"\"\n },\n {\n \"antipattern\": \"icon-tile-stack\",\n \"name\": \"Icon tile stacked above heading\",\n \"description\": \"A small rounded-square icon container above a heading is the universal AI feature-card template — every generator outputs this exact shape. Try a side-by-side icon and heading, or let the icon sit in flow without its own container.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/icon-tile-stack.html\",\n \"line\": 0,\n \"snippet\": \"96x96px icon tile above h3 \\\"Powerful Analytics\\\"\"\n },\n {\n \"antipattern\": \"icon-tile-stack\",\n \"name\": \"Icon tile stacked above heading\",\n \"description\": \"A small rounded-square icon container above a heading is the universal AI feature-card template — every generator outputs this exact shape. Try a side-by-side icon and heading, or let the icon sit in flow without its own container.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/icon-tile-stack.html\",\n \"line\": 0,\n \"snippet\": \"48x48px icon tile above h3 \\\"Emoji Inline Icon\\\"\"\n },\n {\n \"antipattern\": \"flat-type-hierarchy\",\n \"name\": \"Flat type hierarchy\",\n \"description\": \"Font sizes are too close together — no clear visual hierarchy. Use fewer sizes with more contrast (aim for at least a 1.25 ratio between steps).\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/icon-tile-stack.html\",\n \"line\": 0,\n \"snippet\": \"Sizes: 14px, 16px, 18px, 24px (ratio 1.7:1)\"\n },\n {\n \"antipattern\": \"ai-color-palette\",\n \"name\": \"AI color palette\",\n \"description\": \"Purple/violet gradients and cyan-on-dark are the most recognizable tells of AI-generated UIs. Choose a distinctive, intentional palette.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/icon-tile-stack.html\",\n \"line\": 0,\n \"snippet\": \"Purple/violet accent colors detected\"\n },\n {\n \"antipattern\": \"marketing-buzzword\",\n \"name\": \"Marketing buzzword\",\n \"description\": \"Generic SaaS phrases (streamline / empower / supercharge / world-class / enterprise-grade / next-generation / cutting-edge / etc) are instant AI tells. Pick a specific verb and noun that says what the product literally does.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/icon-tile-stack.html\",\n \"line\": 0,\n \"snippet\": \"1 buzzword phrase: \\\"ure Storage Enterprise-grade security fo\\\"\"\n }\n]\n", + "stdout": "[\n {\n \"antipattern\": \"icon-tile-stack\",\n \"name\": \"Icon tile stacked above heading\",\n \"description\": \"A small rounded-square icon container above a heading is the universal AI feature-card template — every generator outputs this exact shape. Try a side-by-side icon and heading, or let the icon sit in flow without its own container.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/icon-tile-stack.html\",\n \"line\": 0,\n \"snippet\": \"80x80px icon tile above h3 \\\"Lightning Fast\\\"\"\n },\n {\n \"antipattern\": \"icon-tile-stack\",\n \"name\": \"Icon tile stacked above heading\",\n \"description\": \"A small rounded-square icon container above a heading is the universal AI feature-card template — every generator outputs this exact shape. Try a side-by-side icon and heading, or let the icon sit in flow without its own container.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/icon-tile-stack.html\",\n \"line\": 0,\n \"snippet\": \"64x64px icon tile above h3 \\\"Secure Storage\\\"\"\n },\n {\n \"antipattern\": \"icon-tile-stack\",\n \"name\": \"Icon tile stacked above heading\",\n \"description\": \"A small rounded-square icon container above a heading is the universal AI feature-card template — every generator outputs this exact shape. Try a side-by-side icon and heading, or let the icon sit in flow without its own container.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/icon-tile-stack.html\",\n \"line\": 0,\n \"snippet\": \"72x72px icon tile above h3 \\\"Easy Setup\\\"\"\n },\n {\n \"antipattern\": \"icon-tile-stack\",\n \"name\": \"Icon tile stacked above heading\",\n \"description\": \"A small rounded-square icon container above a heading is the universal AI feature-card template — every generator outputs this exact shape. Try a side-by-side icon and heading, or let the icon sit in flow without its own container.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/icon-tile-stack.html\",\n \"line\": 0,\n \"snippet\": \"96x96px icon tile above h3 \\\"Powerful Analytics\\\"\"\n },\n {\n \"antipattern\": \"icon-tile-stack\",\n \"name\": \"Icon tile stacked above heading\",\n \"description\": \"A small rounded-square icon container above a heading is the universal AI feature-card template — every generator outputs this exact shape. Try a side-by-side icon and heading, or let the icon sit in flow without its own container.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/icon-tile-stack.html\",\n \"line\": 0,\n \"snippet\": \"48x48px icon tile above h3 \\\"Emoji Inline Icon\\\"\"\n },\n {\n \"antipattern\": \"ai-color-palette\",\n \"name\": \"AI color palette\",\n \"description\": \"Purple/violet gradients and cyan-on-dark are the most recognizable tells of AI-generated UIs. Choose a distinctive, intentional palette.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/icon-tile-stack.html\",\n \"line\": 0,\n \"snippet\": \"Purple/violet accent colors detected\"\n },\n {\n \"antipattern\": \"marketing-buzzword\",\n \"name\": \"Marketing buzzword\",\n \"description\": \"Generic SaaS phrases (streamline / empower / supercharge / world-class / enterprise-grade / next-generation / cutting-edge / etc) are instant AI tells. Pick a specific verb and noun that says what the product literally does.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/icon-tile-stack.html\",\n \"line\": 0,\n \"snippet\": \"1 buzzword phrase: \\\"ure Storage Enterprise-grade security fo\\\"\"\n }\n]\n", "stderr": "", "exit": 2, "signal": null, diff --git a/tests/oracle/golden/detect-fixture-json-layout-html.json b/tests/oracle/golden/detect-fixture-json-layout-html.json index 85afca9b2..d112a0321 100644 --- a/tests/oracle/golden/detect-fixture-json-layout-html.json +++ b/tests/oracle/golden/detect-fixture-json-layout-html.json @@ -1,5 +1,5 @@ { - "stdout": "[\n {\n \"antipattern\": \"flat-type-hierarchy\",\n \"name\": \"Flat type hierarchy\",\n \"description\": \"Font sizes are too close together — no clear visual hierarchy. Use fewer sizes with more contrast (aim for at least a 1.25 ratio between steps).\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/layout.html\",\n \"line\": 0,\n \"snippet\": \"Sizes: 11px, 14px, 16px (ratio 1.5:1)\"\n },\n {\n \"antipattern\": \"nested-cards\",\n \"name\": \"Nested cards\",\n \"description\": \"Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/layout.html\",\n \"line\": 0,\n \"snippet\": \"Card inside card (div)\"\n },\n {\n \"antipattern\": \"nested-cards\",\n \"name\": \"Nested cards\",\n \"description\": \"Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/layout.html\",\n \"line\": 0,\n \"snippet\": \"Card inside card (div)\"\n },\n {\n \"antipattern\": \"nested-cards\",\n \"name\": \"Nested cards\",\n \"description\": \"Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/layout.html\",\n \"line\": 0,\n \"snippet\": \"Card inside card (div)\"\n },\n {\n \"antipattern\": \"nested-cards\",\n \"name\": \"Nested cards\",\n \"description\": \"Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/layout.html\",\n \"line\": 0,\n \"snippet\": \"Card inside card (div)\"\n }\n]\n", + "stdout": "[\n {\n \"antipattern\": \"nested-cards\",\n \"name\": \"Nested cards\",\n \"description\": \"Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/layout.html\",\n \"line\": 0,\n \"snippet\": \"Card inside card (div)\"\n },\n {\n \"antipattern\": \"nested-cards\",\n \"name\": \"Nested cards\",\n \"description\": \"Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/layout.html\",\n \"line\": 0,\n \"snippet\": \"Card inside card (div)\"\n },\n {\n \"antipattern\": \"nested-cards\",\n \"name\": \"Nested cards\",\n \"description\": \"Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/layout.html\",\n \"line\": 0,\n \"snippet\": \"Card inside card (div)\"\n },\n {\n \"antipattern\": \"nested-cards\",\n \"name\": \"Nested cards\",\n \"description\": \"Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/layout.html\",\n \"line\": 0,\n \"snippet\": \"Card inside card (div)\"\n }\n]\n", "stderr": "", "exit": 2, "signal": null, diff --git a/tests/oracle/golden/detect-fixture-json-modern-color-borders-html.json b/tests/oracle/golden/detect-fixture-json-modern-color-borders-html.json index 3c73b2f61..040003088 100644 --- a/tests/oracle/golden/detect-fixture-json-modern-color-borders-html.json +++ b/tests/oracle/golden/detect-fixture-json-modern-color-borders-html.json @@ -1,5 +1,5 @@ { - "stdout": "[\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 3px + border-radius: 4px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 4px + border-radius: 4px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 4px + border-radius: 4px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 4px + border-radius: 6px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 4px + border-radius: 6px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 4px + border-radius: 6px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 3px + border-radius: 4px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 4px + border-radius: 6px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 5px + border-radius: 4px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 4px + border-radius: 4px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-right: 4px + border-radius: 4px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 4px + border-radius: 6px\"\n },\n {\n \"antipattern\": \"low-contrast\",\n \"name\": \"Low contrast text\",\n \"description\": \"Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"4.49:1 (need 4.5:1) — text #64748b on #fef7f2\"\n },\n {\n \"antipattern\": \"flat-type-hierarchy\",\n \"name\": \"Flat type hierarchy\",\n \"description\": \"Font sizes are too close together — no clear visual hierarchy. Use fewer sizes with more contrast (aim for at least a 1.25 ratio between steps).\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"Sizes: 13px, 14px, 16px (ratio 1.2:1)\"\n }\n]\n", + "stdout": "[\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 3px + border-radius: 4px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 4px + border-radius: 4px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 4px + border-radius: 4px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 4px + border-radius: 6px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 4px + border-radius: 6px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 4px + border-radius: 6px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 3px + border-radius: 4px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 4px + border-radius: 6px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 5px + border-radius: 4px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 4px + border-radius: 4px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-right: 4px + border-radius: 4px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 4px + border-radius: 6px\"\n },\n {\n \"antipattern\": \"low-contrast\",\n \"name\": \"Low contrast text\",\n \"description\": \"Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"4.49:1 (need 4.5:1) — text #64748b on #fef7f2\"\n },\n {\n \"antipattern\": \"flat-type-hierarchy\",\n \"name\": \"Flat type hierarchy\",\n \"description\": \"Dominant heading and body roles are separated by less than 1.25× at every step, leaving the size hierarchy flat. Add at least one stronger size step.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/modern-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"Role sizes: body 13px, h2 14px, h3 14px (largest adjacent step 1.08:1; target 1.25:1)\"\n }\n]\n", "stderr": "", "exit": 2, "signal": null, diff --git a/tests/oracle/golden/detect-fixture-json-motion-html.json b/tests/oracle/golden/detect-fixture-json-motion-html.json index 1f8377851..59314c0b4 100644 --- a/tests/oracle/golden/detect-fixture-json-motion-html.json +++ b/tests/oracle/golden/detect-fixture-json-motion-html.json @@ -1,5 +1,5 @@ { - "stdout": "[\n {\n \"antipattern\": \"bounce-easing\",\n \"name\": \"Bounce or elastic easing\",\n \"description\": \"Bounce and elastic easing feel dated and tacky. Real objects decelerate smoothly — use exponential easing (ease-out-quart/quint/expo) instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/motion.html\",\n \"line\": 0,\n \"snippet\": \"animation: bounce-keyframe\"\n },\n {\n \"antipattern\": \"bounce-easing\",\n \"name\": \"Bounce or elastic easing\",\n \"description\": \"Bounce and elastic easing feel dated and tacky. Real objects decelerate smoothly — use exponential easing (ease-out-quart/quint/expo) instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/motion.html\",\n \"line\": 0,\n \"snippet\": \"cubic-bezier(0.68, -0.55, 0.265, 1.55)\"\n },\n {\n \"antipattern\": \"layout-transition\",\n \"name\": \"Layout property animation\",\n \"description\": \"Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"/tests/fixtures/antipatterns/motion.html\",\n \"line\": 0,\n \"snippet\": \"transition: width\"\n },\n {\n \"antipattern\": \"layout-transition\",\n \"name\": \"Layout property animation\",\n \"description\": \"Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"/tests/fixtures/antipatterns/motion.html\",\n \"line\": 0,\n \"snippet\": \"transition: height\"\n },\n {\n \"antipattern\": \"layout-transition\",\n \"name\": \"Layout property animation\",\n \"description\": \"Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"/tests/fixtures/antipatterns/motion.html\",\n \"line\": 0,\n \"snippet\": \"transition: padding\"\n },\n {\n \"antipattern\": \"layout-transition\",\n \"name\": \"Layout property animation\",\n \"description\": \"Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"/tests/fixtures/antipatterns/motion.html\",\n \"line\": 0,\n \"snippet\": \"transition: margin\"\n },\n {\n \"antipattern\": \"layout-transition\",\n \"name\": \"Layout property animation\",\n \"description\": \"Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"/tests/fixtures/antipatterns/motion.html\",\n \"line\": 0,\n \"snippet\": \"transition: max-height\"\n },\n {\n \"antipattern\": \"layout-transition\",\n \"name\": \"Layout property animation\",\n \"description\": \"Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"/tests/fixtures/antipatterns/motion.html\",\n \"line\": 0,\n \"snippet\": \"transition: width, height\"\n },\n {\n \"antipattern\": \"layout-transition\",\n \"name\": \"Layout property animation\",\n \"description\": \"Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"/tests/fixtures/antipatterns/motion.html\",\n \"line\": 0,\n \"snippet\": \"transition: width\"\n },\n {\n \"antipattern\": \"layout-transition\",\n \"name\": \"Layout property animation\",\n \"description\": \"Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"/tests/fixtures/antipatterns/motion.html\",\n \"line\": 0,\n \"snippet\": \"transition: width\"\n },\n {\n \"antipattern\": \"flat-type-hierarchy\",\n \"name\": \"Flat type hierarchy\",\n \"description\": \"Font sizes are too close together — no clear visual hierarchy. Use fewer sizes with more contrast (aim for at least a 1.25 ratio between steps).\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/motion.html\",\n \"line\": 0,\n \"snippet\": \"Sizes: 11px, 12px, 13px, 14px, 16px (ratio 1.5:1)\"\n }\n]\n", + "stdout": "[\n {\n \"antipattern\": \"bounce-easing\",\n \"name\": \"Bounce or elastic easing\",\n \"description\": \"Bounce and elastic easing feel dated and tacky. Real objects decelerate smoothly — use exponential easing (ease-out-quart/quint/expo) instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/motion.html\",\n \"line\": 0,\n \"snippet\": \"animation: bounce-keyframe\"\n },\n {\n \"antipattern\": \"bounce-easing\",\n \"name\": \"Bounce or elastic easing\",\n \"description\": \"Bounce and elastic easing feel dated and tacky. Real objects decelerate smoothly — use exponential easing (ease-out-quart/quint/expo) instead.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/motion.html\",\n \"line\": 0,\n \"snippet\": \"cubic-bezier(0.68, -0.55, 0.265, 1.55)\"\n },\n {\n \"antipattern\": \"layout-transition\",\n \"name\": \"Layout property animation\",\n \"description\": \"Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"/tests/fixtures/antipatterns/motion.html\",\n \"line\": 0,\n \"snippet\": \"transition: width\"\n },\n {\n \"antipattern\": \"layout-transition\",\n \"name\": \"Layout property animation\",\n \"description\": \"Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"/tests/fixtures/antipatterns/motion.html\",\n \"line\": 0,\n \"snippet\": \"transition: height\"\n },\n {\n \"antipattern\": \"layout-transition\",\n \"name\": \"Layout property animation\",\n \"description\": \"Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"/tests/fixtures/antipatterns/motion.html\",\n \"line\": 0,\n \"snippet\": \"transition: padding\"\n },\n {\n \"antipattern\": \"layout-transition\",\n \"name\": \"Layout property animation\",\n \"description\": \"Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"/tests/fixtures/antipatterns/motion.html\",\n \"line\": 0,\n \"snippet\": \"transition: margin\"\n },\n {\n \"antipattern\": \"layout-transition\",\n \"name\": \"Layout property animation\",\n \"description\": \"Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"/tests/fixtures/antipatterns/motion.html\",\n \"line\": 0,\n \"snippet\": \"transition: max-height\"\n },\n {\n \"antipattern\": \"layout-transition\",\n \"name\": \"Layout property animation\",\n \"description\": \"Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"/tests/fixtures/antipatterns/motion.html\",\n \"line\": 0,\n \"snippet\": \"transition: width, height\"\n },\n {\n \"antipattern\": \"layout-transition\",\n \"name\": \"Layout property animation\",\n \"description\": \"Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"/tests/fixtures/antipatterns/motion.html\",\n \"line\": 0,\n \"snippet\": \"transition: width\"\n },\n {\n \"antipattern\": \"layout-transition\",\n \"name\": \"Layout property animation\",\n \"description\": \"Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"/tests/fixtures/antipatterns/motion.html\",\n \"line\": 0,\n \"snippet\": \"transition: width\"\n },\n {\n \"antipattern\": \"flat-type-hierarchy\",\n \"name\": \"Flat type hierarchy\",\n \"description\": \"Dominant heading and body roles are separated by less than 1.25× at every step, leaving the size hierarchy flat. Add at least one stronger size step.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/motion.html\",\n \"line\": 0,\n \"snippet\": \"Role sizes: h3 11px, body 12px, h4 13px, h2 14px (largest adjacent step 1.09:1; target 1.25:1)\"\n }\n]\n", "stderr": "", "exit": 2, "signal": null, diff --git a/tests/oracle/golden/detect-fixture-json-named-color-borders-html.json b/tests/oracle/golden/detect-fixture-json-named-color-borders-html.json index c41ff3b62..ce38ef9e9 100644 --- a/tests/oracle/golden/detect-fixture-json-named-color-borders-html.json +++ b/tests/oracle/golden/detect-fixture-json-named-color-borders-html.json @@ -1,5 +1,5 @@ { - "stdout": "[\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/named-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 4px + border-radius: 8px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/named-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 5px + border-radius: 4px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/named-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-top: 4px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/named-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 3px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/named-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 6px + border-radius: 4px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/named-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 7px\"\n },\n {\n \"antipattern\": \"low-contrast\",\n \"name\": \"Low contrast text\",\n \"description\": \"Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"/tests/fixtures/antipatterns/named-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"4.4:1 (need 4.5:1) — text #64748b on #f6f6f6\"\n },\n {\n \"antipattern\": \"flat-type-hierarchy\",\n \"name\": \"Flat type hierarchy\",\n \"description\": \"Font sizes are too close together — no clear visual hierarchy. Use fewer sizes with more contrast (aim for at least a 1.25 ratio between steps).\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/named-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"Sizes: 13px, 14px, 16px (ratio 1.2:1)\"\n }\n]\n", + "stdout": "[\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/named-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 4px + border-radius: 8px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/named-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 5px + border-radius: 4px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/named-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-top: 4px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/named-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 3px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/named-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 6px + border-radius: 4px\"\n },\n {\n \"antipattern\": \"side-tab\",\n \"name\": \"Side-tab accent border\",\n \"description\": \"Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/named-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"border-left: 7px\"\n },\n {\n \"antipattern\": \"low-contrast\",\n \"name\": \"Low contrast text\",\n \"description\": \"Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"/tests/fixtures/antipatterns/named-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"4.4:1 (need 4.5:1) — text #64748b on #f6f6f6\"\n },\n {\n \"antipattern\": \"flat-type-hierarchy\",\n \"name\": \"Flat type hierarchy\",\n \"description\": \"Dominant heading and body roles are separated by less than 1.25× at every step, leaving the size hierarchy flat. Add at least one stronger size step.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/named-color-borders.html\",\n \"line\": 0,\n \"snippet\": \"Role sizes: body 13px, h2 14px, h3 14px (largest adjacent step 1.08:1; target 1.25:1)\"\n }\n]\n", "stderr": "", "exit": 2, "signal": null, diff --git a/tests/oracle/golden/detect-fixture-json-numbered-section-markers-html.json b/tests/oracle/golden/detect-fixture-json-numbered-section-markers-html.json index afe20a428..1e26561eb 100644 --- a/tests/oracle/golden/detect-fixture-json-numbered-section-markers-html.json +++ b/tests/oracle/golden/detect-fixture-json-numbered-section-markers-html.json @@ -1,7 +1,7 @@ { - "stdout": "[]\n", + "stdout": "[\n {\n \"antipattern\": \"flat-type-hierarchy\",\n \"name\": \"Flat type hierarchy\",\n \"description\": \"Dominant heading and body roles are separated by less than 1.25× at every step, leaving the size hierarchy flat. Add at least one stronger size step.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/numbered-section-markers.html\",\n \"line\": 0,\n \"snippet\": \"Role sizes: body 16px, h1 16px, h2 16px (largest adjacent step 1.00:1; target 1.25:1)\"\n }\n]\n", "stderr": "", - "exit": 0, + "exit": 2, "signal": null, "files": {} } diff --git a/tests/oracle/golden/detect-fixture-json-oklch-neon-text-html.json b/tests/oracle/golden/detect-fixture-json-oklch-neon-text-html.json index 2fe01bf4c..9de5660cf 100644 --- a/tests/oracle/golden/detect-fixture-json-oklch-neon-text-html.json +++ b/tests/oracle/golden/detect-fixture-json-oklch-neon-text-html.json @@ -1,5 +1,5 @@ { - "stdout": "[\n {\n \"antipattern\": \"low-contrast\",\n \"name\": \"Low contrast text\",\n \"description\": \"Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"/tests/fixtures/antipatterns/oklch-neon-text.html\",\n \"line\": 0,\n \"snippet\": \"1.3:1 (need 4.5:1) — text #00f4f6 on #f5f5f5\"\n },\n {\n \"antipattern\": \"flat-type-hierarchy\",\n \"name\": \"Flat type hierarchy\",\n \"description\": \"Font sizes are too close together — no clear visual hierarchy. Use fewer sizes with more contrast (aim for at least a 1.25 ratio between steps).\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/oklch-neon-text.html\",\n \"line\": 0,\n \"snippet\": \"Sizes: 13px, 16px, 18px (ratio 1.4:1)\"\n }\n]\n", + "stdout": "[\n {\n \"antipattern\": \"low-contrast\",\n \"name\": \"Low contrast text\",\n \"description\": \"Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"/tests/fixtures/antipatterns/oklch-neon-text.html\",\n \"line\": 0,\n \"snippet\": \"1.3:1 (need 4.5:1) — text #00f4f6 on #f5f5f5\"\n }\n]\n", "stderr": "", "exit": 2, "signal": null, diff --git a/tests/oracle/golden/detect-fixture-json-typography-should-flag-html.json b/tests/oracle/golden/detect-fixture-json-typography-should-flag-html.json index 8332c38a7..87c6e1f72 100644 --- a/tests/oracle/golden/detect-fixture-json-typography-should-flag-html.json +++ b/tests/oracle/golden/detect-fixture-json-typography-should-flag-html.json @@ -1,5 +1,5 @@ { - "stdout": "[\n {\n \"antipattern\": \"overused-font\",\n \"name\": \"Overused font\",\n \"description\": \"Inter, Roboto, Fraunces, Geist, Plus Jakarta Sans, and Space Grotesk are used on so many sites they no longer feel distinctive. Each new wave of AI-generated UIs converges on the same handful of faces. Choose a face that gives your interface personality.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/typography-should-flag.html\",\n \"line\": 0,\n \"snippet\": \"Primary font: inter\"\n },\n {\n \"antipattern\": \"flat-type-hierarchy\",\n \"name\": \"Flat type hierarchy\",\n \"description\": \"Font sizes are too close together — no clear visual hierarchy. Use fewer sizes with more contrast (aim for at least a 1.25 ratio between steps).\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/typography-should-flag.html\",\n \"line\": 0,\n \"snippet\": \"Sizes: 13px, 14px, 15px, 16px, 18px (ratio 1.4:1)\"\n }\n]\n", + "stdout": "[\n {\n \"antipattern\": \"overused-font\",\n \"name\": \"Overused font\",\n \"description\": \"Inter, Roboto, Fraunces, Geist, Plus Jakarta Sans, and Space Grotesk are used on so many sites they no longer feel distinctive. Each new wave of AI-generated UIs converges on the same handful of faces. Choose a face that gives your interface personality.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/typography-should-flag.html\",\n \"line\": 0,\n \"snippet\": \"Primary font: inter\"\n },\n {\n \"antipattern\": \"flat-type-hierarchy\",\n \"name\": \"Flat type hierarchy\",\n \"description\": \"Dominant heading and body roles are separated by less than 1.25× at every step, leaving the size hierarchy flat. Add at least one stronger size step.\",\n \"severity\": \"warning\",\n \"category\": \"slop\",\n \"file\": \"/tests/fixtures/antipatterns/typography-should-flag.html\",\n \"line\": 0,\n \"snippet\": \"Role sizes: body 14px, h3 15px, h2 16px, h1 18px (largest adjacent step 1.13:1; target 1.25:1)\"\n }\n]\n", "stderr": "", "exit": 2, "signal": null, diff --git a/tests/oracle/golden/detect-fixture-text-glow-html.json b/tests/oracle/golden/detect-fixture-text-glow-html.json index 14d0aafee..100719fb7 100644 --- a/tests/oracle/golden/detect-fixture-text-glow-html.json +++ b/tests/oracle/golden/detect-fixture-text-glow-html.json @@ -1,6 +1,6 @@ { "stdout": "", - "stderr": "\n/tests/fixtures/antipatterns/glow.html\n [dark-glow] Zero-offset box-shadow glow (#3b82f6)\n → Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \"cool\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\n [dark-glow] Zero-offset box-shadow glow (#8b5cf6)\n → Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \"cool\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\n [dark-glow] Zero-offset box-shadow glow (#06b6d4)\n → Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \"cool\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\n [dark-glow] Zero-offset box-shadow glow (#a855f7)\n → Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \"cool\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\n [dark-glow] Zero-offset box-shadow glow (#ec4899)\n → Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \"cool\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\n [dark-glow] Zero-offset box-shadow glow (#00c0c2)\n → Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \"cool\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\n [dark-glow] Zero-offset box-shadow glow (#3b82f6)\n → Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \"cool\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\n [dark-glow] Zero-offset box-shadow glow (#b447eb)\n → Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \"cool\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\n [dark-glow] Zero-offset box-shadow glow (#63d18f)\n → Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \"cool\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\n [dark-glow] Zero-offset text-shadow glow (#22d3ee)\n → Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \"cool\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\n [dark-glow] Zero-offset box-shadow glow (#a46bf5)\n → Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \"cool\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\n [dark-glow] Zero-offset box-shadow glow (#d946ef)\n → Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \"cool\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\n [flat-type-hierarchy] Sizes: 11px, 12px, 13px, 14px, 16px (ratio 1.5:1)\n → Font sizes are too close together — no clear visual hierarchy. Use fewer sizes with more contrast (aim for at least a 1.25 ratio between steps).\n [buried-raster] raster under a near-opaque gradient wash: linear-gradient(#111827, #0b1220), url('/fixtures/antipatterns/missing-photo.png')\n → A background image under a near-opaque gradient wash, or a raster on an element at near-zero opacity, never reaches the screen: the page shows the wash, and the produced texture or photo ships as a compliance token. Let the material show (a tint under 0.9 alpha, a blend mode, an opacity you can see) or remove the file.\n [dark-glow] Zero-offset box-shadow glow (#3b82f6)\n → Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \"cool\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\n\n15 anti-patterns found.\n", + "stderr": "\n/tests/fixtures/antipatterns/glow.html\n [dark-glow] Zero-offset box-shadow glow (#3b82f6)\n → Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \"cool\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\n [dark-glow] Zero-offset box-shadow glow (#8b5cf6)\n → Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \"cool\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\n [dark-glow] Zero-offset box-shadow glow (#06b6d4)\n → Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \"cool\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\n [dark-glow] Zero-offset box-shadow glow (#a855f7)\n → Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \"cool\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\n [dark-glow] Zero-offset box-shadow glow (#ec4899)\n → Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \"cool\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\n [dark-glow] Zero-offset box-shadow glow (#00c0c2)\n → Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \"cool\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\n [dark-glow] Zero-offset box-shadow glow (#3b82f6)\n → Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \"cool\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\n [dark-glow] Zero-offset box-shadow glow (#b447eb)\n → Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \"cool\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\n [dark-glow] Zero-offset box-shadow glow (#63d18f)\n → Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \"cool\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\n [dark-glow] Zero-offset text-shadow glow (#22d3ee)\n → Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \"cool\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\n [dark-glow] Zero-offset box-shadow glow (#a46bf5)\n → Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \"cool\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\n [dark-glow] Zero-offset box-shadow glow (#d946ef)\n → Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \"cool\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\n [flat-type-hierarchy] Role sizes: h3 11px, body 12px, h4 13px, h2 14px (largest adjacent step 1.09:1; target 1.25:1)\n → Dominant heading and body roles are separated by less than 1.25× at every step, leaving the size hierarchy flat. Add at least one stronger size step.\n [buried-raster] raster under a near-opaque gradient wash: linear-gradient(#111827, #0b1220), url('/fixtures/antipatterns/missing-photo.png')\n → A background image under a near-opaque gradient wash, or a raster on an element at near-zero opacity, never reaches the screen: the page shows the wash, and the produced texture or photo ships as a compliance token. Let the material show (a tint under 0.9 alpha, a blend mode, an opacity you can see) or remove the file.\n [dark-glow] Zero-offset box-shadow glow (#3b82f6)\n → Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default \"cool\" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.\n\n15 anti-patterns found.\n", "exit": 2, "signal": null, "files": {} diff --git a/tests/oracle/golden/detect-fixture-text-icon-tile-stack-html.json b/tests/oracle/golden/detect-fixture-text-icon-tile-stack-html.json index 0a1ebcc76..92081c30d 100644 --- a/tests/oracle/golden/detect-fixture-text-icon-tile-stack-html.json +++ b/tests/oracle/golden/detect-fixture-text-icon-tile-stack-html.json @@ -1,6 +1,6 @@ { "stdout": "", - "stderr": "\n/tests/fixtures/antipatterns/icon-tile-stack.html\n [icon-tile-stack] 80x80px icon tile above h3 \"Lightning Fast\"\n → A small rounded-square icon container above a heading is the universal AI feature-card template — every generator outputs this exact shape. Try a side-by-side icon and heading, or let the icon sit in flow without its own container.\n [icon-tile-stack] 64x64px icon tile above h3 \"Secure Storage\"\n → A small rounded-square icon container above a heading is the universal AI feature-card template — every generator outputs this exact shape. Try a side-by-side icon and heading, or let the icon sit in flow without its own container.\n [icon-tile-stack] 72x72px icon tile above h3 \"Easy Setup\"\n → A small rounded-square icon container above a heading is the universal AI feature-card template — every generator outputs this exact shape. Try a side-by-side icon and heading, or let the icon sit in flow without its own container.\n [icon-tile-stack] 96x96px icon tile above h3 \"Powerful Analytics\"\n → A small rounded-square icon container above a heading is the universal AI feature-card template — every generator outputs this exact shape. Try a side-by-side icon and heading, or let the icon sit in flow without its own container.\n [icon-tile-stack] 48x48px icon tile above h3 \"Emoji Inline Icon\"\n → A small rounded-square icon container above a heading is the universal AI feature-card template — every generator outputs this exact shape. Try a side-by-side icon and heading, or let the icon sit in flow without its own container.\n [flat-type-hierarchy] Sizes: 14px, 16px, 18px, 24px (ratio 1.7:1)\n → Font sizes are too close together — no clear visual hierarchy. Use fewer sizes with more contrast (aim for at least a 1.25 ratio between steps).\n [ai-color-palette] Purple/violet accent colors detected\n → Purple/violet gradients and cyan-on-dark are the most recognizable tells of AI-generated UIs. Choose a distinctive, intentional palette.\n [marketing-buzzword] 1 buzzword phrase: \"ure Storage Enterprise-grade security fo\"\n → Generic SaaS phrases (streamline / empower / supercharge / world-class / enterprise-grade / next-generation / cutting-edge / etc) are instant AI tells. Pick a specific verb and noun that says what the product literally does.\n\n8 anti-patterns found.\n", + "stderr": "\n/tests/fixtures/antipatterns/icon-tile-stack.html\n [icon-tile-stack] 80x80px icon tile above h3 \"Lightning Fast\"\n → A small rounded-square icon container above a heading is the universal AI feature-card template — every generator outputs this exact shape. Try a side-by-side icon and heading, or let the icon sit in flow without its own container.\n [icon-tile-stack] 64x64px icon tile above h3 \"Secure Storage\"\n → A small rounded-square icon container above a heading is the universal AI feature-card template — every generator outputs this exact shape. Try a side-by-side icon and heading, or let the icon sit in flow without its own container.\n [icon-tile-stack] 72x72px icon tile above h3 \"Easy Setup\"\n → A small rounded-square icon container above a heading is the universal AI feature-card template — every generator outputs this exact shape. Try a side-by-side icon and heading, or let the icon sit in flow without its own container.\n [icon-tile-stack] 96x96px icon tile above h3 \"Powerful Analytics\"\n → A small rounded-square icon container above a heading is the universal AI feature-card template — every generator outputs this exact shape. Try a side-by-side icon and heading, or let the icon sit in flow without its own container.\n [icon-tile-stack] 48x48px icon tile above h3 \"Emoji Inline Icon\"\n → A small rounded-square icon container above a heading is the universal AI feature-card template — every generator outputs this exact shape. Try a side-by-side icon and heading, or let the icon sit in flow without its own container.\n [ai-color-palette] Purple/violet accent colors detected\n → Purple/violet gradients and cyan-on-dark are the most recognizable tells of AI-generated UIs. Choose a distinctive, intentional palette.\n [marketing-buzzword] 1 buzzword phrase: \"ure Storage Enterprise-grade security fo\"\n → Generic SaaS phrases (streamline / empower / supercharge / world-class / enterprise-grade / next-generation / cutting-edge / etc) are instant AI tells. Pick a specific verb and noun that says what the product literally does.\n\n7 anti-patterns found.\n", "exit": 2, "signal": null, "files": {} diff --git a/tests/oracle/golden/detect-fixture-text-layout-html.json b/tests/oracle/golden/detect-fixture-text-layout-html.json index 4afeeea52..6c422cb97 100644 --- a/tests/oracle/golden/detect-fixture-text-layout-html.json +++ b/tests/oracle/golden/detect-fixture-text-layout-html.json @@ -1,6 +1,6 @@ { "stdout": "", - "stderr": "\n/tests/fixtures/antipatterns/layout.html\n [flat-type-hierarchy] Sizes: 11px, 14px, 16px (ratio 1.5:1)\n → Font sizes are too close together — no clear visual hierarchy. Use fewer sizes with more contrast (aim for at least a 1.25 ratio between steps).\n [nested-cards] Card inside card (div)\n → Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.\n [nested-cards] Card inside card (div)\n → Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.\n [nested-cards] Card inside card (div)\n → Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.\n [nested-cards] Card inside card (div)\n → Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.\n\n5 anti-patterns found.\n", + "stderr": "\n/tests/fixtures/antipatterns/layout.html\n [nested-cards] Card inside card (div)\n → Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.\n [nested-cards] Card inside card (div)\n → Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.\n [nested-cards] Card inside card (div)\n → Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.\n [nested-cards] Card inside card (div)\n → Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.\n\n4 anti-patterns found.\n", "exit": 2, "signal": null, "files": {} diff --git a/tests/oracle/golden/detect-fixture-text-modern-color-borders-html.json b/tests/oracle/golden/detect-fixture-text-modern-color-borders-html.json index 4de7e9d31..23885efe5 100644 --- a/tests/oracle/golden/detect-fixture-text-modern-color-borders-html.json +++ b/tests/oracle/golden/detect-fixture-text-modern-color-borders-html.json @@ -1,6 +1,6 @@ { "stdout": "", - "stderr": "\n/tests/fixtures/antipatterns/modern-color-borders.html\n [side-tab] border-left: 3px + border-radius: 4px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 4px + border-radius: 4px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 4px + border-radius: 4px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 4px + border-radius: 6px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 4px + border-radius: 6px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 4px + border-radius: 6px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 3px + border-radius: 4px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 4px + border-radius: 6px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 5px + border-radius: 4px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 4px + border-radius: 4px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-right: 4px + border-radius: 4px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 4px + border-radius: 6px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [low-contrast] 4.49:1 (need 4.5:1) — text #64748b on #fef7f2\n → Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.\n [flat-type-hierarchy] Sizes: 13px, 14px, 16px (ratio 1.2:1)\n → Font sizes are too close together — no clear visual hierarchy. Use fewer sizes with more contrast (aim for at least a 1.25 ratio between steps).\n\n14 anti-patterns found.\n", + "stderr": "\n/tests/fixtures/antipatterns/modern-color-borders.html\n [side-tab] border-left: 3px + border-radius: 4px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 4px + border-radius: 4px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 4px + border-radius: 4px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 4px + border-radius: 6px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 4px + border-radius: 6px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 4px + border-radius: 6px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 3px + border-radius: 4px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 4px + border-radius: 6px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 5px + border-radius: 4px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 4px + border-radius: 4px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-right: 4px + border-radius: 4px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 4px + border-radius: 6px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [low-contrast] 4.49:1 (need 4.5:1) — text #64748b on #fef7f2\n → Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.\n [flat-type-hierarchy] Role sizes: body 13px, h2 14px, h3 14px (largest adjacent step 1.08:1; target 1.25:1)\n → Dominant heading and body roles are separated by less than 1.25× at every step, leaving the size hierarchy flat. Add at least one stronger size step.\n\n14 anti-patterns found.\n", "exit": 2, "signal": null, "files": {} diff --git a/tests/oracle/golden/detect-fixture-text-motion-html.json b/tests/oracle/golden/detect-fixture-text-motion-html.json index 0ee2b7b13..0bca6379f 100644 --- a/tests/oracle/golden/detect-fixture-text-motion-html.json +++ b/tests/oracle/golden/detect-fixture-text-motion-html.json @@ -1,6 +1,6 @@ { "stdout": "", - "stderr": "\n/tests/fixtures/antipatterns/motion.html\n [bounce-easing] animation: bounce-keyframe\n → Bounce and elastic easing feel dated and tacky. Real objects decelerate smoothly — use exponential easing (ease-out-quart/quint/expo) instead.\n [bounce-easing] cubic-bezier(0.68, -0.55, 0.265, 1.55)\n → Bounce and elastic easing feel dated and tacky. Real objects decelerate smoothly — use exponential easing (ease-out-quart/quint/expo) instead.\n [layout-transition] transition: width\n → Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\n [layout-transition] transition: height\n → Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\n [layout-transition] transition: padding\n → Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\n [layout-transition] transition: margin\n → Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\n [layout-transition] transition: max-height\n → Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\n [layout-transition] transition: width, height\n → Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\n [layout-transition] transition: width\n → Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\n [layout-transition] transition: width\n → Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\n [flat-type-hierarchy] Sizes: 11px, 12px, 13px, 14px, 16px (ratio 1.5:1)\n → Font sizes are too close together — no clear visual hierarchy. Use fewer sizes with more contrast (aim for at least a 1.25 ratio between steps).\n\n11 anti-patterns found.\n", + "stderr": "\n/tests/fixtures/antipatterns/motion.html\n [bounce-easing] animation: bounce-keyframe\n → Bounce and elastic easing feel dated and tacky. Real objects decelerate smoothly — use exponential easing (ease-out-quart/quint/expo) instead.\n [bounce-easing] cubic-bezier(0.68, -0.55, 0.265, 1.55)\n → Bounce and elastic easing feel dated and tacky. Real objects decelerate smoothly — use exponential easing (ease-out-quart/quint/expo) instead.\n [layout-transition] transition: width\n → Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\n [layout-transition] transition: height\n → Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\n [layout-transition] transition: padding\n → Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\n [layout-transition] transition: margin\n → Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\n [layout-transition] transition: max-height\n → Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\n [layout-transition] transition: width, height\n → Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\n [layout-transition] transition: width\n → Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\n [layout-transition] transition: width\n → Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.\n [flat-type-hierarchy] Role sizes: h3 11px, body 12px, h4 13px, h2 14px (largest adjacent step 1.09:1; target 1.25:1)\n → Dominant heading and body roles are separated by less than 1.25× at every step, leaving the size hierarchy flat. Add at least one stronger size step.\n\n11 anti-patterns found.\n", "exit": 2, "signal": null, "files": {} diff --git a/tests/oracle/golden/detect-fixture-text-named-color-borders-html.json b/tests/oracle/golden/detect-fixture-text-named-color-borders-html.json index 54379d833..0585bad53 100644 --- a/tests/oracle/golden/detect-fixture-text-named-color-borders-html.json +++ b/tests/oracle/golden/detect-fixture-text-named-color-borders-html.json @@ -1,6 +1,6 @@ { "stdout": "", - "stderr": "\n/tests/fixtures/antipatterns/named-color-borders.html\n [side-tab] border-left: 4px + border-radius: 8px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 5px + border-radius: 4px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-top: 4px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 3px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 6px + border-radius: 4px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 7px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [low-contrast] 4.4:1 (need 4.5:1) — text #64748b on #f6f6f6\n → Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.\n [flat-type-hierarchy] Sizes: 13px, 14px, 16px (ratio 1.2:1)\n → Font sizes are too close together — no clear visual hierarchy. Use fewer sizes with more contrast (aim for at least a 1.25 ratio between steps).\n\n8 anti-patterns found.\n", + "stderr": "\n/tests/fixtures/antipatterns/named-color-borders.html\n [side-tab] border-left: 4px + border-radius: 8px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 5px + border-radius: 4px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-top: 4px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 3px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 6px + border-radius: 4px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [side-tab] border-left: 7px\n → Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.\n [low-contrast] 4.4:1 (need 4.5:1) — text #64748b on #f6f6f6\n → Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.\n [flat-type-hierarchy] Role sizes: body 13px, h2 14px, h3 14px (largest adjacent step 1.08:1; target 1.25:1)\n → Dominant heading and body roles are separated by less than 1.25× at every step, leaving the size hierarchy flat. Add at least one stronger size step.\n\n8 anti-patterns found.\n", "exit": 2, "signal": null, "files": {} diff --git a/tests/oracle/golden/detect-fixture-text-numbered-section-markers-html.json b/tests/oracle/golden/detect-fixture-text-numbered-section-markers-html.json index 7ea122487..18b5b671b 100644 --- a/tests/oracle/golden/detect-fixture-text-numbered-section-markers-html.json +++ b/tests/oracle/golden/detect-fixture-text-numbered-section-markers-html.json @@ -1,7 +1,7 @@ { "stdout": "", - "stderr": "", - "exit": 0, + "stderr": "\n/tests/fixtures/antipatterns/numbered-section-markers.html\n [flat-type-hierarchy] Role sizes: body 16px, h1 16px, h2 16px (largest adjacent step 1.00:1; target 1.25:1)\n → Dominant heading and body roles are separated by less than 1.25× at every step, leaving the size hierarchy flat. Add at least one stronger size step.\n\n1 anti-pattern found.\n", + "exit": 2, "signal": null, "files": {} } diff --git a/tests/oracle/golden/detect-fixture-text-oklch-neon-text-html.json b/tests/oracle/golden/detect-fixture-text-oklch-neon-text-html.json index 4af1a99c1..208466e4e 100644 --- a/tests/oracle/golden/detect-fixture-text-oklch-neon-text-html.json +++ b/tests/oracle/golden/detect-fixture-text-oklch-neon-text-html.json @@ -1,6 +1,6 @@ { "stdout": "", - "stderr": "\n/tests/fixtures/antipatterns/oklch-neon-text.html\n [low-contrast] 1.3:1 (need 4.5:1) — text #00f4f6 on #f5f5f5\n → Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.\n [flat-type-hierarchy] Sizes: 13px, 16px, 18px (ratio 1.4:1)\n → Font sizes are too close together — no clear visual hierarchy. Use fewer sizes with more contrast (aim for at least a 1.25 ratio between steps).\n\n2 anti-patterns found.\n", + "stderr": "\n/tests/fixtures/antipatterns/oklch-neon-text.html\n [low-contrast] 1.3:1 (need 4.5:1) — text #00f4f6 on #f5f5f5\n → Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.\n\n1 anti-pattern found.\n", "exit": 2, "signal": null, "files": {} diff --git a/tests/oracle/golden/detect-fixture-text-typography-should-flag-html.json b/tests/oracle/golden/detect-fixture-text-typography-should-flag-html.json index 7a14199f0..6715964e2 100644 --- a/tests/oracle/golden/detect-fixture-text-typography-should-flag-html.json +++ b/tests/oracle/golden/detect-fixture-text-typography-should-flag-html.json @@ -1,6 +1,6 @@ { "stdout": "", - "stderr": "\n/tests/fixtures/antipatterns/typography-should-flag.html\n [overused-font] Primary font: inter\n → Inter, Roboto, Fraunces, Geist, Plus Jakarta Sans, and Space Grotesk are used on so many sites they no longer feel distinctive. Each new wave of AI-generated UIs converges on the same handful of faces. Choose a face that gives your interface personality.\n [flat-type-hierarchy] Sizes: 13px, 14px, 15px, 16px, 18px (ratio 1.4:1)\n → Font sizes are too close together — no clear visual hierarchy. Use fewer sizes with more contrast (aim for at least a 1.25 ratio between steps).\n\n2 anti-patterns found.\n", + "stderr": "\n/tests/fixtures/antipatterns/typography-should-flag.html\n [overused-font] Primary font: inter\n → Inter, Roboto, Fraunces, Geist, Plus Jakarta Sans, and Space Grotesk are used on so many sites they no longer feel distinctive. Each new wave of AI-generated UIs converges on the same handful of faces. Choose a face that gives your interface personality.\n [flat-type-hierarchy] Role sizes: body 14px, h3 15px, h2 16px, h1 18px (largest adjacent step 1.13:1; target 1.25:1)\n → Dominant heading and body roles are separated by less than 1.25× at every step, leaving the size hierarchy flat. Add at least one stronger size step.\n\n2 anti-patterns found.\n", "exit": 2, "signal": null, "files": {}