Add layout anti-pattern detection: nested cards, identical grids, spacing, centering

Four new layout detections:
- nested-cards: jsdom DOM walk finds card-like elements (shadow + rounded + bg)
  nested inside other card-like elements. Excludes dropdowns (absolute/fixed),
  form inputs, code blocks, badges (<20 chars), and known component classes.
- identical-card-grid: detects grid/flex parents with 3+ children sharing the
  same structural fingerprint (icon + heading + paragraph template pattern).
- monotonous-spacing: regex on raw HTML collects padding/margin/gap values
  (px, rem, Tailwind classes), rounds to nearest 4px, flags when >60% use
  the same value with <=3 distinct values.
- everything-centered: regex counts text-align:center and Tailwind text-center
  on text elements, flags when >70% of 5+ text elements are centered.

Also narrowed pure-black-white to only flag #000 as background color —
text-black, text-white, bg-white, and #fff are no longer flagged (too
common, per user feedback).

Extensive should-pass fixture covers: shadcn card sub-components, cards
with form inputs/dropdowns/code blocks/badges/accordions/tabs/images,
pricing cards, varied spacing, mixed centered/left-aligned layouts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-03-17 15:15:40 -07:00
co-authored by Claude Opus 4.6
parent e45d6cde1a
commit 72b9ca2941
5 changed files with 1008 additions and 70 deletions
+59
View File
@@ -277,6 +277,65 @@ describe('partials skip page-level checks', () => {
});
});
// ---------------------------------------------------------------------------
// Layout anti-patterns
// ---------------------------------------------------------------------------
describe('detectHtml — layout', () => {
test('layout-should-flag: detects nested cards', async () => {
const f = await detectHtml(path.join(FIXTURES, 'layout-should-flag.html'));
expect(f.some(r => r.antipattern === 'nested-cards')).toBe(true);
});
test('layout-should-flag: detects identical card grid', async () => {
const f = await detectHtml(path.join(FIXTURES, 'layout-should-flag.html'));
expect(f.some(r => r.antipattern === 'identical-card-grid')).toBe(true);
});
test('detects monotonous spacing via regex', () => {
// A page where every padding/margin is 16px
const html = '<!DOCTYPE html><html><body>' +
'<div style="padding: 16px; margin-bottom: 16px;"><p style="margin-bottom: 16px;">a</p></div>'.repeat(5) +
'</body></html>';
const f = detectText(html, 'test.html');
expect(f.some(r => r.antipattern === 'monotonous-spacing')).toBe(true);
});
test('detects everything centered via regex', () => {
const html = `<!DOCTYPE html><html><body>
<h1 style="text-align: center;">Title</h1>
<p style="text-align: center;">Paragraph one more text here</p>
<p style="text-align: center;">Paragraph two more text here</p>
<p style="text-align: center;">Paragraph three more text here</p>
<p style="text-align: center;">Paragraph four more text here</p>
<p style="text-align: center;">Paragraph five more text here</p>
<p style="text-align: center;">Paragraph six more text here</p>
</body></html>`;
const f = detectText(html, 'test.html');
expect(f.some(r => r.antipattern === 'everything-centered')).toBe(true);
});
test('layout-should-pass: no nested-cards false positives', async () => {
const f = await detectHtml(path.join(FIXTURES, 'layout-should-pass.html'));
expect(f.filter(r => r.antipattern === 'nested-cards')).toHaveLength(0);
});
test('layout-should-pass: no identical-card-grid false positives', async () => {
const f = await detectHtml(path.join(FIXTURES, 'layout-should-pass.html'));
expect(f.filter(r => r.antipattern === 'identical-card-grid')).toHaveLength(0);
});
test('layout-should-pass: no monotonous-spacing false positives', async () => {
const f = await detectHtml(path.join(FIXTURES, 'layout-should-pass.html'));
expect(f.filter(r => r.antipattern === 'monotonous-spacing')).toHaveLength(0);
});
test('layout-should-pass: no everything-centered false positives', async () => {
const f = await detectHtml(path.join(FIXTURES, 'layout-should-pass.html'));
expect(f.filter(r => r.antipattern === 'everything-centered')).toHaveLength(0);
});
});
// ---------------------------------------------------------------------------
// ANTIPATTERNS registry
// ---------------------------------------------------------------------------
+137
View File
@@ -0,0 +1,137 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Layout Anti-Patterns — Should Flag</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body { font-family: system-ui, sans-serif; background: #f9fafb; padding: 2rem; }
h1 { font-size: 1.5rem; margin-bottom: 0.5rem; }
h2 { font-size: 1.125rem; margin: 2.5rem 0 0.75rem; color: #6b7280; border-bottom: 1px solid #e5e7eb; padding-bottom: 0.5rem; }
p.intro { color: #6b7280; margin-bottom: 2rem; max-width: 36rem; }
</style>
</head>
<body>
<h1>Layout Anti-Patterns</h1>
<p class="intro">These should all be flagged by the detector.</p>
<!-- ============================================================ -->
<!-- 1. NESTED CARDS — Cardocalypse -->
<!-- ============================================================ -->
<h2>Nested Cards (Cardocalypse)</h2>
<!-- Classic: card inside card, both with shadow + rounded + bg -->
<div class="bg-white rounded-lg shadow-md p-6 max-w-md mb-4">
<h3 class="text-lg font-semibold mb-3">Outer Card</h3>
<div class="bg-white rounded-lg shadow-md p-4">
<h4 class="font-medium">Inner Card</h4>
<p class="text-sm text-gray-600">Card inside card — the classic cardocalypse.</p>
</div>
</div>
<!-- Three levels deep -->
<div class="bg-white rounded-xl shadow-lg p-6 max-w-md mb-4">
<h3 class="text-lg font-semibold mb-3">Level 1</h3>
<div class="bg-gray-50 rounded-lg shadow-sm p-4 mb-3">
<h4 class="font-medium mb-2">Level 2</h4>
<div class="bg-white rounded-md shadow-sm p-3">
<p class="text-sm">Level 3 — nesting inception.</p>
</div>
</div>
</div>
<!-- CSS variant: border + bg + shadow nesting -->
<div style="background: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); padding: 1.5rem; max-width: 28rem; margin-bottom: 1rem;">
<h3 style="font-weight: 600; margin-bottom: 0.75rem;">Outer (CSS)</h3>
<div style="background: #f9fafb; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.08); padding: 1rem;">
<p style="font-size: 0.875rem; color: #6b7280;">Inner card via CSS.</p>
</div>
</div>
<!-- shadcn-style Card nesting (Card inside Card, not Card sub-components) -->
<div class="rounded-lg border bg-white shadow-sm p-6 max-w-md mb-4" data-component="card">
<h3 class="font-semibold mb-3">shadcn-style Outer Card</h3>
<div class="rounded-lg border bg-white shadow-sm p-4" data-component="card">
<p class="text-sm text-gray-600">Another shadcn Card nested inside — still bad.</p>
</div>
</div>
<!-- ============================================================ -->
<!-- 2. IDENTICAL CARD GRIDS -->
<!-- ============================================================ -->
<h2>Identical Card Grid</h2>
<!-- Classic: 4 identical cards with icon + heading + text -->
<div class="grid grid-cols-2 gap-4 max-w-2xl mb-4">
<div class="bg-white rounded-lg shadow-sm p-6">
<div class="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center mb-4">
<svg class="w-6 h-6 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
</div>
<h3 class="font-semibold mb-2">Fast Performance</h3>
<p class="text-sm text-gray-600">Lightning fast response times with optimized infrastructure.</p>
</div>
<div class="bg-white rounded-lg shadow-sm p-6">
<div class="w-12 h-12 bg-green-100 rounded-lg flex items-center justify-center mb-4">
<svg class="w-6 h-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
</div>
<h3 class="font-semibold mb-2">Reliable Uptime</h3>
<p class="text-sm text-gray-600">99.99% uptime guarantee with automatic failover.</p>
</div>
<div class="bg-white rounded-lg shadow-sm p-6">
<div class="w-12 h-12 bg-purple-100 rounded-lg flex items-center justify-center mb-4">
<svg class="w-6 h-6 text-purple-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/></svg>
</div>
<h3 class="font-semibold mb-2">Bank-Level Security</h3>
<p class="text-sm text-gray-600">Enterprise-grade encryption and security controls.</p>
</div>
<div class="bg-white rounded-lg shadow-sm p-6">
<div class="w-12 h-12 bg-orange-100 rounded-lg flex items-center justify-center mb-4">
<svg class="w-6 h-6 text-orange-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
</div>
<h3 class="font-semibold mb-2">Team Collaboration</h3>
<p class="text-sm text-gray-600">Built for teams with real-time collaboration tools.</p>
</div>
</div>
<!-- ============================================================ -->
<!-- 3. MONOTONOUS SPACING -->
<!-- ============================================================ -->
<h2>Monotonous Spacing</h2>
<!-- Everything padded with the same value, same gaps -->
<div style="max-width: 28rem; margin-bottom: 1rem;">
<div style="background: white; padding: 16px; margin-bottom: 16px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<h3 style="margin-bottom: 16px; font-weight: 600;">Section One</h3>
<p style="margin-bottom: 16px; font-size: 0.875rem; color: #6b7280;">Every margin and padding is exactly 16px.</p>
<div style="padding: 16px; background: #f3f4f6; border-radius: 8px;">
<p style="font-size: 0.875rem; color: #6b7280; margin-bottom: 16px;">Even the inner padding is 16px.</p>
</div>
</div>
<div style="background: white; padding: 16px; margin-bottom: 16px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<h3 style="margin-bottom: 16px; font-weight: 600;">Section Two</h3>
<p style="margin-bottom: 16px; font-size: 0.875rem; color: #6b7280;">No rhythm, no variation, just 16px everywhere.</p>
</div>
</div>
<!-- ============================================================ -->
<!-- 4. EVERYTHING CENTERED -->
<!-- ============================================================ -->
<h2>Everything Centered</h2>
<div style="text-align: center; max-width: 32rem; margin: 0 auto 1rem;">
<h3 style="text-align: center; font-size: 1.25rem; font-weight: 700; margin-bottom: 1rem;">Welcome to Our Platform</h3>
<p style="text-align: center; color: #6b7280; margin-bottom: 1rem;">We provide the best solutions for your business needs.</p>
<div style="text-align: center; display: flex; gap: 1rem; justify-content: center; margin-bottom: 1rem;">
<button style="text-align: center; padding: 0.5rem 1rem; background: #3b82f6; color: white; border: none; border-radius: 6px;">Get Started</button>
<button style="text-align: center; padding: 0.5rem 1rem; background: white; color: #374151; border: 1px solid #d1d5db; border-radius: 6px;">Learn More</button>
</div>
<p style="text-align: center; font-size: 0.75rem; color: #9ca3af;">Trusted by over 10,000 companies worldwide.</p>
<div style="text-align: center; padding: 1rem; background: #f3f4f6; border-radius: 8px; margin-top: 1rem;">
<p style="text-align: center; font-size: 0.875rem; color: #6b7280;">Every. Single. Element. Is. Centered.</p>
</div>
</div>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
+242
View File
@@ -0,0 +1,242 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Layout — Clean Patterns (Should NOT Flag)</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body { font-family: system-ui, sans-serif; background: #f9fafb; padding: 2rem; color: #111827; }
h1 { font-size: 2rem; margin-bottom: 0.5rem; }
h2 { font-size: 1.25rem; margin: 2.5rem 0 0.75rem; color: #6b7280; border-bottom: 1px solid #e5e7eb; padding-bottom: 0.5rem; }
p.intro { color: #6b7280; margin-bottom: 2rem; }
</style>
</head>
<body>
<h1>Layout — Should Pass</h1>
<p class="intro">All patterns here are legitimate. None should be flagged.</p>
<!-- ============================================================ -->
<!-- shadcn-style Card sub-components (NOT nested cards) -->
<!-- ============================================================ -->
<h2>shadcn Card Sub-Components</h2>
<!-- Card with CardHeader / CardContent / CardFooter structure -->
<div class="rounded-lg border bg-white shadow-sm max-w-md mb-4">
<div class="flex flex-col space-y-1.5 p-6">
<h3 class="text-lg font-semibold">Card Title</h3>
<p class="text-sm text-gray-500">Card description goes here.</p>
</div>
<div class="p-6 pt-0">
<p class="text-sm text-gray-600">This is CardContent — a sub-section, not a nested card. No shadow, no border-radius of its own.</p>
</div>
<div class="flex items-center p-6 pt-0">
<button class="px-4 py-2 bg-gray-900 text-white text-sm rounded-md">Action</button>
</div>
</div>
<!-- ============================================================ -->
<!-- Card with form inputs (inputs have shadow/rounded) -->
<!-- ============================================================ -->
<h2>Card with Form Inputs</h2>
<div class="bg-white rounded-lg shadow-sm p-6 max-w-md mb-4 border">
<h3 class="font-semibold mb-4">Settings</h3>
<div class="space-y-3">
<input type="text" placeholder="Name" class="w-full px-3 py-2 border rounded-md shadow-sm text-sm focus:outline-none focus:ring-2">
<input type="email" placeholder="Email" class="w-full px-3 py-2 border rounded-md shadow-sm text-sm focus:outline-none focus:ring-2">
<select class="w-full px-3 py-2 border rounded-md shadow-sm text-sm bg-white">
<option>Select option</option>
</select>
<textarea placeholder="Bio" class="w-full px-3 py-2 border rounded-md shadow-sm text-sm h-20 resize-none"></textarea>
</div>
</div>
<!-- ============================================================ -->
<!-- Card with dropdown/popover (has shadow + rounded) -->
<!-- ============================================================ -->
<h2>Card with Dropdown</h2>
<div class="bg-white rounded-lg shadow-sm p-6 max-w-md mb-4 border relative">
<h3 class="font-semibold mb-2">Select Plan</h3>
<p class="text-sm text-gray-600 mb-3">Choose your subscription tier.</p>
<!-- Simulated dropdown menu -->
<div class="absolute top-full left-6 mt-1 w-48 bg-white rounded-md shadow-lg border py-1 z-10">
<div class="px-3 py-2 text-sm hover:bg-gray-50 cursor-pointer">Free</div>
<div class="px-3 py-2 text-sm hover:bg-gray-50 cursor-pointer">Pro</div>
<div class="px-3 py-2 text-sm hover:bg-gray-50 cursor-pointer">Enterprise</div>
</div>
</div>
<div style="height: 80px;"></div> <!-- spacer for dropdown -->
<!-- ============================================================ -->
<!-- Card with code block inside -->
<!-- ============================================================ -->
<h2>Card with Code Block</h2>
<div class="bg-white rounded-lg shadow-sm p-6 max-w-md mb-4 border">
<h3 class="font-semibold mb-3">Installation</h3>
<pre class="bg-gray-900 text-gray-100 rounded-md p-4 text-sm overflow-x-auto"><code>npm install @acme/sdk</code></pre>
</div>
<!-- ============================================================ -->
<!-- Card with badge/chip inside -->
<!-- ============================================================ -->
<h2>Card with Badges</h2>
<div class="bg-white rounded-lg shadow-sm p-6 max-w-md mb-4 border">
<h3 class="font-semibold mb-3">Tags</h3>
<div class="flex flex-wrap gap-2">
<span class="px-2 py-1 bg-blue-100 text-blue-700 rounded-full text-xs font-medium">React</span>
<span class="px-2 py-1 bg-green-100 text-green-700 rounded-full text-xs font-medium">TypeScript</span>
<span class="px-2 py-1 bg-purple-100 text-purple-700 rounded-full text-xs font-medium">Tailwind</span>
</div>
</div>
<!-- ============================================================ -->
<!-- Card with accordion/collapsible inside -->
<!-- ============================================================ -->
<h2>Card with Accordion</h2>
<div class="bg-white rounded-lg shadow-sm max-w-md mb-4 border">
<div class="p-4 border-b cursor-pointer flex justify-between items-center">
<span class="font-medium text-sm">How does billing work?</span>
<span class="text-gray-400">+</span>
</div>
<div class="p-4 border-b cursor-pointer flex justify-between items-center">
<span class="font-medium text-sm">Can I cancel anytime?</span>
<span class="text-gray-400">+</span>
</div>
<div class="p-4 cursor-pointer flex justify-between items-center">
<span class="font-medium text-sm">Do you offer refunds?</span>
<span class="text-gray-400">+</span>
</div>
</div>
<!-- ============================================================ -->
<!-- Card with image (rounded + shadow on image) -->
<!-- ============================================================ -->
<h2>Card with Styled Image</h2>
<div class="bg-white rounded-lg shadow-sm p-6 max-w-md mb-4 border">
<div class="w-full h-40 bg-gradient-to-br from-blue-400 to-purple-500 rounded-lg shadow-inner mb-4"></div>
<h3 class="font-semibold">Project Preview</h3>
<p class="text-sm text-gray-600 mt-1">Image placeholder with rounded corners and shadow.</p>
</div>
<!-- ============================================================ -->
<!-- Card with tab panels inside -->
<!-- ============================================================ -->
<h2>Card with Tabs</h2>
<div class="bg-white rounded-lg shadow-sm max-w-md mb-4 border">
<div class="flex border-b">
<button class="px-4 py-3 text-sm font-medium text-blue-600 border-b-2 border-blue-600">Overview</button>
<button class="px-4 py-3 text-sm text-gray-500">Analytics</button>
<button class="px-4 py-3 text-sm text-gray-500">Settings</button>
</div>
<div class="p-6">
<p class="text-sm text-gray-600">Tab content area — structured content inside a card, not a nested card.</p>
</div>
</div>
<!-- ============================================================ -->
<!-- Pricing cards (similar but intentionally comparative) -->
<!-- ============================================================ -->
<h2>Pricing Cards (Intentionally Similar)</h2>
<div class="grid grid-cols-3 gap-4 max-w-3xl mb-4">
<div class="bg-white rounded-lg shadow-sm p-6 border">
<h3 class="font-semibold mb-1">Free</h3>
<div class="text-2xl font-bold mb-4">$0</div>
<ul class="space-y-2 text-sm text-gray-600">
<li>5 projects</li>
<li>1 GB storage</li>
<li>Community support</li>
</ul>
</div>
<div class="bg-white rounded-lg shadow-sm p-6 border-2 border-blue-500">
<h3 class="font-semibold mb-1">Pro</h3>
<div class="text-2xl font-bold mb-4">$29</div>
<ul class="space-y-2 text-sm text-gray-600">
<li>Unlimited projects</li>
<li>50 GB storage</li>
<li>Priority support</li>
</ul>
</div>
<div class="bg-white rounded-lg shadow-sm p-6 border">
<h3 class="font-semibold mb-1">Enterprise</h3>
<div class="text-2xl font-bold mb-4">Custom</div>
<ul class="space-y-2 text-sm text-gray-600">
<li>Everything in Pro</li>
<li>Unlimited storage</li>
<li>Dedicated support</li>
</ul>
</div>
</div>
<!-- ============================================================ -->
<!-- Good spacing variety -->
<!-- ============================================================ -->
<h2>Varied Spacing (Good Rhythm)</h2>
<div style="max-width: 28rem; margin-bottom: 1rem;">
<div style="background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 2rem;">
<h3 style="font-weight: 600; margin-bottom: 0.5rem; font-size: 1.25rem;">Heading with tight spacing</h3>
<p style="font-size: 0.875rem; color: #6b7280; margin-bottom: 1.5rem;">Body text with medium spacing below.</p>
<div style="padding: 1rem; background: #f3f4f6; border-radius: 6px;">
<p style="font-size: 0.8125rem; color: #6b7280;">Nested content with different padding.</p>
</div>
</div>
<div style="background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<h3 style="font-weight: 600; margin-bottom: 0.75rem;">Another section</h3>
<p style="font-size: 0.875rem; color: #6b7280;">Different padding than above — intentional rhythm.</p>
</div>
</div>
<!-- ============================================================ -->
<!-- Centered hero section (legitimately centered) -->
<!-- ============================================================ -->
<h2>Centered Hero (Legitimate)</h2>
<div style="text-align: center; max-width: 32rem; margin: 0 auto 1rem; padding: 2rem 0;">
<h3 style="font-size: 1.5rem; font-weight: 700; margin-bottom: 0.5rem;">Hero Heading</h3>
<p style="color: #6b7280; margin-bottom: 1.5rem;">A centered hero section is fine — it's the rest of the page that shouldn't all be centered too.</p>
<button style="padding: 0.75rem 1.5rem; background: #111827; color: white; border: none; border-radius: 6px; font-weight: 500;">Get Started</button>
</div>
<!-- Left-aligned content following the hero -->
<div style="max-width: 32rem; margin-bottom: 1rem;">
<h3 style="font-size: 1.25rem; font-weight: 600; margin-bottom: 0.5rem;">Features</h3>
<p style="color: #6b7280; margin-bottom: 1rem;">This content is left-aligned, creating contrast with the centered hero above.</p>
<ul style="color: #4b5563; font-size: 0.875rem; padding-left: 1.25rem; list-style: disc;">
<li style="margin-bottom: 0.5rem;">Left-aligned list items</li>
<li style="margin-bottom: 0.5rem;">Natural reading direction</li>
<li>Intentional layout variety</li>
</ul>
</div>
<!-- ============================================================ -->
<!-- Non-card grid (varied structure) -->
<!-- ============================================================ -->
<h2>Grid with Varied Card Structures</h2>
<div class="grid grid-cols-2 gap-4 max-w-2xl mb-4">
<div class="bg-white rounded-lg shadow-sm p-6 border col-span-2">
<h3 class="font-semibold text-lg mb-2">Featured Item</h3>
<p class="text-sm text-gray-600">This card spans the full width — different from the others.</p>
</div>
<div class="bg-white rounded-lg shadow-sm p-4 border">
<h3 class="font-semibold text-sm">Compact Card</h3>
<p class="text-xs text-gray-500 mt-1">Smaller, less padding.</p>
</div>
<div class="bg-blue-50 rounded-lg p-4 border border-blue-200">
<h3 class="font-semibold text-sm text-blue-900">Highlighted</h3>
<p class="text-xs text-blue-700 mt-1">Different bg, no shadow — visual variety.</p>
</div>
</div>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>