mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 21:57:14 +03:00
Retire the single-font rule
One family with weight and size contrast carrying the hierarchy is a legitimate type system, and in practice the rule mostly punished minimal pages: it was the loudest cross-rule noise on the fixture corpus's should-pass columns. Removed from the registry, both engine paths, the regex page analyzers, and the devtools category map; the negative assertions stay as resurrection guards, and the text-content analyzer index base shifts down one with the removal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
10d16c3c87
commit
19e400e392
@@ -139,16 +139,6 @@ const ANTIPATTERNS = [
|
||||
skillSection: 'Typography',
|
||||
skillGuideline: 'overused fonts like Inter',
|
||||
},
|
||||
{
|
||||
id: 'single-font',
|
||||
category: 'slop',
|
||||
scopes: ['type'],
|
||||
name: 'Single font without hierarchy',
|
||||
description:
|
||||
'Only one font family is used for the entire page. A single family can work when weight and size contrast carry the hierarchy; otherwise pair a distinctive display font with a refined body font.',
|
||||
skillSection: 'Typography',
|
||||
skillGuideline: 'only one font family for the entire page',
|
||||
},
|
||||
{
|
||||
id: 'flat-type-hierarchy',
|
||||
category: 'slop',
|
||||
@@ -4712,12 +4702,6 @@ function checkTypography() {
|
||||
if (isBrandFontOnOwnDomain(font)) continue;
|
||||
findings.push({ type: 'overused-font', detail: `Primary font: ${font} (${Math.round(share * 100)}% of text)` });
|
||||
}
|
||||
|
||||
// Single-font check: only one distinct primary font across all text
|
||||
if (fontUsage.size === 1) {
|
||||
const only = [...fontUsage.keys()][0];
|
||||
findings.push({ type: 'single-font', detail: `only font used is ${only}` });
|
||||
}
|
||||
}
|
||||
|
||||
const sizes = new Set();
|
||||
@@ -4979,14 +4963,6 @@ function checkPageTypography(doc, win) {
|
||||
findings.push({ id: 'overused-font', snippet: `Primary font: ${font}` });
|
||||
}
|
||||
|
||||
// Single font
|
||||
if (fonts.size === 1) {
|
||||
const els = doc.querySelectorAll('*');
|
||||
if (els.length >= 20) {
|
||||
findings.push({ id: 'single-font', snippet: `only font used is ${[...fonts][0]}` });
|
||||
}
|
||||
}
|
||||
|
||||
// Flat type hierarchy
|
||||
const sizes = new Set();
|
||||
const textEls = doc.querySelectorAll('h1, h2, h3, h4, h5, h6, p, span, a, li, td, th, label, button, div');
|
||||
|
||||
@@ -241,24 +241,6 @@ const REGEX_MATCHERS = [
|
||||
];
|
||||
|
||||
const REGEX_ANALYZERS = [
|
||||
// Single font
|
||||
(content, filePath) => {
|
||||
const fontFamilyRe = /font-family\s*:\s*([^;}]+)/gi;
|
||||
const fonts = new Set();
|
||||
let m;
|
||||
while ((m = fontFamilyRe.exec(content)) !== null) {
|
||||
for (const f of m[1].split(',').map(f => f.trim().replace(/^['"]|['"]$/g, '').toLowerCase())) {
|
||||
if (f && !GENERIC_FONTS.has(f)) fonts.add(f);
|
||||
}
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(content)) fonts.add(f);
|
||||
if (fonts.size !== 1 || content.split('\n').length < 20) return [];
|
||||
const name = [...fonts][0];
|
||||
const lines = content.split('\n');
|
||||
let line = 1;
|
||||
for (let i = 0; i < lines.length; i++) { if (lines[i].toLowerCase().includes(name)) { line = i + 1; break; } }
|
||||
return [finding('single-font', filePath, `only font used is ${name}`, line)];
|
||||
},
|
||||
// Flat type hierarchy
|
||||
(content, filePath) => {
|
||||
const sizes = new Set();
|
||||
@@ -626,10 +608,11 @@ const TEXT_CONTENT_ANALYZER_IDS = [
|
||||
function runTextContentAnalyzers(content, filePath, options = {}) {
|
||||
const profile = options?.profile;
|
||||
if (!shouldRunPageAnalyzers(content, filePath)) return [];
|
||||
// The 3 text-content analyzers are at indices 3-5 in REGEX_ANALYZERS.
|
||||
// The 3 text-content analyzers are at indices 2-4 in REGEX_ANALYZERS
|
||||
// (single-font's removal on 2026-07-29 shifted every index down one).
|
||||
const findings = [];
|
||||
for (let i = 0; i < TEXT_CONTENT_ANALYZER_IDS.length; i++) {
|
||||
const analyzer = REGEX_ANALYZERS[3 + i];
|
||||
const analyzer = REGEX_ANALYZERS[2 + i];
|
||||
const ruleId = TEXT_CONTENT_ANALYZER_IDS[i];
|
||||
findings.push(...profileFindings(profile, {
|
||||
engine: 'regex',
|
||||
@@ -750,7 +733,6 @@ function detectText(content, filePath, options = {}) {
|
||||
// Page-level analyzers only run on full pages
|
||||
if (shouldRunPageAnalyzers(content, filePath)) {
|
||||
const analyzerIds = [
|
||||
'single-font',
|
||||
'flat-type-hierarchy',
|
||||
'monotonous-spacing',
|
||||
'em-dash-overuse',
|
||||
|
||||
@@ -60,9 +60,6 @@ function checkStaticPageTypography(document, window) {
|
||||
for (const font of overusedFound) {
|
||||
findings.push({ id: 'overused-font', snippet: `Primary font: ${font}` });
|
||||
}
|
||||
if (fonts.size === 1 && document.querySelectorAll('*').length >= 20) {
|
||||
findings.push({ id: 'single-font', snippet: `only font used is ${[...fonts][0]}` });
|
||||
}
|
||||
const sizes = new Set();
|
||||
for (const el of document.querySelectorAll('h1, h2, h3, h4, h5, h6, p, span, a, li, td, th, label, button, div')) {
|
||||
const fontSize = parseFloat(window.getComputedStyle(el).fontSize);
|
||||
|
||||
@@ -28,16 +28,6 @@ const ANTIPATTERNS = [
|
||||
skillSection: 'Typography',
|
||||
skillGuideline: 'overused fonts like Inter',
|
||||
},
|
||||
{
|
||||
id: 'single-font',
|
||||
category: 'slop',
|
||||
scopes: ['type'],
|
||||
name: 'Single font without hierarchy',
|
||||
description:
|
||||
'Only one font family is used for the entire page. A single family can work when weight and size contrast carry the hierarchy; otherwise pair a distinctive display font with a refined body font.',
|
||||
skillSection: 'Typography',
|
||||
skillGuideline: 'only one font family for the entire page',
|
||||
},
|
||||
{
|
||||
id: 'flat-type-hierarchy',
|
||||
category: 'slop',
|
||||
|
||||
@@ -3911,12 +3911,6 @@ function checkTypography() {
|
||||
if (isBrandFontOnOwnDomain(font)) continue;
|
||||
findings.push({ type: 'overused-font', detail: `Primary font: ${font} (${Math.round(share * 100)}% of text)` });
|
||||
}
|
||||
|
||||
// Single-font check: only one distinct primary font across all text
|
||||
if (fontUsage.size === 1) {
|
||||
const only = [...fontUsage.keys()][0];
|
||||
findings.push({ type: 'single-font', detail: `only font used is ${only}` });
|
||||
}
|
||||
}
|
||||
|
||||
const sizes = new Set();
|
||||
@@ -4178,14 +4172,6 @@ function checkPageTypography(doc, win) {
|
||||
findings.push({ id: 'overused-font', snippet: `Primary font: ${font}` });
|
||||
}
|
||||
|
||||
// Single font
|
||||
if (fonts.size === 1) {
|
||||
const els = doc.querySelectorAll('*');
|
||||
if (els.length >= 20) {
|
||||
findings.push({ id: 'single-font', snippet: `only font used is ${[...fonts][0]}` });
|
||||
}
|
||||
}
|
||||
|
||||
// Flat type hierarchy
|
||||
const sizes = new Set();
|
||||
const textEls = doc.querySelectorAll('h1, h2, h3, h4, h5, h6, p, span, a, li, td, th, label, button, div');
|
||||
|
||||
@@ -228,7 +228,6 @@ const FIX_SKILLS = {
|
||||
'side-tab': 'distill, polish',
|
||||
'border-accent-on-rounded':'distill, polish',
|
||||
'overused-font': 'typeset',
|
||||
'single-font': 'typeset',
|
||||
'flat-type-hierarchy': 'typeset',
|
||||
'gradient-text': 'typeset, distill',
|
||||
'ai-color-palette': 'colorize, distill',
|
||||
|
||||
@@ -497,10 +497,12 @@ describe('detectHtml — static HTML/CSS fixtures', () => {
|
||||
assert.match(sideTabs[0].snippet, /border-left: 2px solid oklch/);
|
||||
});
|
||||
|
||||
it('typography-should-flag: detects all three issues', async () => {
|
||||
it('typography-should-flag: detects both issues', async () => {
|
||||
const f = await detectHtml(path.join(FIXTURES, 'typography-should-flag.html'));
|
||||
assert.ok(f.some(r => r.antipattern === 'overused-font'));
|
||||
assert.ok(f.some(r => r.antipattern === 'single-font'));
|
||||
// single-font retired 2026-07-29: one family with weight/size contrast is
|
||||
// a legitimate system, and the rule mostly punished minimal test pages.
|
||||
assert.ok(!f.some(r => r.antipattern === 'single-font'), 'retired rule single-font should not resurface');
|
||||
assert.ok(f.some(r => r.antipattern === 'flat-type-hierarchy'));
|
||||
assert.equal(
|
||||
f.some(r => r.antipattern === 'low-contrast'),
|
||||
|
||||
Reference in New Issue
Block a user