Skip to main content

Layout Robustness & Fragility Detection

Functions for detecting layout fragility, text overflow issues, responsive risks, and performance problems.

These audits detect three categories of layout issues:

  • Overflow - Content spilling visibly outside container bounds
  • Clipping - Content silently cut off by overflow: hidden (content loss without visual indicator)
  • Truncation - Content shortened with ellipsis (intentional but may hide important text)

checkTextFragility​

Detect text overflow, truncation, and layout shift risks. Analyzes all text elements on the page for issues that may cause content loss or layout problems at different viewport sizes.

window.__devtool.checkTextFragility()

Returns:

{
issues: [
{
selector: ".card-title",
text: "This is the visible text content...",
longestWord: {
word: "internationalization",
length: 20,
minWidthPx: 185
},
issues: [
{
type: "clipped", // Most dangerous: content silently lost
severity: "error",
message: "Text clipped by overflow:hidden without ellipsis - content is silently lost",
details: {
scrollWidth: 250,
clientWidth: 150,
hiddenContent: "100px",
suggestion: "Add text-overflow: ellipsis, or allow text to wrap, or increase container width"
}
}
],
problematicBreakpoints: [
{
breakpoint: 320,
estimatedWidth: 120,
requiredWidth: 185,
deficit: 65
},
{
breakpoint: 375,
estimatedWidth: 140,
requiredWidth: 185,
deficit: 45
}
]
}
],
summary: {
total: 4,
errors: 2,
warnings: 2,
elementsAnalyzed: 156
},
breakpointsTested: [320, 375, 414, 768, 1024, 1280, 1440, 1920]
}

Issue Types:

TypeSeverityCategoryDescription
clippederrorClippingText cut off by overflow:hidden without ellipsis - silent content loss
vertical-clippederrorClippingText cut off vertically by overflow:hidden - silent content loss
truncatedwarningTruncationText shortened with ellipsis (visible indicator, but content hidden)
horizontal-overflowerrorOverflowText spills outside container horizontally (visible)
vertical-overflowerrorOverflowText spills outside container vertically (visible)
multi-line-auto-heightwarningLayout ShiftMulti-line text with auto height - content changes may cause shift
long-word-no-breakwarningOverflow RiskLong word (>15 chars) without word-break may overflow on narrow screens

Key Features:

  • Clipping detection - Finds text silently cut off by overflow:hidden (the most dangerous issue)
  • Reports the longest word in each element and its minimum pixel width
  • Predicts which breakpoints will cause overflow issues
  • Detects both horizontal and vertical overflow/clipping
  • Identifies layout shift risks from dynamic content

Example:

const fragility = window.__devtool.checkTextFragility()

if (fragility.summary.errors > 0) {
console.log('Critical text issues found!')
fragility.issues
.filter(i => i.issues.some(issue => issue.severity === 'error'))
.forEach(i => {
console.log(`${i.selector}: ${i.text}`)
console.log(` Longest word: "${i.longestWord.word}" needs ${i.longestWord.minWidthPx}px`)
if (i.problematicBreakpoints.length > 0) {
console.log(` Breaks at: ${i.problematicBreakpoints.map(b => b.breakpoint + 'px').join(', ')}`)
}
})
}

checkResponsiveRisk​

Detect elements at risk of layout issues at different viewport sizes. Checks for fixed dimensions, small touch targets, horizontal scroll issues, and more.

window.__devtool.checkResponsiveRisk()

Returns:

{
issues: [
{
selector: ".product-card",
tagName: "div",
issues: [
{
type: "fixed-width",
severity: "warning",
message: "Fixed width (400px) may cause horizontal scroll on mobile",
details: {
width: "400px",
breakpointsAffected: [320, 375]
}
}
]
},
{
selector: "button.small-btn",
tagName: "button",
issues: [
{
type: "small-touch-target",
severity: "warning",
message: "Touch target smaller than 44x44px minimum",
details: {
width: "32px",
height: "28px",
recommended: "44x44px minimum"
}
}
]
}
],
summary: {
total: 5,
errors: 1,
warnings: 4,
elementsAnalyzed: 245
},
currentViewport: {
width: 1440,
height: 900
},
breakpointsTested: [320, 375, 414, 768, 1024, 1280, 1440, 1920]
}

Issue Types:

TypeSeverityDescription
fixed-widthwarning/errorFixed pixel width may cause horizontal scroll
min-width-too-largewarning/errormin-width larger than mobile viewports
exceeds-viewporterrorElement currently exceeds viewport width
small-touch-targetwarningInteractive element smaller than 44x44px (Apple HIG)
unintended-horizontal-scrollerrorElement causes horizontal scroll without overflow-x setting
positioned-offscreen-rightwarningAbsolute/fixed element extends past right edge
large-fixed-elementwarningLarge fixed element may obscure content on mobile
small-fontwarningFont size below 12px may be hard to read
extreme-font-sizewarningVery large or small font may cause issues
wide-tableerrorTable wider than viewport
table-not-scrollablewarningWide table without horizontal scroll wrapper

Example:

const risks = window.__devtool.checkResponsiveRisk()

// Check for critical issues
if (risks.summary.errors > 0) {
console.log('Critical responsive issues:')
risks.issues.forEach(el => {
el.issues
.filter(i => i.severity === 'error')
.forEach(issue => {
console.log(`${el.selector}: ${issue.message}`)
})
})
}

// Find small touch targets
const smallTargets = risks.issues.filter(el =>
el.issues.some(i => i.type === 'small-touch-target')
)
console.log(`${smallTargets.length} elements have small touch targets`)

auditPerformance​

Capture comprehensive performance metrics including Core Web Vitals, CLS, long tasks, and resource timing. See Quality Auditing for the full report shape.

window.__devtool.auditPerformance()

Returns: Promise

{
cls: {
score: 0.15,
rating: "needs-improvement", // "good" | "needs-improvement" | "poor"
shifts: [
{
value: 0.08,
startTime: 1523,
sources: [".ad-banner", ".hero-image"]
}
]
},
longTasks: [
{ duration: 85, startTime: 234, name: "self" }
],
resources: {
byType: {
script: { count: 12, totalSize: 450000, totalDuration: 1200 },
img: { count: 25, totalSize: 1200000, totalDuration: 2500 },
css: { count: 5, totalSize: 85000, totalDuration: 300 }
},
largest: [
{ url: "/images/hero.jpg", type: "img", size: 450000, duration: 800 }
],
slowest: [
{ url: "/api/data", type: "fetch", size: 5000, duration: 1200 }
],
renderBlocking: [
{ url: "/styles.css", type: "css", size: 45000, duration: 200 }
]
},
paint: {
firstPaint: 450,
firstContentfulPaint: 620
},
totals: {
pageWeight: 1855000,
resourceCount: 46,
loadTime: 2800,
domContentLoaded: 1200
},
timestamp: 1699999999999
}

CLS Rating Thresholds:

  • good: < 0.1
  • needs-improvement: 0.1 - 0.25
  • poor: > 0.25

Example:

const perf = await window.__devtool.auditPerformance()

console.log(`Page weight: ${(perf.totals.pageWeight / 1024 / 1024).toFixed(2)} MB`)
console.log(`Load time: ${perf.totals.loadTime}ms`)

if (perf.cls && perf.cls.rating !== 'good') {
console.log('CLS issues:', perf.cls.shifts)
}

E2E Testing Examples​

The agnt audit system is tested using Playwright. Here are examples from the test suite:

Running Audits via the Indicator UI​

import { test, expect, Page } from '@playwright/test';

// Wait for the __devtool API to be available
async function waitForDevtool(page: Page): Promise<void> {
await page.waitForFunction(
() => {
return (
typeof window.__devtool !== 'undefined' &&
window.__devtool !== null &&
typeof window.__devtool.indicator !== 'undefined'
);
},
{ timeout: 15000 }
);
}

// Run an audit and check results
test('Text Fragility audit detects overflow issues', async ({ page }) => {
await page.goto('http://localhost:12345/layout-issues.html');
await waitForDevtool(page);

// Open the indicator panel
await page.evaluate(() => {
window.__devtool.indicator.togglePanel(true);
});

// Click the Audit dropdown
const auditBtn = page.locator('#__devtool-indicator button').filter({
hasText: 'Audit',
}).first();
await auditBtn.click({ force: true });

// Select Text Fragility audit
await page.waitForSelector('#__devtool-audit-menu');
const menuItem = page.locator('#__devtool-audit-menu button').filter({
hasText: 'Text Fragility',
});
await menuItem.click({ force: true });

// Wait for audit to complete and check results
await page.waitForTimeout(2000);
const result = await page.evaluate(() => {
return window.__devtool.checkTextFragility();
});

expect(result.summary.elementsAnalyzed).toBeGreaterThan(0);
expect(result.breakpointsTested).toContain(320);
});

Test Fixtures for Layout Issues​

Create test pages with known issues to validate audits:

<!-- layout-issues.html -->
<!DOCTYPE html>
<html>
<head>
<style>
/* Fixed width that breaks on mobile */
.fixed-width-card {
width: 500px;
border: 1px solid #ccc;
}

/* Text that will overflow */
.overflow-text {
width: 100px;
white-space: nowrap;
overflow: visible;
}

/* Small touch target */
.tiny-button {
width: 20px;
height: 20px;
padding: 0;
}

/* Long word without word-break */
.long-word-container {
width: 150px;
}
</style>
</head>
<body>
<div class="fixed-width-card">
<p>This card has a fixed width that will cause issues on mobile.</p>
</div>

<div class="overflow-text">
This text will overflow its container because of nowrap.
</div>

<button class="tiny-button">X</button>

<div class="long-word-container">
<p>Contains supercalifragilisticexpialidocious which is very long.</p>
</div>
</body>
</html>

Smoke Test for All Audits​

test('All audits complete without errors', async ({ page }) => {
await page.goto('http://localhost:12345/clean-baseline.html');
await waitForDevtool(page);

const audits = [
'Full Page Audit',
'Accessibility',
'Security',
'SEO / Meta',
'Layout Issues',
'Text Fragility',
'Responsive Risk',
'Last Click Context',
'Recent DOM Changes',
'Browser State',
'Network/Resources',
'DOM Complexity',
'CSS Quality',
];

for (const label of audits) {
const result = await runAudit(page, label);
expect.soft(result.success, `${label} should appear in results`).toBe(true);
expect.soft(result.hasError, `${label} should not error`).toBe(false);
}
});

Common Patterns​

Pre-Deploy Quality Check​

async function preDeployCheck() {
const textFragility = window.__devtool.checkTextFragility()
const responsiveRisk = window.__devtool.checkResponsiveRisk()

const issues = []

// No text overflow errors
if (textFragility.summary.errors > 0) {
issues.push(`${textFragility.summary.errors} text overflow errors`)
}

// No viewport overflow
const viewportOverflows = responsiveRisk.issues.filter(el =>
el.issues.some(i => i.type === 'exceeds-viewport')
)
if (viewportOverflows.length > 0) {
issues.push(`${viewportOverflows.length} elements overflow viewport`)
}

// No critical touch target issues
const tinyTargets = responsiveRisk.issues.filter(el =>
el.issues.some(i => i.type === 'small-touch-target')
)
if (tinyTargets.length > 5) {
issues.push(`${tinyTargets.length} small touch targets`)
}

return {
pass: issues.length === 0,
issues: issues,
textFragility: textFragility.summary,
responsiveRisk: responsiveRisk.summary
}
}

Mobile Compatibility Check​

function checkMobileReady() {
const risks = window.__devtool.checkResponsiveRisk()

// Find issues that affect mobile breakpoints (320-414px)
const mobileIssues = risks.issues.filter(el =>
el.issues.some(i =>
i.details?.breakpointsAffected?.some(bp => bp <= 414)
)
)

console.log(`Mobile issues: ${mobileIssues.length}`)
mobileIssues.forEach(el => {
console.log(` ${el.selector}:`)
el.issues.forEach(i => console.log(` - ${i.message}`))
})

return {
mobileReady: mobileIssues.length === 0,
issues: mobileIssues
}
}

Content Loss Detection​

function findContentLoss() {
const fragility = window.__devtool.checkTextFragility()

// Find elements losing content - prioritize silent clipping (worst)
const contentLoss = fragility.issues.filter(el =>
el.issues.some(i =>
i.type === 'clipped' || // Silent horizontal clipping (worst)
i.type === 'vertical-clipped' || // Silent vertical clipping (worst)
i.type === 'truncated' || // Visible truncation with ellipsis
i.type === 'horizontal-overflow' ||
i.type === 'vertical-overflow'
)
)

// Report silent clipping first (most dangerous)
const silentClipping = contentLoss.filter(el =>
el.issues.some(i => i.type === 'clipped' || i.type === 'vertical-clipped')
)

if (silentClipping.length > 0) {
console.log('CRITICAL: Silent content clipping detected!')
silentClipping.forEach(el => {
console.log(` ${el.selector}: "${el.text}"`)
const clip = el.issues.find(i => i.type === 'clipped' || i.type === 'vertical-clipped')
console.log(` Hidden: ${clip.details.hiddenContent}`)
console.log(` Fix: ${clip.details.suggestion}`)
})
}

contentLoss.forEach(el => {
console.log(`Content lost at: ${el.selector}`)
console.log(` Text: "${el.text}"`)
console.log(` Longest word: "${el.longestWord.word}" (${el.longestWord.length} chars)`)
console.log(` Min width needed: ${el.longestWord.minWidthPx}px`)
if (el.problematicBreakpoints.length > 0) {
console.log(` Problematic at: ${el.problematicBreakpoints.map(b => b.breakpoint + 'px').join(', ')}`)
}
})

return contentLoss
}

Performance Notes​

  • checkTextFragility and checkResponsiveRisk scan all elements - may be slow on large pages
  • Both functions are synchronous and block the main thread
  • Use for diagnostic purposes, not in production hot paths
  • Consider running audits after page load completes
  • The indicator UI runs audits asynchronously and displays results as attachment chips

See Also​