mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-15 07:36:50 +03:00
The quality detection rules (line-length, cramped-padding, tight-leading, tiny-text, justified-text, all-caps-body, wide-tracking, skipped-heading) were originally added as browser-only and wired only into the overlay loop. The CLI's jsdom path silently skipped all of them. Two of the eight rules genuinely need real browser layout (line-length reads rect.width for chars-per-line; cramped-padding reads rect.width/height to filter small badges). The other six only need computed CSS values and pure DOM walks — they can run in jsdom too. Refactor - Extract a pure checkQuality(opts) from checkElementQualityDOM, taking pre-resolved lineHeightPx and letterSpacingPx so each adapter handles its own unit resolution. - Add resolveFontSizePx(el, win) — walks the parent chain to compute effective font-size in pixels, handling px / rem / em / % through inheritance. Browsers do this automatically in getComputedStyle, but jsdom returns "0.875rem" verbatim, which broke naive parseFloat math. - Add resolveLengthPx(value, fontSizePx) — generic CSS length → px helper used for line-height and letter-spacing in the Node adapter. - Extract checkPageQualityFromDoc(doc) and add a Node call site so skipped-heading fires from the CLI too. - Add checkElementQuality(el, style, tag, window) Node adapter and wire it into detectHtml's element loop. Tests - New tests/detect-antipatterns-browser.test.mjs — Puppeteer-backed runner that spins up a temporary static server (port 8765, mirrors the dev server's /fixtures/* and /js/* routes) and uses detectUrl() to load fixtures in headless Chrome. Asserts the two browser-only rules (cramped-padding, line-length) that need real layout. - New tests/fixtures/antipatterns/cramped-padding.html — focused side-by-side fixture for the cramped-padding rule. Pass column includes a faithful replica of .detection-cmd from the homepage (the disputed "small inline pill" case the user is deciding what to do with). Test asserts 3 findings: 2 from the obvious flag column + 1 from the disputed pill. - New tests/fixtures/antipatterns/quality.html — merged side-by-side replacement for the orphaned quality-should-flag/pass.html files. Covers all 7 typography-quality rules. The 6 jsdom-compatible rules are asserted in the jsdom test; line-length stays in the Puppeteer test. - Delete the orphaned quality-should-flag.html / quality-should-pass.html. - Wire the new browser test into bun run test (~2.6s overhead). Coverage win: the CLI now catches tight-leading, tiny-text, justified-text, all-caps-body, wide-tracking, and skipped-heading on real projects, where it previously missed all six. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
228 lines
9.6 KiB
HTML
228 lines
9.6 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Quality (Typography & Readability) — Should Flag vs Should Pass</title>
|
|
<style>
|
|
/* Two-column fixture: left = should flag, right = should pass.
|
|
Covers the typography-quality rules that need real browser layout
|
|
(line-length, tight-leading, tiny-text, justified-text, all-caps-body,
|
|
wide-tracking, skipped-heading). All are browser-only — see
|
|
tests/detect-antipatterns-browser.test.mjs for assertions. */
|
|
body { font-family: system-ui, sans-serif; background: #fafafa; padding: 24px; margin: 0; color: #0f172a; line-height: 1.6; }
|
|
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; max-width: 1280px; margin: 0 auto; }
|
|
.col h2 { font-size: 14px; text-transform: uppercase; letter-spacing: 0.05em; margin: 0 0 16px; color: #475569; }
|
|
.col h3 { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; margin: 24px 0 8px; color: #64748b; }
|
|
.case { margin-bottom: 16px; padding: 16px; background: white; border: 1px solid #e2e8f0; border-radius: 8px; }
|
|
.case-label { display: block; font-size: 12px; color: #64748b; margin-bottom: 6px; font-style: italic; }
|
|
|
|
/* ── FLAG: typography quality issues ── */
|
|
|
|
/* Line length: 12px body text fits >85 chars/line in a ~528px column.
|
|
12px is not below the tiny-text 12px floor (rule fires at <12px). */
|
|
.long-lines {
|
|
font-size: 12px;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
/* Tight leading */
|
|
.tight-leading {
|
|
font-size: 16px;
|
|
line-height: 1.0;
|
|
max-width: 40em;
|
|
}
|
|
|
|
/* Tiny body text */
|
|
.tiny-text {
|
|
font-size: 10px;
|
|
line-height: 1.6;
|
|
max-width: 40em;
|
|
}
|
|
.tiny-text-11 {
|
|
font-size: 11px;
|
|
line-height: 1.6;
|
|
max-width: 40em;
|
|
}
|
|
|
|
/* Justified text without hyphens */
|
|
.justified-no-hyphens {
|
|
text-align: justify;
|
|
max-width: 30em;
|
|
font-size: 16px;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
/* All-caps long body */
|
|
.all-caps-body {
|
|
text-transform: uppercase;
|
|
font-size: 16px;
|
|
max-width: 40em;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
/* Wide letter spacing on body */
|
|
.wide-tracking-body {
|
|
letter-spacing: 0.15em;
|
|
font-size: 16px;
|
|
max-width: 40em;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
/* ── PASS: comfortable typography ── */
|
|
|
|
.good-measure {
|
|
max-width: 65ch;
|
|
font-size: 16px;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.good-leading {
|
|
font-size: 16px;
|
|
line-height: 1.6;
|
|
max-width: 40em;
|
|
}
|
|
|
|
.good-text-size {
|
|
font-size: 16px;
|
|
line-height: 1.6;
|
|
max-width: 40em;
|
|
}
|
|
|
|
/* Justified text WITH hyphens — fine */
|
|
.justified-with-hyphens {
|
|
text-align: justify;
|
|
hyphens: auto;
|
|
-webkit-hyphens: auto;
|
|
max-width: 30em;
|
|
font-size: 16px;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
/* Short label in all-caps (passes — under 30 chars) */
|
|
.label-caps {
|
|
text-transform: uppercase;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
letter-spacing: 0.1em;
|
|
color: #475569;
|
|
}
|
|
|
|
/* Wide tracking on a short uppercase label (passes — both conditions excluded) */
|
|
.label-tracking {
|
|
letter-spacing: 0.15em;
|
|
text-transform: uppercase;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
color: #475569;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="grid">
|
|
|
|
<!-- ════════════════════════════════════════════════════════════
|
|
LEFT COLUMN: should flag
|
|
═══════════════════════════════════════════════════════════ -->
|
|
<div class="col" data-col="flag">
|
|
<h2>Should flag</h2>
|
|
|
|
<h3>Line length too long</h3>
|
|
<div class="case">
|
|
<span class="case-label">no max-width on a paragraph</span>
|
|
<p class="long-lines">This paragraph has no max-width constraint at all, which means on a wide monitor or ultrawide display, each line of text can stretch to 150 or even 200 characters wide. Research consistently shows that line lengths beyond 75 characters significantly reduce reading speed and comprehension. The eye has to travel too far to find the beginning of the next line, causing readers to lose their place. A simple max-width of 65ch to 75ch on the paragraph or its container would fix this entirely.</p>
|
|
</div>
|
|
|
|
<h3>Tight line height</h3>
|
|
<div class="case">
|
|
<span class="case-label">line-height: 1.0</span>
|
|
<p class="tight-leading">This paragraph has a line-height of 1.0, which means the lines are touching. Multi-line body text needs breathing room between lines for readability. A line-height of 1.5 to 1.7 is generally recommended for body text.</p>
|
|
</div>
|
|
|
|
<h3>Tiny body text</h3>
|
|
<div class="case">
|
|
<span class="case-label">10px body text</span>
|
|
<p class="tiny-text">This body text is only 10px. While this might be fine for a disclaimer or legal footnote, it's too small for primary content that users need to actually read. Aim for at least 14px for body content, 16px is ideal.</p>
|
|
</div>
|
|
|
|
<h3>Justified text without hyphens</h3>
|
|
<div class="case">
|
|
<span class="case-label">text-align: justify, no hyphens: auto</span>
|
|
<p class="justified-no-hyphens">This paragraph uses text-align: justify, which forces each line to stretch to fill the full width. Without hyphenation support, this creates uneven gaps between words known as "rivers of white space" that flow vertically through the text. Left-aligned text is almost always more readable on the web.</p>
|
|
</div>
|
|
|
|
<h3>All-caps body text</h3>
|
|
<div class="case">
|
|
<span class="case-label">text-transform: uppercase on a long passage</span>
|
|
<p class="all-caps-body">This entire paragraph is in uppercase via text-transform. While all-caps works for short labels, headings, or navigation items, longer body text in uppercase is significantly harder to read because we lose the word shape cues that come from ascenders and descenders in mixed-case text.</p>
|
|
</div>
|
|
|
|
<h3>Wide letter spacing on body text</h3>
|
|
<div class="case">
|
|
<span class="case-label">letter-spacing: 0.15em on body text</span>
|
|
<p class="wide-tracking-body">This body text has letter-spacing: 0.15em applied to it. While subtle tracking adjustments can improve readability for headings or all-caps text, adding significant letter spacing to body text actually makes it harder to read by disrupting natural character groupings.</p>
|
|
</div>
|
|
|
|
<h3>Skipped heading levels</h3>
|
|
<div class="case">
|
|
<span class="case-label">h1 → h3 (missing h2)</span>
|
|
<h1 style="font-size: 20px; margin: 0 0 4px;">Top heading</h1>
|
|
<h3 style="font-size: 16px; margin: 0;">Skips straight to h3</h3>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ════════════════════════════════════════════════════════════
|
|
RIGHT COLUMN: should pass
|
|
═══════════════════════════════════════════════════════════ -->
|
|
<div class="col" data-col="pass">
|
|
<h2>Should pass</h2>
|
|
|
|
<h3>Comfortable line length</h3>
|
|
<div class="case">
|
|
<span class="case-label">max-width: 65ch on the paragraph</span>
|
|
<p class="good-measure">This paragraph has a max-width of 65ch, keeping the line length comfortable for reading. The eye can easily track from the end of one line to the beginning of the next without losing its place.</p>
|
|
</div>
|
|
|
|
<h3>Comfortable line height</h3>
|
|
<div class="case">
|
|
<span class="case-label">line-height: 1.6</span>
|
|
<p class="good-leading">This paragraph has a line-height of 1.6, which gives multi-line text plenty of room to breathe and improves the rhythm of the page.</p>
|
|
</div>
|
|
|
|
<h3>Comfortable body text size</h3>
|
|
<div class="case">
|
|
<span class="case-label">16px body text</span>
|
|
<p class="good-text-size">This is 16px body text — the recommended baseline for comfortable reading on modern displays.</p>
|
|
</div>
|
|
|
|
<h3>Justified text with hyphens</h3>
|
|
<div class="case">
|
|
<span class="case-label">text-align: justify + hyphens: auto</span>
|
|
<p class="justified-with-hyphens">When justified text is paired with hyphens: auto, the browser can break long words across lines, eliminating the rivers of white space and making the justification look intentional rather than awkward.</p>
|
|
</div>
|
|
|
|
<h3>Short label in all-caps</h3>
|
|
<div class="case">
|
|
<span class="case-label">short label, uppercase</span>
|
|
<span class="label-caps">Featured</span>
|
|
</div>
|
|
|
|
<h3>Wide tracking on a short uppercase label</h3>
|
|
<div class="case">
|
|
<span class="case-label">letter-spacing on a short uppercase label</span>
|
|
<span class="label-tracking">Beta</span>
|
|
</div>
|
|
|
|
<h3>Proper heading hierarchy</h3>
|
|
<div class="case">
|
|
<span class="case-label">h1 → h2 → h3</span>
|
|
<h1 style="font-size: 20px; margin: 0 0 4px;">Top heading</h1>
|
|
<h2 style="font-size: 18px; margin: 0 0 4px;">Second level</h2>
|
|
<h3 style="font-size: 16px; margin: 0;">Third level</h3>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script src="/js/detect-antipatterns-browser.js"></script>
|
|
</body>
|
|
</html>
|