detector: text-occlusion + first-viewport-column-overflow (57 -> 59)

Two browser-engine quality rules, both warning severity.

text-occlusion / element-overlap fires on three shapes: an opaque
decorated box painted over a text element (elementFromPoint confirms
real coverage, box >= 30%), one text run buried under another when at
least one side is a positioned layer (text >= 45%, so line-box leading
bleed between stacked flow blocks does not count), and an inline element
whose opaque fill leaks past its line onto a neighbour (the class-name
collision bug). A large headline whose edge overhangs a bounded content
card is caught as an element collision even when the text stays on top.
Gradient scrims, decorative SVG emblems, fixed/sticky overlays, floats,
and raw image backdrops (contrast territory, deduped against the pixel
low-contrast rule) are exempt.

first-viewport-column-overflow fires when a multi-column opening section
runs one column past 140% of the viewport while a sibling fits inside
one screen, the stretched-hero signature. Single-column pages and
full-page heroes with no fitting sibling are exempt.

Validated: fires on the diagnosed repros, clean across a 60-sample
sweep. Fixtures + browser tests added.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-07-15 14:53:03 -07:00
co-authored by Claude Fable 5
parent d8eb4d73c7
commit ed7a6fbe4e
10 changed files with 927 additions and 6 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
# Impeccable
Design guidance for AI coding agents. 1 skill, 23 commands, live browser iteration, and 57 deterministic detector rules for AI-generated frontend design.
Design guidance for AI coding agents. 1 skill, 23 commands, live browser iteration, and 59 deterministic detector rules for AI-generated frontend design.
> **Quick start:** From your project root, run `npx impeccable install`, then run `/impeccable init` inside your AI coding tool. Full docs: [impeccable.style](https://impeccable.style).
@@ -13,7 +13,7 @@ Every model trained on the same SaaS templates. Skip the guidance and you get th
Impeccable adds:
- **One setup flow.** `/impeccable init` writes `PRODUCT.md` and offers `DESIGN.md`, so later commands know the audience, brand/product lane, voice, anti-references, colors, type, and components.
- **23 commands.** A shared design vocabulary with your AI: `polish`, `audit`, `critique`, `distill`, `animate`, `bolder`, `quieter`, and more.
- **57 deterministic detector rules** plus LLM-only critique checks. The CLI and browser extension run the deterministic rules with no LLM and no API key.
- **59 deterministic detector rules** plus LLM-only critique checks. The CLI and browser extension run the deterministic rules with no LLM and no API key.
## What's Included
+2 -2
View File
@@ -1,6 +1,6 @@
# Impeccable CLI
Detect UI anti-patterns and design quality issues from the command line. Scans HTML, CSS, JSX, TSX, Vue, and Svelte files for 57 deterministic rules, including AI-generated UI tells, accessibility violations, and general design quality problems.
Detect UI anti-patterns and design quality issues from the command line. Scans HTML, CSS, JSX, TSX, Vue, and Svelte files for 59 deterministic rules, including AI-generated UI tells, accessibility violations, and general design quality problems.
## Quick Start
@@ -56,7 +56,7 @@ npx impeccable detect --fast src/
**Quality**: tiny body text, cramped padding, long line lengths, small touch targets
57 deterministic detector rules in total. See the full catalog at [impeccable.style/slop](https://impeccable.style/slop).
59 deterministic detector rules in total. See the full catalog at [impeccable.style/slop](https://impeccable.style/slop).
## Exit Codes
+14
View File
@@ -1563,6 +1563,20 @@ if (IS_BROWSER) {
addBrowserFindings(groupMap, f.el || document.body, [{ type: f.type, detail: f.detail }]);
}
// Text occlusion / element overlap (browser-only: needs real layout +
// elementFromPoint to confirm what actually paints on top)
const occlusionFindings = checkTextOcclusionDOM().filter(f => _ruleOk(f.type));
for (const f of occlusionFindings) {
addBrowserFindings(groupMap, f.el || document.body, [{ type: f.type, detail: f.detail }]);
}
// First-viewport column overflow — the stretched-hero signature
// (browser-only: needs real layout for the content-extent math)
const colOverflowFindings = checkFirstViewportColumnOverflowDOM().filter(f => _ruleOk(f.type));
for (const f of colOverflowFindings) {
addBrowserFindings(groupMap, f.el || document.body, [{ type: f.type, detail: f.detail }]);
}
// Page-level quality checks (headings, etc.)
const qualityFindings = checkPageQualityDOM().filter(f => _ruleOk(f.type));
if (qualityFindings.length > 0) {
+379
View File
@@ -406,6 +406,24 @@ const ANTIPATTERNS = [
description:
'Cards inside a horizontal scroller or tab panel sit flush against the container edge at rest while keeping a gutter on the other side, so their edges and rounded corners get cut off. Usually the panel is sized wider than its clip box. Keep a consistent inset on both sides.',
},
{
id: 'text-occlusion',
category: 'quality',
scopes: ['layout'],
name: 'Text occluded by an overlapping element',
description:
'Text is painted under an opaque element or a second text run, so part of it cannot be read. A decorative box, a stacked layer, or an inline element with leaked padding lands on the words instead of beside them. Give overlapping layers room, or move the text out from under the layer above it.',
skillSection: 'Layout & Space',
},
{
id: 'first-viewport-column-overflow',
category: 'quality',
scopes: ['layout'],
name: 'One column stretches the first viewport',
description:
'A multi-column opening section lets one column run far past the fold while its sibling fits in a single viewport, so the short column floats in dead space and the fold falls deep inside one section. Balance the columns, cap the tall one, or let the long content flow below the opening row.',
skillSection: 'Layout & Space',
},
{
id: 'gray-on-color',
category: 'quality',
@@ -5439,6 +5457,353 @@ function checkEdgeFlushCardsDOM() {
return findings;
}
// ---------------------------------------------------------------------------
// Text occlusion / element overlap (browser-only)
// ---------------------------------------------------------------------------
// An opaque decorated box: a near-solid background fill or two-plus visible
// borders make it hide whatever sits behind it. Gradient / image fills are
// deliberately excluded — a scrim gradient over hero imagery is a contrast
// layer, not an occluder, and belongs to the pixel low-contrast rule.
function isOpaqueDecoratedBox(cs) {
if (!cs) return false;
const bg = parseAnyColor(cs.backgroundColor || '');
if (bg && (bg.a ?? 1) > 0.6) return true;
const borderSides = ['Top', 'Right', 'Bottom', 'Left'].filter((side) => {
if ((parseFloat(cs[`border${side}Width`]) || 0) <= 0) return false;
const bc = parseAnyColor(cs[`border${side}Color`] || '');
return bc && (bc.a ?? 1) > 0.3;
}).length;
return borderSides >= 2;
}
// Is this element lifted out of normal flow into a layer that can cover
// siblings? Two normal-flow blocks stacked vertically cannot truly hide each
// other's ink — an overlap between their rects is line-box bleed from tight
// leading (a display headline reaching up over the line before it), not
// occlusion. Only out-of-flow positioning (absolute / fixed / sticky) moves an
// element off its own row onto the pixels of another; an in-place transform or
// relative nudge on a display headline does not.
function isLayeredElement(el) {
for (let cur = el; cur && cur.nodeType === 1 && cur !== document.body; cur = cur.parentElement) {
const pos = String(getComputedStyle(cur).position || 'static');
if (pos === 'absolute' || pos === 'fixed' || pos === 'sticky') return true;
}
return false;
}
function elementDirectText(el) {
let t = '';
for (const node of el.childNodes || []) {
if (node.nodeType === 3) t += node.textContent;
}
return t.trim();
}
// Rendered gate that, unlike isRenderedForBrowserRule, does NOT exempt
// aria-hidden subtrees: a decorative aria-hidden box still paints on screen
// and can still visually cover real text.
function isPaintedForOcclusion(el) {
for (let cur = el; cur && cur.nodeType === 1; cur = cur.parentElement) {
const style = getComputedStyle(cur);
const visibility = String(style.visibility || '').toLowerCase();
if (style.display === 'none' || visibility === 'hidden' || visibility === 'collapse') return false;
if ((parseFloat(style.opacity) || 0) <= 0.05) return false;
if (String(style.contentVisibility || '').toLowerCase() === 'hidden') return false;
}
return true;
}
// Detects text that is actually painted UNDER an opaque box or another text
// run (the reader can't read it), plus two structural overlap tells the
// elementFromPoint probe can't reach: a large headline whose edge tucks behind
// an opaque card, and an inline element whose leaked padding-box (a common
// class-name-collision bug) covers a sibling.
//
// The occlusion probe is viewport-bound: elementFromPoint only answers for the
// scan's current viewport (scroll 0), so the ground-truth paths cover the
// first-viewport composition where collisions matter most. The inline-leak
// path is pure geometry and runs anywhere on the page.
const OCCLUSION_TEXT_SKIP_TAGS = new Set(['script', 'style', 'noscript', 'template', 'title']);
function checkTextOcclusionDOM() {
const findings = [];
const seenVictims = new Set();
const vw = window.innerWidth || 1280;
const vh = window.innerHeight || 800;
const isFloated = (cs) => {
const f = String(cs.cssFloat || cs.float || 'none').toLowerCase();
return f === 'left' || f === 'right';
};
const isMarqueeish = (el, cs) => {
if (el.tagName === 'MARQUEE') return true;
const ident = `${el.getAttribute?.('class') || ''} ${el.getAttribute?.('id') || ''}`;
if (/\b(marquee|ticker|scroller|carousel|conveyor)\b/i.test(ident)) return true;
const anim = String(cs.animationName || '').toLowerCase();
return /marquee|ticker|scroll/.test(anim);
};
// A fixed or sticky overlay (status bar, toolbar, sticky header) floats above
// scrolling content by design — whatever sits under it at rest scrolls clear,
// so it is not occluding the page.
const isPinnedOverlay = (el) => {
for (let cur = el; cur && cur.nodeType === 1 && cur !== document.body; cur = cur.parentElement) {
const pos = String(getComputedStyle(cur).position || 'static');
if (pos === 'fixed' || pos === 'sticky') return true;
}
return false;
};
// Collect renderable text owners in / near the first viewport for the
// elementFromPoint probe. SVG <text> counts too.
const textEls = [];
for (const el of document.querySelectorAll('body *')) {
const tag = el.tagName.toLowerCase();
if (OCCLUSION_TEXT_SKIP_TAGS.has(tag)) continue;
const inSvg = !!el.closest('svg');
if (inSvg && tag !== 'text') continue;
const text = inSvg ? (el.textContent || '').trim() : elementDirectText(el);
if (text.length < 2) continue;
if (!isPaintedForOcclusion(el)) continue;
let rect; try { rect = el.getBoundingClientRect(); } catch { continue; }
if (rect.width < 6 || rect.height < 6) continue;
// Viewport-bound probe: keep text whose box overlaps the live viewport.
if (rect.bottom <= 0 || rect.top >= vh) continue;
textEls.push({ el, rect, text, inSvg });
}
for (const victim of textEls) {
const { el, rect, text } = victim;
if (seenVictims.has(el)) continue;
const style = getComputedStyle(el);
if (isScreenReaderOnlyTextStyle(style, { width: rect.width, height: rect.height, clientWidth: el.clientWidth, clientHeight: el.clientHeight })) continue;
const cols = Math.max(6, Math.min(30, Math.round(rect.width / 12)));
const rows = Math.max(1, Math.min(4, Math.round(rect.height / 14)));
let total = 0;
let occluded = 0;
let occluderEl = null;
let occluderKind = '';
for (let i = 0; i < cols; i++) {
const x = rect.left + rect.width * ((i + 0.5) / cols);
if (x < 1 || x > vw - 1) continue;
for (let j = 0; j < rows; j++) {
const y = rect.top + rect.height * ((j + 0.5) / rows);
if (y < 1 || y > vh - 1) continue;
total++;
const top = document.elementFromPoint(x, y);
if (!top) continue;
// Text visible here: the probe returns the text itself, a descendant,
// or one of its ancestors (the text's own container / background).
if (top === el || el.contains(top) || top.contains(el)) continue;
const topCs = getComputedStyle(top);
if (isFloated(topCs) || isMarqueeish(top, topCs) || isPinnedOverlay(top)) continue;
const topTag = top.tagName.toLowerCase();
// Text sitting under a raw image/video is contrast territory (deduped
// against the pixel low-contrast rule); leave those alone here.
if (['img', 'video', 'canvas', 'picture'].includes(topTag)) continue;
const topHasText = elementDirectText(top).length > 0 || !!top.closest('svg');
if (isOpaqueDecoratedBox(topCs)) {
occluded++;
if (!occluderEl) { occluderEl = top; occluderKind = 'box'; }
} else if (topHasText) {
occluded++;
if (!occluderEl) { occluderEl = top; occluderKind = 'text'; }
}
}
}
if (total === 0 || !occluderEl) continue;
const occFrac = occluded / total;
// A solid box's paint fills its rect, so box coverage is real at a lower
// bar. Text coverage rides on elementFromPoint returning the occluder's box
// (line box / container), which can exceed its actual glyph ink, so the
// text bar is higher — partial overlaps below it are crowding, not burial.
if (occFrac < (occluderKind === 'text' ? 0.45 : 0.3)) continue;
// (i) Substantial occlusion: a real slab of the text is behind something.
if (occluderKind === 'text') {
// Two SVG texts inside the same emblem (concentric arcs, monogram) are one
// decorative unit, not a collision.
const victimSvg = el.closest('svg');
const occSvg = occluderEl.closest('svg');
if (victimSvg && occSvg && victimSvg === occSvg) continue;
// Both sides in plain flow: the overlap is line-box bleed from tight
// leading (a big headline reaching up over its own eyebrow), not one text
// run painted over another.
if (!isLayeredElement(el) && !isLayeredElement(occluderEl)) continue;
}
seenVictims.add(el);
findings.push({
el,
type: 'text-occlusion',
detail: `${classSelector(el)} "${text.slice(0, 24)}" is ${Math.round(occFrac * 100)}% covered by ${occluderKind === 'text' ? 'overlapping text' : 'an opaque element'} (${classSelector(occluderEl)})`,
});
}
// (ii) Headline overhanging an opaque card: a display-scale line whose bulk
// sits outside a bounded content card but whose edge clips into it. The text
// may still paint on top and stay readable, but the two layers were dropped
// on the same pixels — a placement collision, not a composition.
const cards = [];
for (const el of document.querySelectorAll('body *')) {
if (el.closest('svg')) continue;
if (!isPaintedForOcclusion(el)) continue;
const cs = getComputedStyle(el);
const bg = parseAnyColor(cs.backgroundColor || '');
const bgImg = cs.backgroundImage || '';
if (!bg || (bg.a ?? 1) <= 0.7) continue;
if (bgImg && bgImg !== 'none' && /(gradient|url)\(/i.test(bgImg)) continue;
const hasBorder = ['Top', 'Right', 'Bottom', 'Left'].some((s) => (parseFloat(cs[`border${s}Width`]) || 0) > 0);
const hasShadow = cs.boxShadow && cs.boxShadow !== 'none';
if (!hasBorder && !hasShadow) continue;
if (isPinnedOverlay(el)) continue;
let cr; try { cr = el.getBoundingClientRect(); } catch { continue; }
if (cr.width < 100 || cr.width > 0.8 * vw || cr.height < 60) continue;
cards.push({ el, rect: cr });
}
for (const victim of textEls) {
const { el, rect, text } = victim;
if (seenVictims.has(el)) continue;
const style = getComputedStyle(el);
if ((parseFloat(style.fontSize) || 16) < 40) continue;
let lineHeight = parseFloat(style.lineHeight);
if (!Number.isFinite(lineHeight)) lineHeight = (parseFloat(style.fontSize) || 16) * 1.2;
const centerX = rect.left + rect.width / 2;
for (const card of cards) {
if (card.el === el || el.contains(card.el) || card.el.contains(el)) continue;
const ix = Math.max(0, Math.min(rect.right, card.rect.right) - Math.max(rect.left, card.rect.left));
const iy = Math.max(0, Math.min(rect.bottom, card.rect.bottom) - Math.max(rect.top, card.rect.top));
if (ix < 8 || iy < 0.5 * lineHeight) continue;
// The headline's bulk must sit outside the card — only its edge clips in.
if (centerX >= card.rect.left && centerX <= card.rect.right) continue;
if (ix > 0.5 * rect.width) continue;
seenVictims.add(el);
findings.push({
el,
type: 'text-occlusion',
detail: `${classSelector(el)} "${text.slice(0, 24)}" overhangs ${classSelector(card.el)} by ${Math.round(ix)}px — the headline and the card collide`,
});
break;
}
}
// (iii) Inline padding leak: an inline element with an opaque background and
// large vertical padding paints a filled block whose padding-box overflows
// its line (inline padding reserves no vertical space), so the fill lands on
// the content above and below instead of enclosing its own text. The
// canonical bug is a class-name collision that hands a decorative marker a
// payoff card's padding. The tell is a rendered height several times the line
// height, which distinguishes the leak from a padded inline highlight.
for (const el of document.querySelectorAll('body *')) {
if (el.closest('svg')) continue;
if (!isPaintedForOcclusion(el)) continue;
const cs = getComputedStyle(el);
if (cs.display !== 'inline') continue;
const bg = parseAnyColor(cs.backgroundColor || '');
if (!bg || (bg.a ?? 1) <= 0.6) continue;
const padTop = parseFloat(cs.paddingTop) || 0;
const padBottom = parseFloat(cs.paddingBottom) || 0;
if (padTop + padBottom < 24) continue;
let rect; try { rect = el.getBoundingClientRect(); } catch { continue; }
if (rect.width < 12 || rect.height < 24) continue;
const fontSize = parseFloat(cs.fontSize) || 16;
let lineHeight = parseFloat(cs.lineHeight);
if (!Number.isFinite(lineHeight)) lineHeight = fontSize * 1.4;
// The padding box has to overflow the line by a clear margin — a padded
// inline highlight sits at roughly one line height, the leak at several.
if (rect.height < 2.2 * lineHeight) continue;
if (seenVictims.has(el)) continue;
// Name a neighbour the fill lands on, if one is nearby (paint state aside,
// reveal-on-scroll siblings still occupy the space it covers).
let overlaps = null;
for (const other of el.parentElement ? el.parentElement.children : []) {
if (other === el || el.contains(other) || other.contains(el)) continue;
if (getComputedStyle(other).display === 'none') continue;
const oRect = other.getBoundingClientRect();
const ix = Math.max(0, Math.min(rect.right, oRect.right) - Math.max(rect.left, oRect.left));
const iy = Math.max(0, Math.min(rect.bottom, oRect.bottom) - Math.max(rect.top, oRect.top));
if (ix > 4 && iy > 4 && (other.textContent || '').trim().length > 0) { overlaps = other; break; }
}
seenVictims.add(el);
findings.push({
el,
type: 'text-occlusion',
detail: `${classSelector(el)} is an inline element whose opaque fill leaks ${Math.round(rect.height)}px past its line${overlaps ? ` onto ${classSelector(overlaps)}` : ''}`,
});
}
return findings;
}
// ---------------------------------------------------------------------------
// First-viewport column overflow — the stretched-hero signature (browser-only)
// ---------------------------------------------------------------------------
// A multi-column composition that opens the page (grid/flex with two or more
// side-by-side columns, each a real share of the width) where one column's
// content runs far past the fold while its sibling fits inside a single
// viewport. The row stretches to the tall column, so the short one floats in a
// screen-and-a-half of dead space and the fold falls deep inside a single
// section. Single-column pages and full-page heroes (no sibling column) are
// exempt because there is no fitting sibling to contrast against.
function checkFirstViewportColumnOverflowDOM() {
const findings = [];
const vw = window.innerWidth || 1280;
const vh = window.innerHeight || 800;
const isMultiCol = (s) => /(^|inline-)(grid|flex)$/.test(String(s.display || ''));
for (const el of document.querySelectorAll('body *')) {
const style = getComputedStyle(el);
if (!isMultiCol(style)) continue;
let rect; try { rect = el.getBoundingClientRect(); } catch { continue; }
if (rect.width < 0.5 * vw) continue;
const pageTop = rect.top + (window.scrollY || 0);
const pageBottom = pageTop + rect.height;
// The fold must fall inside this container: it opens within the first
// viewport and runs past it.
if (pageTop >= vh * 0.9 || pageBottom <= vh) continue;
// Direct children that read as side-by-side columns: a real width share,
// not full-bleed (stacked single column), sharing the container's top row.
const cols = [];
for (const child of el.children) {
const cs = getComputedStyle(child);
if (cs.display === 'none') continue;
if (String(cs.position || '') === 'absolute' || String(cs.position || '') === 'fixed') continue;
let cr; try { cr = child.getBoundingClientRect(); } catch { continue; }
const wShare = cr.width / rect.width;
if (wShare < 0.25 || wShare > 0.9) continue;
if (cr.height < 40) continue;
// Content extent: how far the child's own content actually reaches,
// independent of a stretched row height.
let contentBottom = cr.top;
for (const d of child.querySelectorAll('*')) {
const ds = getComputedStyle(d);
if (ds.position === 'absolute' || ds.position === 'fixed') continue;
if (ds.display === 'none' || ds.visibility === 'hidden') continue;
let dr; try { dr = d.getBoundingClientRect(); } catch { continue; }
if (dr.width > 0 && dr.height > 0) contentBottom = Math.max(contentBottom, dr.bottom);
}
cols.push({ child, top: cr.top, contentH: contentBottom - cr.top });
}
if (cols.length < 2) continue;
// Side-by-side: the two candidate columns must share the top row.
cols.sort((a, b) => b.contentH - a.contentH);
const tall = cols[0];
const shortest = cols[cols.length - 1];
if (Math.abs(tall.top - shortest.top) > 0.25 * vh) continue;
if (tall.contentH <= vh * 1.4) continue;
if (shortest.contentH > vh) continue;
findings.push({
el,
type: 'first-viewport-column-overflow',
detail: `${classSelector(el)} opens the page with one column running ${Math.round(tall.contentH / vh * 100)}% of the viewport tall while a sibling fits in ${Math.round(shortest.contentH / vh * 100)}% — the fold falls deep inside the section`,
});
}
return findings;
}
// --- cli/engine/browser/injected/index.mjs ---
const IS_BROWSER = typeof window !== 'undefined';
@@ -7005,6 +7370,20 @@ if (IS_BROWSER) {
addBrowserFindings(groupMap, f.el || document.body, [{ type: f.type, detail: f.detail }]);
}
// Text occlusion / element overlap (browser-only: needs real layout +
// elementFromPoint to confirm what actually paints on top)
const occlusionFindings = checkTextOcclusionDOM().filter(f => _ruleOk(f.type));
for (const f of occlusionFindings) {
addBrowserFindings(groupMap, f.el || document.body, [{ type: f.type, detail: f.detail }]);
}
// First-viewport column overflow — the stretched-hero signature
// (browser-only: needs real layout for the content-extent math)
const colOverflowFindings = checkFirstViewportColumnOverflowDOM().filter(f => _ruleOk(f.type));
for (const f of colOverflowFindings) {
addBrowserFindings(groupMap, f.el || document.body, [{ type: f.type, detail: f.detail }]);
}
// Page-level quality checks (headings, etc.)
const qualityFindings = checkPageQualityDOM().filter(f => _ruleOk(f.type));
if (qualityFindings.length > 0) {
+18
View File
@@ -304,6 +304,24 @@ const ANTIPATTERNS = [
description:
'Cards inside a horizontal scroller or tab panel sit flush against the container edge at rest while keeping a gutter on the other side, so their edges and rounded corners get cut off. Usually the panel is sized wider than its clip box. Keep a consistent inset on both sides.',
},
{
id: 'text-occlusion',
category: 'quality',
scopes: ['layout'],
name: 'Text occluded by an overlapping element',
description:
'Text is painted under an opaque element or a second text run, so part of it cannot be read. A decorative box, a stacked layer, or an inline element with leaked padding lands on the words instead of beside them. Give overlapping layers room, or move the text out from under the layer above it.',
skillSection: 'Layout & Space',
},
{
id: 'first-viewport-column-overflow',
category: 'quality',
scopes: ['layout'],
name: 'One column stretches the first viewport',
description:
'A multi-column opening section lets one column run far past the fold while its sibling fits in a single viewport, so the short column floats in dead space and the fold falls deep inside one section. Balance the columns, cap the tall one, or let the long content flow below the opening row.',
skillSection: 'Layout & Space',
},
{
id: 'gray-on-color',
category: 'quality',
+351
View File
@@ -4668,6 +4668,353 @@ function checkEdgeFlushCardsDOM() {
return findings;
}
// ---------------------------------------------------------------------------
// Text occlusion / element overlap (browser-only)
// ---------------------------------------------------------------------------
// An opaque decorated box: a near-solid background fill or two-plus visible
// borders make it hide whatever sits behind it. Gradient / image fills are
// deliberately excluded — a scrim gradient over hero imagery is a contrast
// layer, not an occluder, and belongs to the pixel low-contrast rule.
function isOpaqueDecoratedBox(cs) {
if (!cs) return false;
const bg = parseAnyColor(cs.backgroundColor || '');
if (bg && (bg.a ?? 1) > 0.6) return true;
const borderSides = ['Top', 'Right', 'Bottom', 'Left'].filter((side) => {
if ((parseFloat(cs[`border${side}Width`]) || 0) <= 0) return false;
const bc = parseAnyColor(cs[`border${side}Color`] || '');
return bc && (bc.a ?? 1) > 0.3;
}).length;
return borderSides >= 2;
}
// Is this element lifted out of normal flow into a layer that can cover
// siblings? Two normal-flow blocks stacked vertically cannot truly hide each
// other's ink — an overlap between their rects is line-box bleed from tight
// leading (a display headline reaching up over the line before it), not
// occlusion. Only out-of-flow positioning (absolute / fixed / sticky) moves an
// element off its own row onto the pixels of another; an in-place transform or
// relative nudge on a display headline does not.
function isLayeredElement(el) {
for (let cur = el; cur && cur.nodeType === 1 && cur !== document.body; cur = cur.parentElement) {
const pos = String(getComputedStyle(cur).position || 'static');
if (pos === 'absolute' || pos === 'fixed' || pos === 'sticky') return true;
}
return false;
}
function elementDirectText(el) {
let t = '';
for (const node of el.childNodes || []) {
if (node.nodeType === 3) t += node.textContent;
}
return t.trim();
}
// Rendered gate that, unlike isRenderedForBrowserRule, does NOT exempt
// aria-hidden subtrees: a decorative aria-hidden box still paints on screen
// and can still visually cover real text.
function isPaintedForOcclusion(el) {
for (let cur = el; cur && cur.nodeType === 1; cur = cur.parentElement) {
const style = getComputedStyle(cur);
const visibility = String(style.visibility || '').toLowerCase();
if (style.display === 'none' || visibility === 'hidden' || visibility === 'collapse') return false;
if ((parseFloat(style.opacity) || 0) <= 0.05) return false;
if (String(style.contentVisibility || '').toLowerCase() === 'hidden') return false;
}
return true;
}
// Detects text that is actually painted UNDER an opaque box or another text
// run (the reader can't read it), plus two structural overlap tells the
// elementFromPoint probe can't reach: a large headline whose edge tucks behind
// an opaque card, and an inline element whose leaked padding-box (a common
// class-name-collision bug) covers a sibling.
//
// The occlusion probe is viewport-bound: elementFromPoint only answers for the
// scan's current viewport (scroll 0), so the ground-truth paths cover the
// first-viewport composition where collisions matter most. The inline-leak
// path is pure geometry and runs anywhere on the page.
const OCCLUSION_TEXT_SKIP_TAGS = new Set(['script', 'style', 'noscript', 'template', 'title']);
function checkTextOcclusionDOM() {
const findings = [];
const seenVictims = new Set();
const vw = window.innerWidth || 1280;
const vh = window.innerHeight || 800;
const isFloated = (cs) => {
const f = String(cs.cssFloat || cs.float || 'none').toLowerCase();
return f === 'left' || f === 'right';
};
const isMarqueeish = (el, cs) => {
if (el.tagName === 'MARQUEE') return true;
const ident = `${el.getAttribute?.('class') || ''} ${el.getAttribute?.('id') || ''}`;
if (/\b(marquee|ticker|scroller|carousel|conveyor)\b/i.test(ident)) return true;
const anim = String(cs.animationName || '').toLowerCase();
return /marquee|ticker|scroll/.test(anim);
};
// A fixed or sticky overlay (status bar, toolbar, sticky header) floats above
// scrolling content by design — whatever sits under it at rest scrolls clear,
// so it is not occluding the page.
const isPinnedOverlay = (el) => {
for (let cur = el; cur && cur.nodeType === 1 && cur !== document.body; cur = cur.parentElement) {
const pos = String(getComputedStyle(cur).position || 'static');
if (pos === 'fixed' || pos === 'sticky') return true;
}
return false;
};
// Collect renderable text owners in / near the first viewport for the
// elementFromPoint probe. SVG <text> counts too.
const textEls = [];
for (const el of document.querySelectorAll('body *')) {
const tag = el.tagName.toLowerCase();
if (OCCLUSION_TEXT_SKIP_TAGS.has(tag)) continue;
const inSvg = !!el.closest('svg');
if (inSvg && tag !== 'text') continue;
const text = inSvg ? (el.textContent || '').trim() : elementDirectText(el);
if (text.length < 2) continue;
if (!isPaintedForOcclusion(el)) continue;
let rect; try { rect = el.getBoundingClientRect(); } catch { continue; }
if (rect.width < 6 || rect.height < 6) continue;
// Viewport-bound probe: keep text whose box overlaps the live viewport.
if (rect.bottom <= 0 || rect.top >= vh) continue;
textEls.push({ el, rect, text, inSvg });
}
for (const victim of textEls) {
const { el, rect, text } = victim;
if (seenVictims.has(el)) continue;
const style = getComputedStyle(el);
if (isScreenReaderOnlyTextStyle(style, { width: rect.width, height: rect.height, clientWidth: el.clientWidth, clientHeight: el.clientHeight })) continue;
const cols = Math.max(6, Math.min(30, Math.round(rect.width / 12)));
const rows = Math.max(1, Math.min(4, Math.round(rect.height / 14)));
let total = 0;
let occluded = 0;
let occluderEl = null;
let occluderKind = '';
for (let i = 0; i < cols; i++) {
const x = rect.left + rect.width * ((i + 0.5) / cols);
if (x < 1 || x > vw - 1) continue;
for (let j = 0; j < rows; j++) {
const y = rect.top + rect.height * ((j + 0.5) / rows);
if (y < 1 || y > vh - 1) continue;
total++;
const top = document.elementFromPoint(x, y);
if (!top) continue;
// Text visible here: the probe returns the text itself, a descendant,
// or one of its ancestors (the text's own container / background).
if (top === el || el.contains(top) || top.contains(el)) continue;
const topCs = getComputedStyle(top);
if (isFloated(topCs) || isMarqueeish(top, topCs) || isPinnedOverlay(top)) continue;
const topTag = top.tagName.toLowerCase();
// Text sitting under a raw image/video is contrast territory (deduped
// against the pixel low-contrast rule); leave those alone here.
if (['img', 'video', 'canvas', 'picture'].includes(topTag)) continue;
const topHasText = elementDirectText(top).length > 0 || !!top.closest('svg');
if (isOpaqueDecoratedBox(topCs)) {
occluded++;
if (!occluderEl) { occluderEl = top; occluderKind = 'box'; }
} else if (topHasText) {
occluded++;
if (!occluderEl) { occluderEl = top; occluderKind = 'text'; }
}
}
}
if (total === 0 || !occluderEl) continue;
const occFrac = occluded / total;
// A solid box's paint fills its rect, so box coverage is real at a lower
// bar. Text coverage rides on elementFromPoint returning the occluder's box
// (line box / container), which can exceed its actual glyph ink, so the
// text bar is higher — partial overlaps below it are crowding, not burial.
if (occFrac < (occluderKind === 'text' ? 0.45 : 0.3)) continue;
// (i) Substantial occlusion: a real slab of the text is behind something.
if (occluderKind === 'text') {
// Two SVG texts inside the same emblem (concentric arcs, monogram) are one
// decorative unit, not a collision.
const victimSvg = el.closest('svg');
const occSvg = occluderEl.closest('svg');
if (victimSvg && occSvg && victimSvg === occSvg) continue;
// Both sides in plain flow: the overlap is line-box bleed from tight
// leading (a big headline reaching up over its own eyebrow), not one text
// run painted over another.
if (!isLayeredElement(el) && !isLayeredElement(occluderEl)) continue;
}
seenVictims.add(el);
findings.push({
el,
type: 'text-occlusion',
detail: `${classSelector(el)} "${text.slice(0, 24)}" is ${Math.round(occFrac * 100)}% covered by ${occluderKind === 'text' ? 'overlapping text' : 'an opaque element'} (${classSelector(occluderEl)})`,
});
}
// (ii) Headline overhanging an opaque card: a display-scale line whose bulk
// sits outside a bounded content card but whose edge clips into it. The text
// may still paint on top and stay readable, but the two layers were dropped
// on the same pixels — a placement collision, not a composition.
const cards = [];
for (const el of document.querySelectorAll('body *')) {
if (el.closest('svg')) continue;
if (!isPaintedForOcclusion(el)) continue;
const cs = getComputedStyle(el);
const bg = parseAnyColor(cs.backgroundColor || '');
const bgImg = cs.backgroundImage || '';
if (!bg || (bg.a ?? 1) <= 0.7) continue;
if (bgImg && bgImg !== 'none' && /(gradient|url)\(/i.test(bgImg)) continue;
const hasBorder = ['Top', 'Right', 'Bottom', 'Left'].some((s) => (parseFloat(cs[`border${s}Width`]) || 0) > 0);
const hasShadow = cs.boxShadow && cs.boxShadow !== 'none';
if (!hasBorder && !hasShadow) continue;
if (isPinnedOverlay(el)) continue;
let cr; try { cr = el.getBoundingClientRect(); } catch { continue; }
if (cr.width < 100 || cr.width > 0.8 * vw || cr.height < 60) continue;
cards.push({ el, rect: cr });
}
for (const victim of textEls) {
const { el, rect, text } = victim;
if (seenVictims.has(el)) continue;
const style = getComputedStyle(el);
if ((parseFloat(style.fontSize) || 16) < 40) continue;
let lineHeight = parseFloat(style.lineHeight);
if (!Number.isFinite(lineHeight)) lineHeight = (parseFloat(style.fontSize) || 16) * 1.2;
const centerX = rect.left + rect.width / 2;
for (const card of cards) {
if (card.el === el || el.contains(card.el) || card.el.contains(el)) continue;
const ix = Math.max(0, Math.min(rect.right, card.rect.right) - Math.max(rect.left, card.rect.left));
const iy = Math.max(0, Math.min(rect.bottom, card.rect.bottom) - Math.max(rect.top, card.rect.top));
if (ix < 8 || iy < 0.5 * lineHeight) continue;
// The headline's bulk must sit outside the card — only its edge clips in.
if (centerX >= card.rect.left && centerX <= card.rect.right) continue;
if (ix > 0.5 * rect.width) continue;
seenVictims.add(el);
findings.push({
el,
type: 'text-occlusion',
detail: `${classSelector(el)} "${text.slice(0, 24)}" overhangs ${classSelector(card.el)} by ${Math.round(ix)}px — the headline and the card collide`,
});
break;
}
}
// (iii) Inline padding leak: an inline element with an opaque background and
// large vertical padding paints a filled block whose padding-box overflows
// its line (inline padding reserves no vertical space), so the fill lands on
// the content above and below instead of enclosing its own text. The
// canonical bug is a class-name collision that hands a decorative marker a
// payoff card's padding. The tell is a rendered height several times the line
// height, which distinguishes the leak from a padded inline highlight.
for (const el of document.querySelectorAll('body *')) {
if (el.closest('svg')) continue;
if (!isPaintedForOcclusion(el)) continue;
const cs = getComputedStyle(el);
if (cs.display !== 'inline') continue;
const bg = parseAnyColor(cs.backgroundColor || '');
if (!bg || (bg.a ?? 1) <= 0.6) continue;
const padTop = parseFloat(cs.paddingTop) || 0;
const padBottom = parseFloat(cs.paddingBottom) || 0;
if (padTop + padBottom < 24) continue;
let rect; try { rect = el.getBoundingClientRect(); } catch { continue; }
if (rect.width < 12 || rect.height < 24) continue;
const fontSize = parseFloat(cs.fontSize) || 16;
let lineHeight = parseFloat(cs.lineHeight);
if (!Number.isFinite(lineHeight)) lineHeight = fontSize * 1.4;
// The padding box has to overflow the line by a clear margin — a padded
// inline highlight sits at roughly one line height, the leak at several.
if (rect.height < 2.2 * lineHeight) continue;
if (seenVictims.has(el)) continue;
// Name a neighbour the fill lands on, if one is nearby (paint state aside,
// reveal-on-scroll siblings still occupy the space it covers).
let overlaps = null;
for (const other of el.parentElement ? el.parentElement.children : []) {
if (other === el || el.contains(other) || other.contains(el)) continue;
if (getComputedStyle(other).display === 'none') continue;
const oRect = other.getBoundingClientRect();
const ix = Math.max(0, Math.min(rect.right, oRect.right) - Math.max(rect.left, oRect.left));
const iy = Math.max(0, Math.min(rect.bottom, oRect.bottom) - Math.max(rect.top, oRect.top));
if (ix > 4 && iy > 4 && (other.textContent || '').trim().length > 0) { overlaps = other; break; }
}
seenVictims.add(el);
findings.push({
el,
type: 'text-occlusion',
detail: `${classSelector(el)} is an inline element whose opaque fill leaks ${Math.round(rect.height)}px past its line${overlaps ? ` onto ${classSelector(overlaps)}` : ''}`,
});
}
return findings;
}
// ---------------------------------------------------------------------------
// First-viewport column overflow — the stretched-hero signature (browser-only)
// ---------------------------------------------------------------------------
// A multi-column composition that opens the page (grid/flex with two or more
// side-by-side columns, each a real share of the width) where one column's
// content runs far past the fold while its sibling fits inside a single
// viewport. The row stretches to the tall column, so the short one floats in a
// screen-and-a-half of dead space and the fold falls deep inside a single
// section. Single-column pages and full-page heroes (no sibling column) are
// exempt because there is no fitting sibling to contrast against.
function checkFirstViewportColumnOverflowDOM() {
const findings = [];
const vw = window.innerWidth || 1280;
const vh = window.innerHeight || 800;
const isMultiCol = (s) => /(^|inline-)(grid|flex)$/.test(String(s.display || ''));
for (const el of document.querySelectorAll('body *')) {
const style = getComputedStyle(el);
if (!isMultiCol(style)) continue;
let rect; try { rect = el.getBoundingClientRect(); } catch { continue; }
if (rect.width < 0.5 * vw) continue;
const pageTop = rect.top + (window.scrollY || 0);
const pageBottom = pageTop + rect.height;
// The fold must fall inside this container: it opens within the first
// viewport and runs past it.
if (pageTop >= vh * 0.9 || pageBottom <= vh) continue;
// Direct children that read as side-by-side columns: a real width share,
// not full-bleed (stacked single column), sharing the container's top row.
const cols = [];
for (const child of el.children) {
const cs = getComputedStyle(child);
if (cs.display === 'none') continue;
if (String(cs.position || '') === 'absolute' || String(cs.position || '') === 'fixed') continue;
let cr; try { cr = child.getBoundingClientRect(); } catch { continue; }
const wShare = cr.width / rect.width;
if (wShare < 0.25 || wShare > 0.9) continue;
if (cr.height < 40) continue;
// Content extent: how far the child's own content actually reaches,
// independent of a stretched row height.
let contentBottom = cr.top;
for (const d of child.querySelectorAll('*')) {
const ds = getComputedStyle(d);
if (ds.position === 'absolute' || ds.position === 'fixed') continue;
if (ds.display === 'none' || ds.visibility === 'hidden') continue;
let dr; try { dr = d.getBoundingClientRect(); } catch { continue; }
if (dr.width > 0 && dr.height > 0) contentBottom = Math.max(contentBottom, dr.bottom);
}
cols.push({ child, top: cr.top, contentH: contentBottom - cr.top });
}
if (cols.length < 2) continue;
// Side-by-side: the two candidate columns must share the top row.
cols.sort((a, b) => b.contentH - a.contentH);
const tall = cols[0];
const shortest = cols[cols.length - 1];
if (Math.abs(tall.top - shortest.top) > 0.25 * vh) continue;
if (tall.contentH <= vh * 1.4) continue;
if (shortest.contentH > vh) continue;
findings.push({
el,
type: 'first-viewport-column-overflow',
detail: `${classSelector(el)} opens the page with one column running ${Math.round(tall.contentH / vh * 100)}% of the viewport tall while a sibling fits in ${Math.round(shortest.contentH / vh * 100)}% — the fold falls deep inside the section`,
});
}
return findings;
}
export {
checkBorders,
isEmojiOnlyText,
@@ -4769,4 +5116,8 @@ export {
measureHiddenTextDOM,
checkContentHiddenAtRest,
checkEdgeFlushCardsDOM,
isOpaqueDecoratedBox,
isLayeredElement,
checkTextOcclusionDOM,
checkFirstViewportColumnOverflowDOM,
};
+2 -2
View File
@@ -523,7 +523,7 @@ import '../styles/testimonials.css';
<article class="ks-bento-tile ks-bento-tile--span-6" id="why-ci">
<span class="ks-bento-num" data-color="patina">06</span>
<h3 class="why-panel-title">Block slop before it ships.</h3>
<p class="why-panel-body">A detector you can wire into PR checks. 57 deterministic rules, no LLM, exit codes the build can read.</p>
<p class="why-panel-body">A detector you can wire into PR checks. 59 deterministic rules, no LLM, exit codes the build can read.</p>
<div class="why-visual why-visual--ci">
<div class="why-ci-window">
<div class="why-ci-header">
@@ -801,7 +801,7 @@ import '../styles/testimonials.css';
</li>
<li>
<strong>CLI for CI</strong>
<span><code>npx impeccable detect src/</code> in a PR check. 57 deterministic rules. JSON output, exit codes for build gates.</span>
<span><code>npx impeccable detect src/</code> in a PR check. 59 deterministic rules. JSON output, exit codes for build gates.</span>
<a href="https://www.npmjs.com/package/impeccable" target="_blank" rel="noopener">View on npm →</a>
</li>
<li>
@@ -359,6 +359,29 @@ describe('detectUrl — browser-only fixtures', () => {
}
});
it('text-occlusion: box-over-text, inline padding leak, headline/card overhang flag; leading bleed, scrim, fixed bar pass', async () => {
const f = await detectUrl(`${baseUrl}/fixtures/antipatterns/text-occlusion.html`, { visualContrast: false });
const hits = f.filter(r => r.antipattern === 'text-occlusion');
const snippets = hits.map(h => h.snippet).join('\n');
assert.match(snippets, /flag-box-text/, `opaque box painted over text should flag: ${snippets}`);
assert.match(snippets, /flag-leak/, `inline element with leaked opaque padding should flag: ${snippets}`);
assert.match(snippets, /flag-headline/, `headline overhanging an opaque card should flag: ${snippets}`);
for (const cls of ['pass-title', 'pass-eyebrow', 'pass-hero', 'cap', 'pass-under', 'pass-fixedbar']) {
assert.doesNotMatch(snippets, new RegExp(cls), `".${cls}" must not flag: ${snippets}`);
}
assert.equal(hits.length, 3, `expected exactly 3 text-occlusion findings, got ${hits.length}: ${snippets}`);
});
it('first-viewport-column-overflow: stretched-hero column flags; balanced columns and single hero pass', async () => {
const f = await detectUrl(`${baseUrl}/fixtures/antipatterns/first-viewport-column-overflow.html`, { visualContrast: false });
const hits = f.filter(r => r.antipattern === 'first-viewport-column-overflow');
assert.equal(hits.length, 1, `expected exactly 1 first-viewport-column-overflow finding, got ${hits.length}: ${JSON.stringify(hits.map(h => h.snippet))}`);
assert.match(hits[0].snippet, /\bflag\b/, `finding must attach to the stretched-hero section: ${hits[0].snippet}`);
for (const cls of ['pass-balanced', 'pass-hero']) {
assert.doesNotMatch(hits[0].snippet, new RegExp(cls), `".${cls}" must not flag`);
}
});
it('visual contrast: browser fallback catches low contrast on image backgrounds', async () => {
const analyticOnly = await detectUrl(`${baseUrl}/fixtures/antipatterns/visual-contrast.html`, {
waitUntil: 'load',
@@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>first-viewport-column-overflow fixture</title>
<style>
* { margin: 0; box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #111; background: #fff; }
p { margin: 0 0 14px; line-height: 1.6; }
/* ── FLAG: two-column opening section, right column runs far past the fold
while the left column fits inside one viewport ── */
.flag { display: grid; grid-template-columns: 1.4fr 1fr; gap: 40px; padding: 40px; }
.flag .short { }
.flag .short h1 { font-size: 40px; margin-bottom: 16px; }
.flag .tall .filler { height: 1600px; background: #eee; border-radius: 8px; padding: 16px; }
/* ── PASS: balanced two-column section, both fit ── */
.pass-balanced { display: grid; grid-template-columns: 1fr 1fr; gap: 40px; padding: 40px; }
.pass-balanced .col { }
/* ── PASS: single full-page hero (no sibling column) ── */
.pass-hero { padding: 40px; }
.pass-hero .art { height: 1400px; background: #f3f3f3; }
</style>
</head>
<body>
<section class="flag">
<div class="short">
<h1>Upload your film and add a narrator</h1>
<p>Paste a link or drop a file. We transcribe it, translate, and one narrator reads over the top.</p>
<p>The original stays audible underneath, just quieter.</p>
</div>
<aside class="tall">
<p>What is a lektor? A single calm voice reads every line of a foreign film.</p>
<div class="filler">A tall editorial column with a long photo and a lot of copy.</div>
</aside>
</section>
<section class="pass-balanced">
<div class="col">
<p>Left column with a normal amount of copy that fits comfortably within the opening viewport.</p>
<p>Two short paragraphs, nothing that runs long.</p>
</div>
<div class="col">
<p>Right column, also short. Both sides sit inside the first screen.</p>
<p>No column stretches the fold.</p>
</div>
</section>
<section class="pass-hero">
<div class="art"></div>
</section>
</body>
</html>
+81
View File
@@ -0,0 +1,81 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>text-occlusion fixture</title>
<style>
* { margin: 0; box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #111; background: #fff; }
section { padding: 40px; position: relative; }
/* ── FLAG 1: opaque box painted over text (>30%) ── */
.flag-box-wrap { position: relative; height: 120px; }
.flag-box-text { position: absolute; top: 40px; left: 40px; font-size: 20px; width: 300px; }
.flag-box-cover { position: absolute; top: 30px; left: 30px; width: 240px; height: 60px;
background: #1f7a3d; border-radius: 8px; z-index: 5; }
/* ── FLAG 2: inline element with opaque fill + leaked vertical padding ── */
.flag-leak-host { position: relative; margin-top: 30px; }
.flag-leak { display: inline; background: #2f8543; padding: 36px; border-radius: 6px; }
.flag-leak-card { background: #14202b; color: #fff; padding: 24px; border-radius: 6px; }
/* ── FLAG 3: big headline overhanging an opaque card ── */
.flag-hero { position: relative; height: 220px; }
.flag-headline { position: absolute; top: 40px; left: 0; font-size: 64px; font-weight: 800;
width: 760px; color: #b22; white-space: nowrap; }
.flag-card { position: absolute; top: 20px; left: 700px; width: 360px; height: 180px;
background: #f4f5f7; border: 1px solid #ddd; box-shadow: 0 2px 8px rgba(0,0,0,.08); border-radius: 8px; }
/* ── PASS: stacked headline with tight leading (line-box bleed, no real overlap) ── */
.pass-stack { margin-top: 40px; }
.pass-eyebrow { font-size: 14px; letter-spacing: .1em; text-transform: uppercase; color: #a33; }
.pass-title { font-size: 96px; line-height: .9; font-weight: 800; }
/* ── PASS: text over image with a scrim, text on top (readable) ── */
.pass-hero { position: relative; height: 200px; overflow: hidden; border-radius: 8px; }
.pass-hero img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.pass-hero .scrim { position: absolute; inset: 0; background: linear-gradient(transparent, rgba(0,0,0,.6)); }
.pass-hero .cap { position: absolute; bottom: 16px; left: 16px; color: #fff; font-size: 24px; z-index: 2; }
/* ── PASS: fixed status bar over content at the fold ── */
.pass-under { margin-top: 20px; font-size: 16px; }
.pass-fixedbar { position: fixed; left: 0; right: 0; bottom: 0; height: 40px; background: #b22;
color: #fff; display: flex; align-items: center; padding: 0 16px; }
</style>
</head>
<body>
<section>
<div class="flag-box-wrap">
<p class="flag-box-text">Root cause identified in four minutes</p>
<div class="flag-box-cover" aria-hidden="true"></div>
</div>
<div class="flag-leak-host">
<span class="flag-leak" aria-hidden="true"></span>
<div class="flag-leak-card">Incident closed. Missing index on the replica, found from one alert.</div>
</div>
<div class="flag-hero">
<div class="flag-headline">The trace you need is the one they sampled away.</div>
<div class="flag-card">
<p>INCIDENT 4417</p>
<p>service checkout-api</p>
</div>
</div>
<div class="pass-stack">
<p class="pass-eyebrow">Family Italian on the waterfront</p>
<h1 class="pass-title">Trattoria da Nonna Lucia</h1>
</div>
<div class="pass-hero">
<img src="data:image/gif;base64,R0lGODlhAQABAIABAAAAAP///yH5BAEAAAEALAAAAAABAAEAAAICTAEAOw==" alt="">
<div class="scrim"></div>
<div class="cap">Villa on the cliff</div>
</div>
<p class="pass-under">Every span of every trace, kept on disk, no sampling ever applied here.</p>
</section>
<div class="pass-fixedbar">8/8 services up &middot; p99 1.20s &middot; retention infinite</div>
</body>
</html>