Compare commits

...
Author SHA1 Message Date
Abdul WahabandCursor 3950a8221d Fix: keep multiline JSX stripe-child matches (#394)
A class-only line has no opening tag, so emptiness is unknown; still require a self-closing or empty tag when the tag is on this line.

Prepared with AI assistance.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-10 10:05:56 +05:00
Abdul WahabandCursor fc5dcefce2 Fix: tighten stripe-child text and static cascade gates (#394)
Keep flex/align out of the frozen expandStaticDeclaration map, require empty tagged stripe cues on the text path, and honor inset order plus rem width.

Prepared with AI assistance.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-10 09:58:28 +05:00
Abdul WahabandCursor 5c67bf725d Fix: detect side-tab stripes drawn as child elements (#394)
Empty narrow chromatic children (Tailwind w-1 bg-*, flex-row rails, absolute inset) now flag as side-tab across the text, static HTML, and browser engines.

Prepared with AI assistance.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-10 09:43:54 +05:00
67d018fe05 Fix: print JSON on live-poll --reply success (#800)
Successful --reply was exit 0 with empty stdout, so agents could not tell delivery from a hang. Prepared with AI assistance.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-10 08:47:39 +05:00
3bdb9ff06c Fix: drop stale carbonize diagnostic on complete (#801)
Complete and discarded snapshots no longer keep carbonize_cleanup_required after cleanup is done.

AI assistance: Cursor Grok 4.6.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-10 08:47:16 +05:00
30 changed files with 1004 additions and 99 deletions
+1
View File
@@ -1350,6 +1350,7 @@ pub fn collect_browser_findings(dom: &dyn Dom, config: &BrowserConfig) -> Collec
let mut findings: Vec<BrowserFinding> = Vec::new();
findings.extend(hits(ec::check_element_borders_dom(dom, el)));
findings.extend(hits(ec::check_element_pseudo_stripe_dom(dom, el)));
findings.extend(hits(ec::check_element_stripe_child_dom(dom, el)));
findings.extend(hits(ec::check_element_colors_dom(dom, el)));
findings.extend(hits(ec::check_element_motion_dom(dom, el)));
findings.extend(hits(ec::check_element_glow_dom(dom, el)));
+92 -2
View File
@@ -20,8 +20,9 @@ use crate::checks::measures::{
};
use crate::checks::rules::{
check_borders, check_colors, check_glow, check_hero_eyebrow, check_icon_tile,
check_italic_serif, check_motion, is_emoji_only_text, BorderOpts, ColorOpts, GlowOpts,
HeroEyebrowOpts, IconTileOpts, ItalicSerifOpts, MotionOpts, RuleHit, Sides, HEADING_TAGS,
check_italic_serif, check_motion, check_stripe_child, is_emoji_only_text, BorderOpts,
ColorOpts, GlowOpts, HeroEyebrowOpts, IconTileOpts, ItalicSerifOpts, MotionOpts, RuleHit,
Sides, HEADING_TAGS,
};
use crate::checks::text_rules::{
CURSOR_FIRST_VIEWPORT_PX, CURSOR_GLYPH_RE, POSITIONED_CHILD_INTERACTIVE_SELECTOR,
@@ -367,6 +368,57 @@ pub fn check_element_pseudo_stripe_dom(dom: &dyn Dom, el: ElId) -> Vec<RuleHit>
findings
}
const STRIPE_CHILD_SKIP: &str = "nav, blockquote, pre, table, button, a, select, progress, meter, [role=\"progressbar\"], [role=\"slider\"], [role=\"scrollbar\"], [role=\"separator\"], [role=\"tablist\"]";
/// JS: checks.mjs#checkElementStripeChildDOM(el)
pub fn check_element_stripe_child_dom(dom: &dyn Dom, el: ElId) -> Vec<RuleHit> {
let tag = tag_lower(dom, el);
if tag != "div" && tag != "span" {
return Vec::new();
}
let Some(host) = dom.parent(el) else {
return Vec::new();
};
let host_tag = tag_lower(dom, host);
if host_tag == "body" || host_tag == "html" {
return Vec::new();
}
if !dom.children(el).is_empty() {
return Vec::new();
}
if !js::trim(&collapse_ws(&dom.text_content(el))).is_empty() {
return Vec::new();
}
if closest_or_none(dom, el, STRIPE_CHILD_SKIP).is_some() {
return Vec::new();
}
if !is_rendered_for_browser_rule(dom, el) {
return Vec::new();
}
if is_tab_context_element(dom, el) || is_status_context_element(dom, el) {
return Vec::new();
}
let host_rect = dom.rect(host);
if host_rect.width < 40.0 || host_rect.height < 20.0 {
return Vec::new();
}
let child_rect = dom.rect(el);
if child_rect.height < host_rect.height - 44.0 || child_rect.height < host_rect.height * 0.5 {
return Vec::new();
}
let hugs = |v: f64| v.is_finite() && v.abs() <= 3.0;
let edge = if hugs(child_rect.left - host_rect.left) {
Some("left")
} else if hugs(host_rect.right - child_rect.right) {
Some("right")
} else {
None
};
let width = child_rect.width;
let bg = parse_rgb_or_any(&dom.style(el, "backgroundColor"));
check_stripe_child(&class_selector(dom, el), width, edge, bg)
}
/// JS: checks.mjs#readPseudoSurfaceDOM(el, rect)
pub fn read_pseudo_surface_dom(dom: &dyn Dom, el: ElId, rect: &Rect) -> Option<Rgba> {
for which in PSEUDOS {
@@ -1378,6 +1430,44 @@ mod tests {
assert!(check_element_pseudo_stripe_dom(&d, card).is_empty());
}
#[test]
fn stripe_child_flags_left_edge_and_skips_neutral_text_and_tab_context() {
let (mut d, body) = page();
let host = d.add(Some(body), "div");
visible(&mut d, host);
d.set_attr(host, "class", "card");
d.set_rect(host, 0.0, 0.0, 300.0, 100.0);
let stripe = d.add(Some(host), "div");
visible(&mut d, stripe);
d.set_rect(stripe, 0.0, 0.0, 4.0, 100.0);
d.set_styles(
stripe,
&[
("backgroundColor", "rgb(245, 158, 11)"),
("width", "4px"),
("height", "100px"),
],
);
let hits = check_element_stripe_child_dom(&d, stripe);
assert_eq!(hits.len(), 1);
assert_eq!(hits[0].id, "side-tab");
assert_eq!(hits[0].snippet, "div — 4px stripe child (left)");
d.set_styles(stripe, &[("backgroundColor", "rgb(120, 120, 120)")]);
assert!(check_element_stripe_child_dom(&d, stripe).is_empty());
let stripe_text = d.add(Some(host), "div");
visible(&mut d, stripe_text);
d.set_rect(stripe_text, 4.0, 0.0, 4.0, 100.0);
d.set_styles(
stripe_text,
&[("backgroundColor", "rgb(245, 158, 11)")],
);
d.add_text(stripe_text, "x");
assert!(check_element_stripe_child_dom(&d, stripe_text).is_empty());
d.set_styles(stripe, &[("backgroundColor", "rgb(245, 158, 11)")]);
d.set_attr(host, "class", "card is-active");
assert!(check_element_stripe_child_dom(&d, stripe).is_empty());
}
#[test]
fn colors_low_contrast_on_resolved_surface_and_pseudo_surface() {
let (mut d, body) = page();
+33
View File
@@ -88,6 +88,39 @@ pub fn check_borders(
findings
}
/// Pure gate for dedicated stripe-child side-tabs (empty narrow chromatic
/// `div`/`span` at a card edge).
pub fn check_stripe_child(
selector: &str,
width: f64,
edge: Option<&str>,
bg: Option<Rgba>,
) -> Vec<RuleHit> {
let Some(edge) = edge else {
return Vec::new();
};
if !(width >= 2.0 && width <= 12.0) {
return Vec::new();
}
let Some(bg) = bg else {
return Vec::new();
};
if bg.alpha_or_one() <= 0.1 {
return Vec::new();
}
let spread = js::math_max3(bg.r, bg.g, bg.b) - js::math_min3(bg.r, bg.g, bg.b);
if spread < 30.0 {
return Vec::new();
}
vec![RuleHit::new(
"side-tab",
format!(
"{selector}{}px stripe child ({edge})",
number_to_string(math_round(width))
),
)]
}
re!(GRADIENT_CI, ci("gradient"));
re!(
+152 -24
View File
@@ -146,36 +146,65 @@ where
}
}
/// Opening-tag span that contains `index`, if any. `end` is the `>` byte.
fn markup_tag_span(line: &str, index: usize) -> Option<(usize, usize)> {
let mut i = 0usize;
while i < line.len() {
let Some(rel) = line[i..].find('<') else {
return None;
};
let tag_start = i + rel;
let after = &line[tag_start + 1..];
if !after.chars().next().is_some_and(|c| c.is_ascii_alphabetic()) {
i = tag_start + 1;
continue;
}
let mut tag_end: Option<usize> = None;
scan_js(line, tag_start + 1, |ch, j, _p, _n, depth| {
if ch == '>' && depth.brace == 0 {
tag_end = Some(j);
return true;
}
false
});
let Some(end) = tag_end else {
return None;
};
if index >= tag_start && index <= end {
return Some((tag_start, end));
}
i = end + 1;
}
None
}
/// JS: detect-text.mjs#containingMarkupTag (only its `text` is read).
fn containing_markup_tag(line: &str) -> impl Fn(usize) -> String + '_ {
move |index: usize| {
let mut i = 0usize;
while i < line.len() {
let Some(rel) = line[i..].find('<') else { break };
let tag_start = i + rel;
let after = &line[tag_start + 1..];
if !after.chars().next().is_some_and(|c| c.is_ascii_alphabetic()) {
i = tag_start + 1;
continue;
}
let mut tag_end: Option<usize> = None;
scan_js(line, tag_start + 1, |ch, j, _p, _n, depth| {
if ch == '>' && depth.brace == 0 {
tag_end = Some(j);
return true;
}
false
});
let Some(end) = tag_end else { break };
if index >= tag_start && index <= end {
return line[tag_start..end + 1].to_string();
}
i = end + 1;
}
line.to_string()
markup_tag_span(line, index)
.map(|(start, end)| line[start..end + 1].to_string())
.unwrap_or_else(|| line.to_string())
}
}
fn is_self_closing_tag(tag: &str) -> bool {
tag.trim_end_matches('>').trim_end().ends_with('/')
}
/// Text path cannot see the DOM. When this line holds a whole tag, require
/// it empty or self-closing. A class list with no `<` is a split JSX tag,
/// so emptiness is unknown and the other gates still apply.
fn stripe_child_markup_empty(line: &str, index: usize) -> bool {
let Some((start, end)) = markup_tag_span(line, index) else {
return true;
};
if is_self_closing_tag(&line[start..end + 1]) {
return true;
}
let rest = line.get(end + 1..).unwrap_or("").trim_start();
rest.starts_with("</")
}
struct TernarySplit {
common: String,
consequent: String,
@@ -494,6 +523,43 @@ re!(
SIDE_TAB_JS_RE,
format!("border(?:Left|Right){WS}*[:=]{WS}*[\"'`]({D}+)px{WS}+solid")
);
re!(
SIDE_TAB_STRIPE_CHILD_TW_RE,
r"w-(?:0\.5|1(?:\.5)?|2(?:\.5)?|3|\[(?:[2-9]|1[0-2])px\])"
);
re!(STRIPE_CHILD_HEIGHT_TOKEN_RE, r"h-(?:px\b|[0-9]|\[)");
re!(
STRIPE_CHILD_ARIA_RE,
r"(?i)aria-(?:current|selected)"
);
re!(STRIPE_CHILD_ROUNDED_FULL_RE, format!("{B}rounded-full{B}"));
re!(
STRIPE_CHILD_CUE_RE,
format!("{B}(?:shrink-0|rounded-[lres](?:-{W}+)?|left-0|right-0|inset-y-0){B}")
);
/// Hyphen-safe class-token boundary: the byte before `index` must not be `-`
/// or an ASCII word character (mirrors JS `(?<![\w-])`; the `regex` crate has
/// no lookbehind).
fn hyphen_safe_prefix(text: &str, index: usize) -> bool {
match text.as_bytes().get(index.wrapping_sub(1)) {
Some(b) if index > 0 => !b.is_ascii_alphanumeric() && *b != b'-',
_ => true,
}
}
fn hyphen_safe_suffix(text: &str, end: usize) -> bool {
!matches!(
text.as_bytes().get(end),
Some(b) if b.is_ascii_alphanumeric() || *b == b'-' || *b == b'.' || *b == b'/'
)
}
fn scope_has_fixed_height(scope: &str) -> bool {
STRIPE_CHILD_HEIGHT_TOKEN_RE.find_iter(scope).any(|m| {
hyphen_safe_prefix(scope, m.start())
})
}
re!(BORDER_ACCENT_TW_RE, format!("{B}border-[tb]-({D}+){B}"));
re!(
BORDER_ACCENT_CSS_RE,
@@ -864,6 +930,29 @@ pub static REGEX_MATCHERS: Lazy<Vec<Matcher>> = Lazy::new(|| {
test: |m, _| num(m.g(1)) >= 3.0,
fmt: |m, _| m.whole().to_string(),
},
Matcher {
id: "side-tab",
find_all: |l| all(&SIDE_TAB_STRIPE_CHILD_TW_RE, l),
test: |m, line| {
if !hyphen_safe_prefix(line, m.index)
|| !hyphen_safe_suffix(line, m.index + m.whole().len())
{
return false;
}
let scope = containing_markup_tag(line)(m.index);
find_solid_chromatic_bg(&scope).is_some()
&& stripe_child_markup_empty(line, m.index)
&& STRIPE_CHILD_CUE_RE.is_match(&scope)
&& !scope_has_fixed_height(&scope)
&& !STRIPE_CHILD_ROUNDED_FULL_RE.is_match(&scope)
&& !STRIPE_CHILD_ARIA_RE.is_match(&scope)
},
fmt: |m, line| {
let scope = containing_markup_tag(line)(m.index);
let bg = find_solid_chromatic_bg(&scope).unwrap();
format!("{} + {bg} stripe child", m.whole())
},
},
Matcher {
id: "border-accent-on-rounded",
find_all: |l| all(&BORDER_ACCENT_TW_RE, l),
@@ -1433,6 +1522,45 @@ mod tests {
);
}
#[test]
fn stripe_child_tailwind() {
let s = |line: &str| run("side-tab", line);
assert_eq!(
s(r#"<div className="w-1 shrink-0 rounded-l-lg bg-amber-500" />"#),
vec!["w-1 + bg-amber-500 stripe child"]
);
assert_eq!(
s(r#"<div class="w-[4px] bg-blue-500 shrink-0"></div>"#),
vec!["w-[4px] + bg-blue-500 stripe child"]
);
assert_eq!(
s(r#"<span className="w-0.5 bg-rose-500 shrink-0" />"#),
vec!["w-0.5 + bg-rose-500 stripe child"]
);
assert_eq!(
s(r#"<div className="w-1 min-h-0 bg-amber-500 shrink-0" />"#),
vec!["w-1 + bg-amber-500 stripe child"]
);
assert!(s(r#"<div className="w-2 h-2 rounded-full bg-green-500" />"#).is_empty());
assert!(s(
r#"<div className="flex items-center gap-1.5"><div className="w-3 h-3 rounded bg-amber-500" /><span className="text-slate-400">Vital few</span></div>"#
)
.is_empty());
assert!(s(r#"<div className="w-1 bg-amber-500/10" />"#).is_empty());
assert!(s(r#"<a className="w-1 bg-amber-500" aria-current="page"></a>"#).is_empty());
assert!(s(
r#"<div className="w-1 shrink-0"><span className="bg-amber-500" /></div>"#
)
.is_empty());
assert!(s(r#"<div className="w-1 bg-amber-500">|</div>"#).is_empty());
assert!(s(r#"<div className="w-1 bg-amber-500" />"#).is_empty());
assert_eq!(
s(r#" className="w-1 shrink-0 rounded-l-lg bg-amber-500""#),
vec!["w-1 + bg-amber-500 stripe child"]
);
assert!(s(r#"<div className="w-1 shrink-0 bg-amber-500">"#).is_empty());
}
#[test]
fn matchers() {
assert_eq!(
+117 -4
View File
@@ -9,9 +9,10 @@ use crate::background::{
a_ge, a_gt, read_own_background_color, resolve_background, resolve_background_info,
resolve_border_radius_px, resolve_gradient_stops, sv, sv_opt, CustomPropMap,
};
use crate::cascade::StyleValues;
use crate::cascade::{expand_static_box_values, split_css_tokens, StyleValues};
use crate::dom::{StaticDocument, StaticElement};
use crate::quality::{collapse_ws, pf0, resolve_font_size_px};
use impeccable_core::checks::css_scan::css_length_to_px;
use impeccable_core::checks::measures::{
self, border_colors_from_style, border_widths_from_style, check_gpt_thin_border_wide_shadow,
check_oversized_h1, check_radial_spotlight, positioned_style_implies_escape, resolve_length_px,
@@ -20,9 +21,9 @@ use impeccable_core::checks::measures::{
use impeccable_core::checks::rules::{
check_borders, check_colors, check_glow, check_hero_eyebrow, check_hover_contrast,
check_icon_tile, check_italic_serif, check_kicker_above_heading, check_motion,
is_emoji_only_text, is_heading_tag, resolve_hero_heading_size_px, BorderOpts, ColorOpts,
GlowOpts, HeroEyebrowOpts, HoverContrastOpts, IconTileOpts, ItalicSerifOpts, KickerCandidate,
MotionOpts, RuleHit, Sides,
check_stripe_child, is_emoji_only_text, is_heading_tag, resolve_hero_heading_size_px,
BorderOpts, ColorOpts, GlowOpts, HeroEyebrowOpts, HoverContrastOpts, IconTileOpts,
ItalicSerifOpts, KickerCandidate, MotionOpts, RuleHit, Sides,
};
use impeccable_core::checks::text_rules::{
check_numbered_section_labels, is_kicker_candidate, is_numbered_section_label_candidate,
@@ -460,6 +461,118 @@ pub fn check_element_borders(
)
}
const STRIPE_CHILD_SKIP: &str = "nav, blockquote, pre, table, button, a, select, progress, meter, [role=\"progressbar\"], [role=\"slider\"], [role=\"scrollbar\"], [role=\"separator\"], [role=\"tablist\"]";
fn static_edge_hugs(value: &str) -> bool {
let n = parse_float(value);
n.is_finite() && n.abs() <= 2.0
}
fn static_resolved_inset(style: &StyleValues) -> [String; 4] {
let mut out = [
sv(style, "top").to_string(),
sv(style, "right").to_string(),
sv(style, "bottom").to_string(),
sv(style, "left").to_string(),
];
let inset = sv(style, "inset");
if !inset.is_empty() {
let expanded = expand_static_box_values(&split_css_tokens(inset));
for (i, val) in expanded.into_iter().enumerate() {
if out[i].is_empty() || out[i] == "auto" {
out[i] = val;
}
}
}
out
}
/// JS: checks.mjs#checkElementStripeChild(el, style)
pub fn check_element_stripe_child(el: &StaticElement<'_>, style: &StyleValues) -> Vec<RuleHit> {
let tag = el.tag_lower();
if tag != "div" && tag != "span" {
return Vec::new();
}
let Some(host) = el.parent_element() else {
return Vec::new();
};
if host.tag_lower() == "body" || host.tag_lower() == "html" {
return Vec::new();
}
if !el.children().is_empty() {
return Vec::new();
}
if !collapsed_text_content(el).is_empty() {
return Vec::new();
}
if el.closest(STRIPE_CHILD_SKIP).is_some() {
return Vec::new();
}
if is_tab_context_element(el) || is_status_context_element(el) {
return Vec::new();
}
let width = css_length_to_px(sv(style, "width")).unwrap_or_else(|| pf0(sv(style, "width")));
let position = js::to_lower_case(sv(style, "position"));
let host_style = host.style();
let edge = if position == "absolute" || position == "fixed" {
let height_raw = sv(style, "height");
let inset = static_resolved_inset(style);
let height_stretches =
height_raw == "100%" || (static_edge_hugs(&inset[0]) && static_edge_hugs(&inset[2]));
if !height_stretches {
return Vec::new();
}
if static_edge_hugs(&inset[3]) {
Some("left")
} else if static_edge_hugs(&inset[1]) {
Some("right")
} else {
None
}
} else {
let pdisplay = sv(host_style, "display");
if !pdisplay.contains("flex") {
return Vec::new();
}
let pdir = sv(host_style, "flexDirection");
if pdir.starts_with("column") {
return Vec::new();
}
let align_self = sv(style, "alignSelf");
let effective_align = if !align_self.is_empty() && align_self != "auto" {
align_self
} else {
sv(host_style, "alignItems")
};
let is_stretch = effective_align.is_empty()
|| effective_align == "stretch"
|| effective_align == "normal";
let height_raw = sv(style, "height");
let height_stretches =
height_raw == "100%" || ((height_raw.is_empty() || height_raw == "auto") && is_stretch);
if !height_stretches {
return Vec::new();
}
let siblings = host.children();
if siblings.len() < 2 {
return Vec::new();
}
let reverse = pdir.contains("reverse");
if siblings.first() == Some(el) {
Some(if reverse { "right" } else { "left" })
} else if siblings.last() == Some(el) {
Some(if reverse { "left" } else { "right" })
} else {
None
}
};
let bg_raw = sv(style, "backgroundColor");
let bg = parse_rgb(Some(&bg_raw)).or_else(|| parse_any_color(Some(&bg_raw)));
check_stripe_child(&class_selector(el), width, edge, bg)
}
/// JS: checks.mjs#checkElementColors(el, style, tag, window, customPropMap, hasAnchorInheritRule)
pub fn check_element_colors(
el: &StaticElement<'_>,
+3
View File
@@ -184,6 +184,9 @@ pub const STATIC_PROP_MAP: &[(&str, &str)] = &[
("left", "left"),
("inset", "inset"),
("display", "display"),
("flex-direction", "flexDirection"),
("align-items", "alignItems"),
("align-self", "alignSelf"),
("overflow", "overflow"),
("overflow-x", "overflowX"),
("overflow-y", "overflowY"),
+33 -2
View File
@@ -65,7 +65,8 @@
//! parse in the same process).
use super::csstree::{self, Important, Node};
use super::shorthand::expand_static_declaration;
use super::shorthand::{expand_static_box_values, expand_static_declaration, Expanded};
use super::values::split_css_tokens;
use impeccable_core::js;
use indexmap::IndexMap;
use once_cell::sync::Lazy;
@@ -177,6 +178,32 @@ impl<K: Hash + Eq> SpecifiedStore<K> {
}
}
/// Layout properties the stripe-child static adapter needs that are not in
/// the frozen `expandStaticDeclaration` allowlist. Applied here so the
/// recorded vectors stay byte-equal.
fn extra_specified_expansions(prop: &str, value: &str) -> Vec<Expanded> {
let p = js::to_lower_case(prop);
let v = js::trim(value);
if v.is_empty() {
return Vec::new();
}
match p.as_str() {
"flex-direction" => vec![("flexDirection".into(), v.to_string())],
"align-items" => vec![("alignItems".into(), v.to_string())],
"align-self" => vec![("alignSelf".into(), v.to_string())],
"inset" => {
let vals = expand_static_box_values(&split_css_tokens(v));
vec![
("top".into(), vals[0].clone()),
("right".into(), vals[1].clone()),
("bottom".into(), vals[2].clone()),
("left".into(), vals[3].clone()),
]
}
_ => Vec::new(),
}
}
/// JS: css-cascade.mjs#applyStaticDeclaration(specified, node, prop, value, meta)
pub fn apply_static_declaration<K: Hash + Eq>(
specified: &mut SpecifiedStore<K>,
@@ -186,7 +213,11 @@ pub fn apply_static_declaration<K: Hash + Eq>(
meta: &DeclMeta,
) {
let map = specified.map.entry(node).or_default();
for (expanded_prop, expanded_value) in expand_static_declaration(prop, value) {
let extra = extra_specified_expansions(prop, value);
for (expanded_prop, expanded_value) in expand_static_declaration(prop, value)
.into_iter()
.chain(extra)
{
let existing = map.get(&expanded_prop).map(|d| &d.meta);
if compare_static_priority(existing, meta) {
let next = SpecifiedDecl {
+5 -2
View File
@@ -14,8 +14,9 @@ use crate::adapters::{
check_element_colors, check_element_glow, check_element_gpt_border_shadow,
check_element_hero_eyebrow, check_element_hover_contrast, check_element_icon_tile,
check_element_italic_serif, check_element_motion, check_element_oversized_h1,
check_element_radial_spotlight, check_kicker_above_heading_from_doc,
check_numbered_section_labels_from_doc, scoped_ignore_active,
check_element_radial_spotlight, check_element_stripe_child,
check_kicker_above_heading_from_doc, check_numbered_section_labels_from_doc,
scoped_ignore_active,
};
use crate::background::{resolve_background, resolve_border_radius_px, sv};
use crate::cascade::{build_static_style_map, collect_static_css_text};
@@ -109,6 +110,7 @@ const STATIC_ELEMENT_RULES: &[(&str, &str)] = &[
("dark-glow", "*"),
("motion-rules", "*"),
("icon-tile-stack", "h1,h2,h3,h4,h5,h6"),
("stripe-child", "div,span"),
("italic-serif-display", "h1,h2"),
("hero-eyebrow-chip", "h1"),
("broken-image", "img"),
@@ -134,6 +136,7 @@ fn run_rule(rule_id: &str, el: &StaticElement<'_>, tag: &str) -> Vec<RuleHit> {
}
"motion-rules" => check_element_motion(tag, style),
"icon-tile-stack" => check_element_icon_tile(el, tag),
"stripe-child" => check_element_stripe_child(el, style),
"italic-serif-display" => check_element_italic_serif(el, style, tag),
"hero-eyebrow-chip" => check_element_hero_eyebrow(el, style, tag),
"broken-image" => check_element_broken_image(el),
+167
View File
@@ -0,0 +1,167 @@
use impeccable_html::{detect_html_source, DetectHtmlOptions};
use std::path::Path;
fn side_tab_snippets(html: &str) -> Vec<String> {
detect_html_source(
html,
Path::new("/app/stripe.html"),
&DetectHtmlOptions::default(),
)
.into_iter()
.filter(|f| f.antipattern == "side-tab")
.map(|f| f.snippet)
.collect()
}
#[test]
fn flex_row_first_child_flags() {
let html = r#"<!DOCTYPE html><html><head><style>
.card { display: flex; flex-direction: row; width: 320px; height: 100px; }
.stripe { width: 4px; background: #f59e0b; }
.body { flex: 1; }
</style></head><body>
<div class="card"><div class="stripe"></div><div class="body">Content</div></div>
</body></html>"#;
let hits = side_tab_snippets(html);
assert_eq!(hits.len(), 1);
assert!(hits[0].contains("stripe child (left)"));
}
#[test]
fn absolute_left_inset_flags() {
let html = r#"<!DOCTYPE html><html><head><style>
.card { position: relative; width: 320px; height: 100px; }
.stripe { position: absolute; inset: 0 auto 0 0; width: 4px; background: #3b82f6; }
</style></head><body>
<div class="card"><div class="stripe"></div></div>
</body></html>"#;
let hits = side_tab_snippets(html);
assert_eq!(hits.len(), 1);
assert!(hits[0].contains("stripe child (left)"));
}
#[test]
fn absolute_top_bottom_flags() {
let html = r#"<!DOCTYPE html><html><head><style>
.card { position: relative; width: 320px; height: 100px; }
.stripe { position: absolute; left: 0; top: 0; bottom: 0; width: 4px; background: #3b82f6; }
</style></head><body>
<div class="card"><div class="stripe"></div></div>
</body></html>"#;
let hits = side_tab_snippets(html);
assert_eq!(hits.len(), 1);
assert!(hits[0].contains("stripe child (left)"));
}
#[test]
fn flex_column_does_not_flag() {
let html = r#"<!DOCTYPE html><html><head><style>
.card { display: flex; flex-direction: column; width: 320px; height: 100px; }
.stripe { width: 4px; background: #f59e0b; }
</style></head><body>
<div class="card"><div class="stripe"></div><div>Body</div></div>
</body></html>"#;
assert!(side_tab_snippets(html).is_empty());
}
#[test]
fn align_items_center_does_not_flag() {
let html = r#"<!DOCTYPE html><html><head><style>
.card { display: flex; align-items: center; width: 320px; height: 100px; }
.stripe { width: 4px; background: #f59e0b; }
</style></head><body>
<div class="card"><div class="stripe"></div><div>Body</div></div>
</body></html>"#;
assert!(side_tab_snippets(html).is_empty());
}
#[test]
fn align_self_flex_start_does_not_flag() {
let html = r#"<!DOCTYPE html><html><head><style>
.card { display: flex; width: 320px; height: 100px; }
.stripe { width: 4px; align-self: flex-start; background: #f59e0b; }
</style></head><body>
<div class="card"><div class="stripe"></div><div>Body</div></div>
</body></html>"#;
assert!(side_tab_snippets(html).is_empty());
}
#[test]
fn neutral_and_contentful_and_wide_do_not_flag() {
let neutral = r#"<!DOCTYPE html><html><head><style>
.card { display: flex; width: 320px; height: 100px; }
.stripe { width: 4px; background: #e5e5e5; }
</style></head><body>
<div class="card"><div class="stripe"></div><div>Body</div></div>
</body></html>"#;
assert!(side_tab_snippets(neutral).is_empty());
let text = r#"<!DOCTYPE html><html><head><style>
.card { display: flex; width: 320px; height: 100px; }
.stripe { width: 4px; background: #f59e0b; }
</style></head><body>
<div class="card"><div class="stripe">!</div><div>Body</div></div>
</body></html>"#;
assert!(side_tab_snippets(text).is_empty());
let wide = r#"<!DOCTYPE html><html><head><style>
.card { display: flex; width: 320px; height: 100px; }
.stripe { width: 40px; background: #f59e0b; }
</style></head><body>
<div class="card"><div class="stripe"></div><div>Body</div></div>
</body></html>"#;
assert!(side_tab_snippets(wide).is_empty());
}
#[test]
fn rem_width_flags() {
let html = r#"<!DOCTYPE html><html><head><style>
.card { display: flex; width: 320px; height: 100px; }
.stripe { width: 0.25rem; background: #f59e0b; }
</style></head><body>
<div class="card"><div class="stripe"></div><div>Body</div></div>
</body></html>"#;
let hits = side_tab_snippets(html);
assert_eq!(hits.len(), 1);
assert!(hits[0].contains("stripe child (left)"));
}
#[test]
fn height_full_with_align_center_flags() {
let html = r#"<!DOCTYPE html><html><head><style>
.card { display: flex; align-items: center; width: 320px; height: 100px; }
.stripe { width: 4px; height: 100%; background: #f59e0b; }
</style></head><body>
<div class="card"><div class="stripe"></div><div>Body</div></div>
</body></html>"#;
let hits = side_tab_snippets(html);
assert_eq!(hits.len(), 1);
assert!(hits[0].contains("stripe child (left)"));
}
#[test]
fn inset_after_left_longhand_flags() {
let html = r#"<!DOCTYPE html><html><head><style>
.card { position: relative; width: 320px; height: 100px; }
.stripe { position: absolute; left: 10px; inset: 0 auto 0 0; width: 4px; background: #3b82f6; }
</style></head><body>
<div class="card"><div class="stripe"></div></div>
</body></html>"#;
let hits = side_tab_snippets(html);
assert_eq!(hits.len(), 1);
assert!(hits[0].contains("stripe child (left)"));
}
#[test]
fn row_reverse_first_child_is_right() {
let html = r#"<!DOCTYPE html><html><head><style>
.card { display: flex; flex-direction: row-reverse; width: 320px; height: 100px; }
.stripe { width: 4px; background: #f59e0b; }
.body { flex: 1; }
</style></head><body>
<div class="card"><div class="stripe"></div><div class="body">Content</div></div>
</body></html>"#;
let hits = side_tab_snippets(html);
assert_eq!(hits.len(), 1);
assert!(hits[0].contains("stripe child (right)"));
}
File diff suppressed because one or more lines are too long
+55 -1
View File
@@ -596,6 +596,18 @@ fn write_carbonize_banner(event: &Map<String, Value>, io: &mut Io) {
}
}
fn reply_ack_json(reply: &Reply) -> Value {
let mut m = Map::new();
m.insert("ok".into(), json!(true));
m.insert("id".into(), json!(reply.id));
m.insert("status".into(), json!(reply.ty));
if let Some(f) = &reply.file {
m.insert("file".into(), json!(f));
}
m.insert("_instructions".into(), json!("Poll again now."));
Value::Object(m)
}
/// JS: printPollEvent(event) — a wire-supplied `_instructions` must never
/// win over the locally generated one (#488).
fn print_poll_event(event: &mut Value, io: &mut Io) {
@@ -718,7 +730,13 @@ pub fn run(args: &[String], io: &mut Io) -> i32 {
}
};
return match post_reply(&base, &token, &reply) {
Ok(()) => 0,
Ok(()) => {
println(
io,
&serde_json::to_string(&reply_ack_json(&reply)).unwrap_or_default(),
);
0
}
Err(PollError::ConnRefused) => {
io.err(&format!(
"Live server not running. Start one with: {}\n",
@@ -839,4 +857,40 @@ mod tests {
}));
assert!(parsed.get("_instructions").is_none(), "{}", parsed);
}
#[test]
fn reply_ack_json_includes_file_when_present() {
let reply = Reply {
id: "ab12cd34".into(),
ty: "done".into(),
message: None,
file: Some("index.html".into()),
data: None,
source_event_type: None,
};
let parsed = reply_ack_json(&reply);
assert_eq!(parsed["ok"], json!(true));
assert_eq!(parsed["id"], json!("ab12cd34"));
assert_eq!(parsed["status"], json!("done"));
assert_eq!(parsed["file"], json!("index.html"));
assert_eq!(parsed["_instructions"], json!("Poll again now."));
}
#[test]
fn reply_ack_json_omits_file_when_absent() {
let reply = Reply {
id: "ab12cd34".into(),
ty: "steer_done".into(),
message: None,
file: None,
data: None,
source_event_type: None,
};
let parsed = reply_ack_json(&reply);
assert_eq!(parsed["ok"], json!(true));
assert_eq!(parsed["id"], json!("ab12cd34"));
assert_eq!(parsed["status"], json!("steer_done"));
assert!(parsed.get("file").is_none(), "{}", parsed);
assert_eq!(parsed["_instructions"], json!("Poll again now."));
}
}
+72
View File
@@ -418,6 +418,16 @@ fn push_diag(next: &mut Map<String, Value>, d: Value) {
next.insert("diagnostics".to_string(), Value::Array(arr));
}
fn drop_diag(next: &mut Map<String, Value>, error: &str) {
let mut arr = next
.get("diagnostics")
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_default();
arr.retain(|d| d.get("error").and_then(|e| e.as_str()) != Some(error));
next.insert("diagnostics".to_string(), Value::Array(arr));
}
/// JS: applyEvent(snapshot, entry)
pub fn apply_event(snapshot: &Map<String, Value>, entry: &Value) -> Map<String, Value> {
let event: Map<String, Value> = match entry.get("event") {
@@ -864,6 +874,7 @@ pub fn apply_event(snapshot: &Map<String, Value>, entry: &Value) -> Map<String,
set!("phase", json!("discarded"));
set!("pendingEventSeq", Value::Null);
set!("pendingEvent", Value::Null);
drop_diag(&mut next, "carbonize_cleanup_required");
}
"complete" => {
set!("phase", json!("completed"));
@@ -876,6 +887,7 @@ pub fn apply_event(snapshot: &Map<String, Value>, entry: &Value) -> Map<String,
set_if!("previewMode", ev("previewMode"));
set!("pendingEventSeq", Value::Null);
set!("pendingEvent", Value::Null);
drop_diag(&mut next, "carbonize_cleanup_required");
}
"agent_error" => {
if canceled && ev("sourceEventType").and_then(|v| v.as_str()) == Some("generate") {
@@ -925,3 +937,63 @@ fn write_snapshot(path: &str, snapshot: &Map<String, Value>, journal_bytes: i64,
pub fn get_str<'a>(m: &'a Map<String, Value>, k: &str) -> Option<&'a str> {
get(m, k).and_then(|v| v.as_str())
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn journal_entry(seq: i64, event: Value) -> Value {
json!({ "seq": seq, "ts": "2026-01-01T00:00:00.000Z", "event": event })
}
fn has_diag(snapshot: &Map<String, Value>, error: &str) -> bool {
snapshot
.get("diagnostics")
.and_then(|v| v.as_array())
.map(|a| {
a.iter()
.any(|d| d.get("error").and_then(|e| e.as_str()) == Some(error))
})
.unwrap_or(false)
}
fn replay(id: &str, events: &[Value]) -> Map<String, Value> {
let mut snap = base_snapshot(id);
for entry in events {
snap = apply_event(&snap, entry);
}
snap
}
fn accept_carbonize_done(id: &str, terminal: &str) -> Map<String, Value> {
replay(
id,
&[
journal_entry(
1,
json!({ "id": id, "type": "accept", "variantId": 2 }),
),
journal_entry(
2,
json!({ "id": id, "type": "agent_done", "carbonize": true, "file": "index.html" }),
),
journal_entry(3, json!({ "id": id, "type": terminal })),
],
)
}
#[test]
fn complete_drops_carbonize_cleanup_required() {
let snap = accept_carbonize_done("ab12cd34", "complete");
assert_eq!(snap.get("phase").and_then(|p| p.as_str()), Some("completed"));
assert!(!has_diag(&snap, "carbonize_cleanup_required"));
}
#[test]
fn discarded_drops_carbonize_cleanup_required() {
let snap = accept_carbonize_done("ab12cd34", "discarded");
assert_eq!(snap.get("phase").and_then(|p| p.as_str()), Some("discarded"));
assert!(!has_diag(&snap, "carbonize_cleanup_required"));
}
}
+2 -2
View File
@@ -332,7 +332,7 @@ Optional keys added later by engines (appended after the above): `ignoreValue` (
#### Static and regex engines (only what affects the contract)
- `detectHtml`: reads file, imports `htmlparser2`, `css-select`, `css-tree`, `domutils`; on import failure prints once to stderr `impeccable detect: DEGRADED - HTML parser modules unavailable (htmlparser2, css-select, css-tree, domutils).\nFalling back to regex matching. Custom properties, selector matching and computed contrast are NOT evaluated; findings are an undercount, not a clean bill of health.\n` and falls back to `detectText`. Inlines `<link rel=stylesheet href>` that are local (not `/^(https?:)?\/\//i`), query/hash stripped. Runs element rules, design-system rules (`checkSourceDesignSystem` + `collectStaticDesignSystemFindings`, merged), then page rules only when `isFullPage(html)` (`/<!doctype\s|<html[\s>]|<head[\s>]/i` after stripping comments), plus text-content analyzers; ends with inline-ignore filtering.
- `detectText`: regex line matchers (ids: side-tab, border-accent-on-rounded, overused-font, gradient-text, ai-color-palette, gray-on-color, bounce-easing, layout-transition, broken-image), inset-stripe/pseudo-stripe CSS scans, `codex-grid-background`, `<style>` blocks (Astro/Vue/Svelte), CSS-in-JS templates, design-system source checks; dedupe (same antipattern+snippet within 2 lines); page analyzers only when `isFullPage` and ext ∈ `{'.html','.htm','.astro','.vue','.svelte'}` or no ext (`<stdin>`): flat-type-hierarchy, monotonous-spacing, em-dash-overuse, marketing-buzzword, aphoristic-cadence, dark-glow (+ radial-halo, marquee); inline ignores last.
- `detectText`: regex line matchers (ids: side-tab including Tailwind stripe-child empty `w-*` + chromatic `bg-*` plus a `shrink-0` / edge-round cue, border-accent-on-rounded, overused-font, gradient-text, ai-color-palette, gray-on-color, bounce-easing, layout-transition, broken-image), inset-stripe/pseudo-stripe CSS scans, `codex-grid-background`, `<style>` blocks (Astro/Vue/Svelte), CSS-in-JS templates, design-system source checks; dedupe (same antipattern+snippet within 2 lines); page analyzers only when `isFullPage` and ext ∈ `{'.html','.htm','.astro','.vue','.svelte'}` or no ext (`<stdin>`): flat-type-hierarchy, monotonous-spacing, em-dash-overuse, marketing-buzzword, aphoristic-cadence, dark-glow (+ radial-halo, marquee); inline ignores last.
#### Profiler (`cli/engine/profile/profiler.mjs`)
@@ -1740,7 +1740,7 @@ Conventions: every script's "run directly" guard is `process.argv[1]` ending wit
#### `live-poll.mjs` -> `impeccable poll`
- Invoked from live.md poll loop; `--reply` forms quoted in `_instructions` (see instructions.mjs strings in 6.3/below).
- Args: `--stream`, `--timeout=MS` (one-shot total, default 600000), `--types=A,B`, `--ack-timeout=MS` (stream, default 600000), `--reply <id> <status> [--file PATH] [--data JSON] [message]`, `--help`. `--reply` errors (stderr, exit 1): `Usage: node "<abs>/live-poll.mjs" --reply <id> <status> [--file path] [--data '<json>'] [message]` + `Missing event id after --reply.` / `The value after --reply must be the event id, not the status "done". Use --reply EVENT_ID done.` / `Missing reply status after event id "X".`; `--data must be valid JSON: <err>`.
- Args: `--stream`, `--timeout=MS` (one-shot total, default 600000), `--types=A,B`, `--ack-timeout=MS` (stream, default 600000), `--reply <id> <status> [--file PATH] [--data JSON] [message]`, `--help`. `--reply` success (stdout, exit 0): one compact JSON line `{ok:true,id,status,file? (only when --file was passed),_instructions:'Poll again now.'}`. `--reply` errors (stderr, exit 1): `Usage: node "<abs>/live-poll.mjs" --reply <id> <status> [--file path] [--data '<json>'] [message]` + `Missing event id after --reply.` / `The value after --reply must be the event id, not the status "done". Use --reply EVENT_ID done.` / `Missing reply status after event id "X".`; `--data must be valid JSON: <err>`.
- Needs `server.json`; else stderr `No running live server found. Start one with: node "<abs>/live.mjs"` exit 1.
- One-shot: loops `GET /poll?token&timeout=<slice ≤270000>&leaseMs=600000[&types]` until an event or total deadline; prints one JSON line (`console.log(JSON.stringify(event))`) with `_instructions` added by `instructionsForEvent` (unless already present). For `accept`/`discard`: spawns `node live-accept.mjs --id ID (--discard | --variant N) [--page-url U] [--param-values JSON]` (30 s), sets `event._acceptResult` (parse failure/throw → `{handled:false, mode:'error', error}`), then POSTs completion `{id, type: completionType, sourceEventType: event.type, message: _acceptResult.error, file: _acceptResult.file, data: {carbonize:true}?}` where completionType = discard: `discarded` if handled else `error`; accept: `agent_done` if handled&carbonize, `complete` if handled, `error` if mode error or (svelte-component unhandled), else `agent_done`; sets `event._completionAck = {ok:true, type}` (+ `final:false, requiresComplete:true, nextCommand:'live-complete.mjs --id <id>', message:'Carbonize cleanup must be verified, then the session must be completed explicitly before polling again.'` for carbonize) or `{ok:false, error}`. Stderr banners: manual_edit_apply → 4-line banner starting `Manual Apply action required: edit source, then reply with \`live-poll.mjs --reply <id> done --data '<json>'\`.`; carbonize → `⚠ Carbonize cleanup REQUIRED before next poll. After cleanup, run live-complete.mjs --id <id>. See reference/live.md "Required after accept".`
- Stream: stderr `[impeccable-poll] stream mode: one JSON object per line on stdout; use --reply while this process stays running`; after each reply-needing event waits (poll `/status` every 400 ms) until the id leaves `pendingEvents` (else `Timed out waiting for --reply on event <id>` exit 1); returns on `exit`.
+121
View File
@@ -0,0 +1,121 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Stripe-Child Side-Tab Fixture</title>
<style>
body { font-family: system-ui, sans-serif; margin: 0; padding: 24px; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; max-width: 960px; margin: 0 auto; }
.col h2 { font-size: 14px; text-transform: uppercase; letter-spacing: 0.05em; margin: 0 0 16px; color: #475569; }
.case { margin-bottom: 24px; padding: 16px; border: 1px solid #e2e8f0; border-radius: 12px; background: #fff; }
/* FLAG: flex-row first child stripe */
.card-flex-left { display: flex; flex-direction: row; width: 320px; height: 100px; border: 1px solid #e2e8f0; border-radius: 12px; overflow: hidden; }
.stripe-left { width: 4px; background: #f59e0b; flex-shrink: 0; }
.card-flex-left .body { flex: 1; padding: 16px; }
/* FLAG: flex-row last child stripe */
.card-flex-right { display: flex; flex-direction: row; width: 320px; height: 100px; border: 1px solid #e2e8f0; border-radius: 12px; overflow: hidden; }
.card-flex-right .body { flex: 1; padding: 16px; }
.stripe-right { width: 4px; background: #3b82f6; flex-shrink: 0; }
/* FLAG: absolute left stripe */
.card-abs { position: relative; width: 320px; height: 100px; border: 1px solid #e2e8f0; border-radius: 12px; }
.stripe-abs { position: absolute; left: 0; top: 0; bottom: 0; width: 4px; background: #f59e0b; }
/* FLAG: absolute inset shorthand */
.card-inset { position: relative; width: 320px; height: 100px; border: 1px solid #e2e8f0; border-radius: 12px; }
.stripe-inset { position: absolute; inset: 0 auto 0 0; width: 4px; background: #3b82f6; }
/* PASS: neutral gray stripe */
.card-neutral { display: flex; width: 320px; height: 100px; border: 1px solid #e2e8f0; border-radius: 12px; }
.stripe-neutral { width: 4px; background: #e5e5e5; }
/* PASS: black / low-spread fill */
.card-black { display: flex; width: 320px; height: 100px; border: 1px solid #e2e8f0; border-radius: 12px; }
.stripe-black { width: 4px; background: black; }
/* PASS: align-items center (short child) */
.card-center { display: flex; align-items: center; width: 320px; height: 100px; border: 1px solid #e2e8f0; border-radius: 12px; }
.stripe-center { width: 4px; background: #f59e0b; }
/* PASS: flex-direction column */
.card-column { display: flex; flex-direction: column; width: 320px; height: 100px; border: 1px solid #e2e8f0; border-radius: 12px; }
.stripe-column { width: 4px; background: #f59e0b; }
/* PASS: active/selected host context */
.card-active { display: flex; width: 320px; height: 100px; border: 1px solid #e2e8f0; border-radius: 12px; }
.card-active.is-active .stripe-active { width: 4px; background: #f59e0b; }
/* PASS: contentful narrow child */
.card-text { display: flex; width: 320px; height: 100px; border: 1px solid #e2e8f0; border-radius: 12px; }
.stripe-text { width: 4px; background: #f59e0b; }
/* PASS: progressbar role */
.card-progress { display: flex; width: 320px; height: 100px; border: 1px solid #e2e8f0; border-radius: 12px; }
.stripe-progress { width: 4px; background: #f59e0b; }
/* PASS: wide first child */
.card-wide { display: flex; width: 320px; height: 100px; border: 1px solid #e2e8f0; border-radius: 12px; }
.stripe-wide { width: 40px; background: #f59e0b; }
</style>
</head>
<body>
<div class="grid">
<div class="col">
<h2>Should flag</h2>
<div class="case">
<h3>Flex row first-child stripe</h3>
<div class="card-flex-left"><div class="stripe-left"></div><div class="body">Card body</div></div>
</div>
<div class="case">
<h3>Flex row last-child stripe</h3>
<div class="card-flex-right"><div class="body">Card body</div><div class="stripe-right"></div></div>
</div>
<div class="case">
<h3>Absolute left stripe</h3>
<div class="card-abs"><div class="stripe-abs"></div><div class="body">Card body</div></div>
</div>
<div class="case">
<h3>Absolute inset stripe</h3>
<div class="card-inset"><div class="stripe-inset"></div><div class="body">Card body</div></div>
</div>
</div>
<div class="col">
<h2>Should pass</h2>
<div class="case">
<h3>Neutral gray stripe</h3>
<div class="card-neutral"><div class="stripe-neutral"></div><div class="body">Card body</div></div>
</div>
<div class="case">
<h3>Black stripe fill</h3>
<div class="card-black"><div class="stripe-black"></div><div class="body">Card body</div></div>
</div>
<div class="case">
<h3>Align items center short child</h3>
<div class="card-center"><div class="stripe-center"></div><div class="body">Card body</div></div>
</div>
<div class="case">
<h3>Flex column layout</h3>
<div class="card-column"><div class="stripe-column"></div><div class="body">Card body</div></div>
</div>
<div class="case">
<h3>Active selected host</h3>
<div class="card-active is-active"><div class="stripe-active"></div><div>Card body</div></div>
</div>
<div class="case">
<h3>Contentful narrow child</h3>
<div class="card-text"><div class="stripe-text">|</div><div class="body">Card body</div></div>
</div>
<div class="case">
<h3>Progressbar context</h3>
<div class="card-progress" role="progressbar"><div class="stripe-progress"></div><div class="body">50%</div></div>
</div>
<div class="case">
<h3>Wide forty pixel child</h3>
<div class="card-wide"><div class="stripe-wide"></div><div class="body">Card body</div></div>
</div>
</div>
</div>
</body>
</html>
+61
View File
@@ -0,0 +1,61 @@
// Dedicated stripe-child side-tab fixture (Tailwind / JSX text path)
export function StripeChildCard() {
return (
<div className="flex rounded-lg border">
<div className="w-1 shrink-0 rounded-l-lg bg-amber-500" />
<div className="p-4">Card content</div>
</div>
);
}
export function StripeChildBracketWidth() {
return <div className="w-[4px] bg-blue-500 shrink-0" />;
}
export function StripeChildHalf() {
return <span className="w-0.5 bg-rose-500 shrink-0" />;
}
export function StripeChildMinHeightOk() {
return <div className="w-1 min-h-0 bg-amber-500 shrink-0" />;
}
// PASS: dot indicator, not a stripe
export function DotIndicator() {
return <div className="w-2 h-2 rounded-full bg-green-500" />;
}
// PASS: small square with sibling text (gray-on-color sibling line)
export function VitalFewLegend() {
return (
<div className="flex items-center gap-1.5">
<div className="w-3 h-3 rounded bg-amber-500" />
<span className="text-slate-400">Vital few</span>
</div>
);
}
// PASS: chart bar with explicit height
export function ChartBar() {
return <div className="w-3 h-24 bg-blue-500" />;
}
// PASS: opacity tint
export function TintStripe() {
return <div className="w-1 bg-amber-500/10 shrink-0" />;
}
// PASS: aria-current on stripe tag
export function CurrentNavStripe() {
return <a className="w-1 bg-amber-500" aria-current="page" />;
}
// PASS: width and chromatic bg on sibling tags
export function SplitSiblingClasses() {
return (
<div className="w-1 shrink-0">
<span className="bg-amber-500" />
</div>
);
}
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
{
"stdout": "",
"stderr": "415 anti-patterns found.\n17 advisory notes (not counted).\n",
"stderr": "432 anti-patterns found.\n17 advisory notes (not counted).\n",
"exit": 2,
"signal": null,
"files": {}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,7 @@
{
"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\": \"<REPO>/tests/fixtures/antipatterns/stripe-child.jsx\",\n \"line\": 6,\n \"snippet\": \"w-1 + bg-amber-500 stripe child\"\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\": \"<REPO>/tests/fixtures/antipatterns/stripe-child.jsx\",\n \"line\": 13,\n \"snippet\": \"w-[4px] + bg-blue-500 stripe child\"\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\": \"<REPO>/tests/fixtures/antipatterns/stripe-child.jsx\",\n \"line\": 17,\n \"snippet\": \"w-0.5 + bg-rose-500 stripe child\"\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\": \"<REPO>/tests/fixtures/antipatterns/stripe-child.jsx\",\n \"line\": 21,\n \"snippet\": \"w-1 + bg-amber-500 stripe child\"\n }\n]\n",
"stderr": "",
"exit": 2,
"signal": null,
"files": {}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,7 @@
{
"stdout": "",
"stderr": "\n<REPO>/tests/fixtures/antipatterns/stripe-child.jsx\n line 6: [side-tab] w-1 + bg-amber-500 stripe child\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 line 13: [side-tab] w-[4px] + bg-blue-500 stripe child\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 line 17: [side-tab] w-0.5 + bg-rose-500 stripe child\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 line 21: [side-tab] w-1 + bg-amber-500 stripe child\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\n4 anti-patterns found.\n",
"exit": 2,
"signal": null,
"files": {}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -1,11 +1,11 @@
{
"stdout": "{\n \"ok\": true,\n \"id\": \"ab12cd34\",\n \"phase\": \"completed\",\n \"snapshot\": {\n \"id\": \"ab12cd34\",\n \"phase\": \"completed\",\n \"pageUrl\": \"/\",\n \"sourceFile\": \"index.html\",\n \"previewFile\": null,\n \"previewMode\": null,\n \"expectedVariants\": 3,\n \"arrivedVariants\": 3,\n \"visibleVariant\": 2,\n \"paramValues\": {\n \"face\": \"serif\"\n },\n \"pendingEventSeq\": null,\n \"pendingEvent\": null,\n \"deliveryLease\": null,\n \"checkpointRevision\": 0,\n \"browserCheckpointRevision\": 0,\n \"publicationCheckpointRevision\": 0,\n \"activeOwner\": null,\n \"sourceMarkers\": {},\n \"fallbackMode\": null,\n \"generationPhase\": null,\n \"generationCompletedAt\": 1785578580000,\n \"generationTimings\": {},\n \"variantPlan\": null,\n \"generationCanceled\": true,\n \"generationCanceledAt\": 1785578520000,\n \"cancelReason\": \"accept\",\n \"annotationArtifacts\": [],\n \"mountedVariants\": [],\n \"mountFailures\": [],\n \"renderState\": \"pending\",\n \"diagnostics\": [\n {\n \"error\": \"carbonize_cleanup_required\",\n \"file\": \"index.html\",\n \"message\": \"Accepted variant still has carbonize markers that must be folded into source CSS.\"\n }\n ],\n \"updatedAt\": \"<ISO>\"\n }\n}\n",
"stdout": "{\n \"ok\": true,\n \"id\": \"ab12cd34\",\n \"phase\": \"completed\",\n \"snapshot\": {\n \"id\": \"ab12cd34\",\n \"phase\": \"completed\",\n \"pageUrl\": \"/\",\n \"sourceFile\": \"index.html\",\n \"previewFile\": null,\n \"previewMode\": null,\n \"expectedVariants\": 3,\n \"arrivedVariants\": 3,\n \"visibleVariant\": 2,\n \"paramValues\": {\n \"face\": \"serif\"\n },\n \"pendingEventSeq\": null,\n \"pendingEvent\": null,\n \"deliveryLease\": null,\n \"checkpointRevision\": 0,\n \"browserCheckpointRevision\": 0,\n \"publicationCheckpointRevision\": 0,\n \"activeOwner\": null,\n \"sourceMarkers\": {},\n \"fallbackMode\": null,\n \"generationPhase\": null,\n \"generationCompletedAt\": 1785578580000,\n \"generationTimings\": {},\n \"variantPlan\": null,\n \"generationCanceled\": true,\n \"generationCanceledAt\": 1785578520000,\n \"cancelReason\": \"accept\",\n \"annotationArtifacts\": [],\n \"mountedVariants\": [],\n \"mountFailures\": [],\n \"renderState\": \"pending\",\n \"diagnostics\": [],\n \"updatedAt\": \"<ISO>\"\n }\n}\n",
"stderr": "",
"exit": 0,
"signal": null,
"files": {
".impeccable/live/config.json": "{\n \"files\": [\"index.html\", \"public/**/*.html\"],\n \"insertBefore\": \"</body>\",\n \"commentSyntax\": \"html\"\n}\n",
".impeccable/live/sessions/ab12cd34.jsonl": "{\"seq\":1,\"id\":\"ab12cd34\",\"type\":\"generate\",\"ts\":\"<ISO>\",\"event\":{\"id\":\"ab12cd34\",\"type\":\"generate\",\"action\":\"bolder\",\"count\":3,\"pageUrl\":\"/\",\"element\":{\"tagName\":\"h1\",\"id\":\"hero\",\"classes\":[\"hero-title\"],\"textContent\":\"Oracle Fixture\",\"outerHTML\":\"<h1 id=\\\"hero\\\" class=\\\"hero-title\\\">Oracle Fixture</h1>\"},\"clientSentAt\":1754042400000}}\n{\"seq\":2,\"id\":\"ab12cd34\",\"type\":\"agent_done\",\"ts\":\"<ISO>\",\"event\":{\"id\":\"ab12cd34\",\"type\":\"agent_done\",\"file\":\"index.html\",\"sourceEventType\":\"generate\",\"carbonize\":false,\"arrivedVariants\":3}}\n{\"seq\":3,\"id\":\"ab12cd34\",\"type\":\"accept\",\"ts\":\"<ISO>\",\"event\":{\"id\":\"ab12cd34\",\"type\":\"accept\",\"variantId\":\"2\",\"pageUrl\":\"/\",\"paramValues\":{\"face\":\"serif\"}}}\n{\"seq\":4,\"id\":\"ab12cd34\",\"type\":\"agent_done\",\"ts\":\"<ISO>\",\"event\":{\"id\":\"ab12cd34\",\"type\":\"agent_done\",\"file\":\"index.html\",\"sourceEventType\":\"accept\",\"carbonize\":true}}\n{\"seq\":5,\"id\":\"ab12cd34\",\"type\":\"complete\",\"ts\":\"<ISO>\",\"event\":{\"type\":\"complete\",\"id\":\"ab12cd34\"}}\n",
".impeccable/live/sessions/ab12cd34.snapshot.json": "{\n \"id\": \"ab12cd34\",\n \"phase\": \"completed\",\n \"pageUrl\": \"/\",\n \"sourceFile\": \"index.html\",\n \"previewFile\": null,\n \"previewMode\": null,\n \"expectedVariants\": 3,\n \"arrivedVariants\": 3,\n \"visibleVariant\": 2,\n \"paramValues\": {\n \"face\": \"serif\"\n },\n \"pendingEventSeq\": null,\n \"pendingEvent\": null,\n \"deliveryLease\": null,\n \"checkpointRevision\": 0,\n \"browserCheckpointRevision\": 0,\n \"publicationCheckpointRevision\": 0,\n \"activeOwner\": null,\n \"sourceMarkers\": {},\n \"fallbackMode\": null,\n \"generationPhase\": null,\n \"generationCompletedAt\": 1785578580000,\n \"generationTimings\": {},\n \"variantPlan\": null,\n \"generationCanceled\": true,\n \"generationCanceledAt\": 1785578520000,\n \"cancelReason\": \"accept\",\n \"annotationArtifacts\": [],\n \"mountedVariants\": [],\n \"mountFailures\": [],\n \"renderState\": \"pending\",\n \"diagnostics\": [\n {\n \"error\": \"carbonize_cleanup_required\",\n \"file\": \"index.html\",\n \"message\": \"Accepted variant still has carbonize markers that must be folded into source CSS.\"\n }\n ],\n \"updatedAt\": \"<ISO>\",\n \"__journalBytes\": 1053,\n \"__nextSeq\": 6\n}\n"
".impeccable/live/sessions/ab12cd34.snapshot.json": "{\n \"id\": \"ab12cd34\",\n \"phase\": \"completed\",\n \"pageUrl\": \"/\",\n \"sourceFile\": \"index.html\",\n \"previewFile\": null,\n \"previewMode\": null,\n \"expectedVariants\": 3,\n \"arrivedVariants\": 3,\n \"visibleVariant\": 2,\n \"paramValues\": {\n \"face\": \"serif\"\n },\n \"pendingEventSeq\": null,\n \"pendingEvent\": null,\n \"deliveryLease\": null,\n \"checkpointRevision\": 0,\n \"browserCheckpointRevision\": 0,\n \"publicationCheckpointRevision\": 0,\n \"activeOwner\": null,\n \"sourceMarkers\": {},\n \"fallbackMode\": null,\n \"generationPhase\": null,\n \"generationCompletedAt\": 1785578580000,\n \"generationTimings\": {},\n \"variantPlan\": null,\n \"generationCanceled\": true,\n \"generationCanceledAt\": 1785578520000,\n \"cancelReason\": \"accept\",\n \"annotationArtifacts\": [],\n \"mountedVariants\": [],\n \"mountFailures\": [],\n \"renderState\": \"pending\",\n \"diagnostics\": [],\n \"updatedAt\": \"<ISO>\",\n \"__journalBytes\": 1053,\n \"__nextSeq\": 6\n}\n"
}
}
@@ -1,11 +1,11 @@
{
"stdout": "{\n \"ok\": true,\n \"id\": \"ab12cd34\",\n \"phase\": \"completed\",\n \"snapshot\": {\n \"id\": \"ab12cd34\",\n \"phase\": \"completed\",\n \"pageUrl\": \"/\",\n \"sourceFile\": \"index.html\",\n \"previewFile\": null,\n \"previewMode\": null,\n \"expectedVariants\": 3,\n \"arrivedVariants\": 3,\n \"visibleVariant\": 2,\n \"paramValues\": {\n \"face\": \"serif\"\n },\n \"pendingEventSeq\": null,\n \"pendingEvent\": null,\n \"deliveryLease\": null,\n \"checkpointRevision\": 0,\n \"browserCheckpointRevision\": 0,\n \"publicationCheckpointRevision\": 0,\n \"activeOwner\": null,\n \"sourceMarkers\": {},\n \"fallbackMode\": null,\n \"generationPhase\": null,\n \"generationCompletedAt\": 1785578580000,\n \"generationTimings\": {},\n \"variantPlan\": null,\n \"generationCanceled\": true,\n \"generationCanceledAt\": 1785578520000,\n \"cancelReason\": \"accept\",\n \"annotationArtifacts\": [],\n \"mountedVariants\": [],\n \"mountFailures\": [],\n \"renderState\": \"pending\",\n \"diagnostics\": [\n {\n \"error\": \"carbonize_cleanup_required\",\n \"file\": \"index.html\",\n \"message\": \"Accepted variant still has carbonize markers that must be folded into source CSS.\"\n }\n ],\n \"updatedAt\": \"<ISO>\"\n }\n}\n",
"stdout": "{\n \"ok\": true,\n \"id\": \"ab12cd34\",\n \"phase\": \"completed\",\n \"snapshot\": {\n \"id\": \"ab12cd34\",\n \"phase\": \"completed\",\n \"pageUrl\": \"/\",\n \"sourceFile\": \"index.html\",\n \"previewFile\": null,\n \"previewMode\": null,\n \"expectedVariants\": 3,\n \"arrivedVariants\": 3,\n \"visibleVariant\": 2,\n \"paramValues\": {\n \"face\": \"serif\"\n },\n \"pendingEventSeq\": null,\n \"pendingEvent\": null,\n \"deliveryLease\": null,\n \"checkpointRevision\": 0,\n \"browserCheckpointRevision\": 0,\n \"publicationCheckpointRevision\": 0,\n \"activeOwner\": null,\n \"sourceMarkers\": {},\n \"fallbackMode\": null,\n \"generationPhase\": null,\n \"generationCompletedAt\": 1785578580000,\n \"generationTimings\": {},\n \"variantPlan\": null,\n \"generationCanceled\": true,\n \"generationCanceledAt\": 1785578520000,\n \"cancelReason\": \"accept\",\n \"annotationArtifacts\": [],\n \"mountedVariants\": [],\n \"mountFailures\": [],\n \"renderState\": \"pending\",\n \"diagnostics\": [],\n \"updatedAt\": \"<ISO>\"\n }\n}\n",
"stderr": "",
"exit": 0,
"signal": null,
"files": {
".impeccable/live/config.json": "{\n \"files\": [\"index.html\", \"public/**/*.html\"],\n \"insertBefore\": \"</body>\",\n \"commentSyntax\": \"html\"\n}\n",
".impeccable/live/sessions/ab12cd34.jsonl": "{\"seq\":1,\"id\":\"ab12cd34\",\"type\":\"generate\",\"ts\":\"<ISO>\",\"event\":{\"id\":\"ab12cd34\",\"type\":\"generate\",\"action\":\"bolder\",\"count\":3,\"pageUrl\":\"/\",\"element\":{\"tagName\":\"h1\",\"id\":\"hero\",\"classes\":[\"hero-title\"],\"textContent\":\"Oracle Fixture\",\"outerHTML\":\"<h1 id=\\\"hero\\\" class=\\\"hero-title\\\">Oracle Fixture</h1>\"},\"clientSentAt\":1754042400000}}\n{\"seq\":2,\"id\":\"ab12cd34\",\"type\":\"agent_done\",\"ts\":\"<ISO>\",\"event\":{\"id\":\"ab12cd34\",\"type\":\"agent_done\",\"file\":\"index.html\",\"sourceEventType\":\"generate\",\"carbonize\":false,\"arrivedVariants\":3}}\n{\"seq\":3,\"id\":\"ab12cd34\",\"type\":\"accept\",\"ts\":\"<ISO>\",\"event\":{\"id\":\"ab12cd34\",\"type\":\"accept\",\"variantId\":\"2\",\"pageUrl\":\"/\",\"paramValues\":{\"face\":\"serif\"}}}\n{\"seq\":4,\"id\":\"ab12cd34\",\"type\":\"agent_done\",\"ts\":\"<ISO>\",\"event\":{\"id\":\"ab12cd34\",\"type\":\"agent_done\",\"file\":\"index.html\",\"sourceEventType\":\"accept\",\"carbonize\":true}}\n{\"seq\":5,\"id\":\"ab12cd34\",\"type\":\"complete\",\"ts\":\"<ISO>\",\"event\":{\"type\":\"complete\",\"id\":\"ab12cd34\"}}\n",
".impeccable/live/sessions/ab12cd34.snapshot.json": "{\n \"id\": \"ab12cd34\",\n \"phase\": \"completed\",\n \"pageUrl\": \"/\",\n \"sourceFile\": \"index.html\",\n \"previewFile\": null,\n \"previewMode\": null,\n \"expectedVariants\": 3,\n \"arrivedVariants\": 3,\n \"visibleVariant\": 2,\n \"paramValues\": {\n \"face\": \"serif\"\n },\n \"pendingEventSeq\": null,\n \"pendingEvent\": null,\n \"deliveryLease\": null,\n \"checkpointRevision\": 0,\n \"browserCheckpointRevision\": 0,\n \"publicationCheckpointRevision\": 0,\n \"activeOwner\": null,\n \"sourceMarkers\": {},\n \"fallbackMode\": null,\n \"generationPhase\": null,\n \"generationCompletedAt\": 1785578580000,\n \"generationTimings\": {},\n \"variantPlan\": null,\n \"generationCanceled\": true,\n \"generationCanceledAt\": 1785578520000,\n \"cancelReason\": \"accept\",\n \"annotationArtifacts\": [],\n \"mountedVariants\": [],\n \"mountFailures\": [],\n \"renderState\": \"pending\",\n \"diagnostics\": [\n {\n \"error\": \"carbonize_cleanup_required\",\n \"file\": \"index.html\",\n \"message\": \"Accepted variant still has carbonize markers that must be folded into source CSS.\"\n }\n ],\n \"updatedAt\": \"<ISO>\",\n \"__journalBytes\": 1053,\n \"__nextSeq\": 6\n}\n"
".impeccable/live/sessions/ab12cd34.snapshot.json": "{\n \"id\": \"ab12cd34\",\n \"phase\": \"completed\",\n \"pageUrl\": \"/\",\n \"sourceFile\": \"index.html\",\n \"previewFile\": null,\n \"previewMode\": null,\n \"expectedVariants\": 3,\n \"arrivedVariants\": 3,\n \"visibleVariant\": 2,\n \"paramValues\": {\n \"face\": \"serif\"\n },\n \"pendingEventSeq\": null,\n \"pendingEvent\": null,\n \"deliveryLease\": null,\n \"checkpointRevision\": 0,\n \"browserCheckpointRevision\": 0,\n \"publicationCheckpointRevision\": 0,\n \"activeOwner\": null,\n \"sourceMarkers\": {},\n \"fallbackMode\": null,\n \"generationPhase\": null,\n \"generationCompletedAt\": 1785578580000,\n \"generationTimings\": {},\n \"variantPlan\": null,\n \"generationCanceled\": true,\n \"generationCanceledAt\": 1785578520000,\n \"cancelReason\": \"accept\",\n \"annotationArtifacts\": [],\n \"mountedVariants\": [],\n \"mountFailures\": [],\n \"renderState\": \"pending\",\n \"diagnostics\": [],\n \"updatedAt\": \"<ISO>\",\n \"__journalBytes\": 1053,\n \"__nextSeq\": 6\n}\n"
}
}
+3 -3
View File
@@ -1,13 +1,13 @@
{
"steps": [
{
"stdout": "{\n \"ok\": true,\n \"id\": \"ab12cd34\",\n \"phase\": \"completed\",\n \"snapshot\": {\n \"id\": \"ab12cd34\",\n \"phase\": \"completed\",\n \"pageUrl\": \"/\",\n \"sourceFile\": \"index.html\",\n \"previewFile\": null,\n \"previewMode\": null,\n \"expectedVariants\": 3,\n \"arrivedVariants\": 3,\n \"visibleVariant\": 2,\n \"paramValues\": {\n \"face\": \"serif\"\n },\n \"pendingEventSeq\": null,\n \"pendingEvent\": null,\n \"deliveryLease\": null,\n \"checkpointRevision\": 0,\n \"browserCheckpointRevision\": 0,\n \"publicationCheckpointRevision\": 0,\n \"activeOwner\": null,\n \"sourceMarkers\": {},\n \"fallbackMode\": null,\n \"generationPhase\": null,\n \"generationCompletedAt\": 1785578580000,\n \"generationTimings\": {},\n \"variantPlan\": null,\n \"generationCanceled\": true,\n \"generationCanceledAt\": 1785578520000,\n \"cancelReason\": \"accept\",\n \"annotationArtifacts\": [],\n \"mountedVariants\": [],\n \"mountFailures\": [],\n \"renderState\": \"pending\",\n \"diagnostics\": [\n {\n \"error\": \"carbonize_cleanup_required\",\n \"file\": \"index.html\",\n \"message\": \"Accepted variant still has carbonize markers that must be folded into source CSS.\"\n }\n ],\n \"updatedAt\": \"<ISO>\"\n }\n}\n",
"stdout": "{\n \"ok\": true,\n \"id\": \"ab12cd34\",\n \"phase\": \"completed\",\n \"snapshot\": {\n \"id\": \"ab12cd34\",\n \"phase\": \"completed\",\n \"pageUrl\": \"/\",\n \"sourceFile\": \"index.html\",\n \"previewFile\": null,\n \"previewMode\": null,\n \"expectedVariants\": 3,\n \"arrivedVariants\": 3,\n \"visibleVariant\": 2,\n \"paramValues\": {\n \"face\": \"serif\"\n },\n \"pendingEventSeq\": null,\n \"pendingEvent\": null,\n \"deliveryLease\": null,\n \"checkpointRevision\": 0,\n \"browserCheckpointRevision\": 0,\n \"publicationCheckpointRevision\": 0,\n \"activeOwner\": null,\n \"sourceMarkers\": {},\n \"fallbackMode\": null,\n \"generationPhase\": null,\n \"generationCompletedAt\": 1785578580000,\n \"generationTimings\": {},\n \"variantPlan\": null,\n \"generationCanceled\": true,\n \"generationCanceledAt\": 1785578520000,\n \"cancelReason\": \"accept\",\n \"annotationArtifacts\": [],\n \"mountedVariants\": [],\n \"mountFailures\": [],\n \"renderState\": \"pending\",\n \"diagnostics\": [],\n \"updatedAt\": \"<ISO>\"\n }\n}\n",
"stderr": "",
"exit": 0,
"signal": null
},
{
"stdout": "{\n \"ok\": true,\n \"id\": \"ab12cd34\",\n \"phase\": \"completed\",\n \"snapshot\": {\n \"id\": \"ab12cd34\",\n \"phase\": \"completed\",\n \"pageUrl\": \"/\",\n \"sourceFile\": \"index.html\",\n \"previewFile\": null,\n \"previewMode\": null,\n \"expectedVariants\": 3,\n \"arrivedVariants\": 3,\n \"visibleVariant\": 2,\n \"paramValues\": {\n \"face\": \"serif\"\n },\n \"pendingEventSeq\": null,\n \"pendingEvent\": null,\n \"deliveryLease\": null,\n \"checkpointRevision\": 0,\n \"browserCheckpointRevision\": 0,\n \"publicationCheckpointRevision\": 0,\n \"activeOwner\": null,\n \"sourceMarkers\": {},\n \"fallbackMode\": null,\n \"generationPhase\": null,\n \"generationCompletedAt\": 1785578580000,\n \"generationTimings\": {},\n \"variantPlan\": null,\n \"generationCanceled\": true,\n \"generationCanceledAt\": 1785578520000,\n \"cancelReason\": \"accept\",\n \"annotationArtifacts\": [],\n \"mountedVariants\": [],\n \"mountFailures\": [],\n \"renderState\": \"pending\",\n \"diagnostics\": [\n {\n \"error\": \"carbonize_cleanup_required\",\n \"file\": \"index.html\",\n \"message\": \"Accepted variant still has carbonize markers that must be folded into source CSS.\"\n }\n ],\n \"updatedAt\": \"<ISO>\"\n }\n}\n",
"stdout": "{\n \"ok\": true,\n \"id\": \"ab12cd34\",\n \"phase\": \"completed\",\n \"snapshot\": {\n \"id\": \"ab12cd34\",\n \"phase\": \"completed\",\n \"pageUrl\": \"/\",\n \"sourceFile\": \"index.html\",\n \"previewFile\": null,\n \"previewMode\": null,\n \"expectedVariants\": 3,\n \"arrivedVariants\": 3,\n \"visibleVariant\": 2,\n \"paramValues\": {\n \"face\": \"serif\"\n },\n \"pendingEventSeq\": null,\n \"pendingEvent\": null,\n \"deliveryLease\": null,\n \"checkpointRevision\": 0,\n \"browserCheckpointRevision\": 0,\n \"publicationCheckpointRevision\": 0,\n \"activeOwner\": null,\n \"sourceMarkers\": {},\n \"fallbackMode\": null,\n \"generationPhase\": null,\n \"generationCompletedAt\": 1785578580000,\n \"generationTimings\": {},\n \"variantPlan\": null,\n \"generationCanceled\": true,\n \"generationCanceledAt\": 1785578520000,\n \"cancelReason\": \"accept\",\n \"annotationArtifacts\": [],\n \"mountedVariants\": [],\n \"mountFailures\": [],\n \"renderState\": \"pending\",\n \"diagnostics\": [],\n \"updatedAt\": \"<ISO>\"\n }\n}\n",
"stderr": "",
"exit": 0,
"signal": null
@@ -16,6 +16,6 @@
"files": {
".impeccable/live/config.json": "{\n \"files\": [\"index.html\", \"public/**/*.html\"],\n \"insertBefore\": \"</body>\",\n \"commentSyntax\": \"html\"\n}\n",
".impeccable/live/sessions/ab12cd34.jsonl": "{\"seq\":1,\"id\":\"ab12cd34\",\"type\":\"generate\",\"ts\":\"<ISO>\",\"event\":{\"id\":\"ab12cd34\",\"type\":\"generate\",\"action\":\"bolder\",\"count\":3,\"pageUrl\":\"/\",\"element\":{\"tagName\":\"h1\",\"id\":\"hero\",\"classes\":[\"hero-title\"],\"textContent\":\"Oracle Fixture\",\"outerHTML\":\"<h1 id=\\\"hero\\\" class=\\\"hero-title\\\">Oracle Fixture</h1>\"},\"clientSentAt\":1754042400000}}\n{\"seq\":2,\"id\":\"ab12cd34\",\"type\":\"agent_done\",\"ts\":\"<ISO>\",\"event\":{\"id\":\"ab12cd34\",\"type\":\"agent_done\",\"file\":\"index.html\",\"sourceEventType\":\"generate\",\"carbonize\":false,\"arrivedVariants\":3}}\n{\"seq\":3,\"id\":\"ab12cd34\",\"type\":\"accept\",\"ts\":\"<ISO>\",\"event\":{\"id\":\"ab12cd34\",\"type\":\"accept\",\"variantId\":\"2\",\"pageUrl\":\"/\",\"paramValues\":{\"face\":\"serif\"}}}\n{\"seq\":4,\"id\":\"ab12cd34\",\"type\":\"agent_done\",\"ts\":\"<ISO>\",\"event\":{\"id\":\"ab12cd34\",\"type\":\"agent_done\",\"file\":\"index.html\",\"sourceEventType\":\"accept\",\"carbonize\":true}}\n{\"seq\":5,\"id\":\"ab12cd34\",\"type\":\"complete\",\"ts\":\"<ISO>\",\"event\":{\"type\":\"complete\",\"id\":\"ab12cd34\"}}\n{\"seq\":6,\"id\":\"ab12cd34\",\"type\":\"complete\",\"ts\":\"<ISO>\",\"event\":{\"type\":\"complete\",\"id\":\"ab12cd34\"}}\n",
".impeccable/live/sessions/ab12cd34.snapshot.json": "{\n \"id\": \"ab12cd34\",\n \"phase\": \"completed\",\n \"pageUrl\": \"/\",\n \"sourceFile\": \"index.html\",\n \"previewFile\": null,\n \"previewMode\": null,\n \"expectedVariants\": 3,\n \"arrivedVariants\": 3,\n \"visibleVariant\": 2,\n \"paramValues\": {\n \"face\": \"serif\"\n },\n \"pendingEventSeq\": null,\n \"pendingEvent\": null,\n \"deliveryLease\": null,\n \"checkpointRevision\": 0,\n \"browserCheckpointRevision\": 0,\n \"publicationCheckpointRevision\": 0,\n \"activeOwner\": null,\n \"sourceMarkers\": {},\n \"fallbackMode\": null,\n \"generationPhase\": null,\n \"generationCompletedAt\": 1785578580000,\n \"generationTimings\": {},\n \"variantPlan\": null,\n \"generationCanceled\": true,\n \"generationCanceledAt\": 1785578520000,\n \"cancelReason\": \"accept\",\n \"annotationArtifacts\": [],\n \"mountedVariants\": [],\n \"mountFailures\": [],\n \"renderState\": \"pending\",\n \"diagnostics\": [\n {\n \"error\": \"carbonize_cleanup_required\",\n \"file\": \"index.html\",\n \"message\": \"Accepted variant still has carbonize markers that must be folded into source CSS.\"\n }\n ],\n \"updatedAt\": \"<ISO>\",\n \"__journalBytes\": 1173,\n \"__nextSeq\": 7\n}\n"
".impeccable/live/sessions/ab12cd34.snapshot.json": "{\n \"id\": \"ab12cd34\",\n \"phase\": \"completed\",\n \"pageUrl\": \"/\",\n \"sourceFile\": \"index.html\",\n \"previewFile\": null,\n \"previewMode\": null,\n \"expectedVariants\": 3,\n \"arrivedVariants\": 3,\n \"visibleVariant\": 2,\n \"paramValues\": {\n \"face\": \"serif\"\n },\n \"pendingEventSeq\": null,\n \"pendingEvent\": null,\n \"deliveryLease\": null,\n \"checkpointRevision\": 0,\n \"browserCheckpointRevision\": 0,\n \"publicationCheckpointRevision\": 0,\n \"activeOwner\": null,\n \"sourceMarkers\": {},\n \"fallbackMode\": null,\n \"generationPhase\": null,\n \"generationCompletedAt\": 1785578580000,\n \"generationTimings\": {},\n \"variantPlan\": null,\n \"generationCanceled\": true,\n \"generationCanceledAt\": 1785578520000,\n \"cancelReason\": \"accept\",\n \"annotationArtifacts\": [],\n \"mountedVariants\": [],\n \"mountFailures\": [],\n \"renderState\": \"pending\",\n \"diagnostics\": [],\n \"updatedAt\": \"<ISO>\",\n \"__journalBytes\": 1173,\n \"__nextSeq\": 7\n}\n"
}
}
@@ -26,7 +26,7 @@
"signal": null
},
{
"stdout": "",
"stdout": "{\"ok\":true,\"id\":\"ab12cd34\",\"status\":\"done\",\"file\":\"index.html\",\"_instructions\":\"Poll again now.\"}\n",
"stderr": "",
"exit": 0,
"signal": null
@@ -44,7 +44,7 @@
"signal": null
},
{
"stdout": "",
"stdout": "{\"ok\":true,\"id\":\"ab12cd34\",\"status\":\"steer_done\",\"_instructions\":\"Poll again now.\"}\n",
"stderr": "",
"exit": 0,
"signal": null