---
sidebar_position: 11
---

# 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.

```javascript
window.__devtool.checkTextFragility()
```

**Returns:**
```javascript
{
  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:**
| Type | Severity | Category | Description |
|------|----------|----------|-------------|
| `clipped` | error | Clipping | Text cut off by `overflow:hidden` without ellipsis - **silent content loss** |
| `vertical-clipped` | error | Clipping | Text cut off vertically by `overflow:hidden` - **silent content loss** |
| `truncated` | warning | Truncation | Text shortened with ellipsis (visible indicator, but content hidden) |
| `horizontal-overflow` | error | Overflow | Text spills outside container horizontally (visible) |
| `vertical-overflow` | error | Overflow | Text spills outside container vertically (visible) |
| `multi-line-auto-height` | warning | Layout Shift | Multi-line text with auto height - content changes may cause shift |
| `long-word-no-break` | warning | Overflow Risk | Long 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:**
```javascript
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.

```javascript
window.__devtool.checkResponsiveRisk()
```

**Returns:**
```javascript
{
  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:**
| Type | Severity | Description |
|------|----------|-------------|
| `fixed-width` | warning/error | Fixed pixel width may cause horizontal scroll |
| `min-width-too-large` | warning/error | min-width larger than mobile viewports |
| `exceeds-viewport` | error | Element currently exceeds viewport width |
| `small-touch-target` | warning | Interactive element smaller than 44x44px (Apple HIG) |
| `unintended-horizontal-scroll` | error | Element causes horizontal scroll without overflow-x setting |
| `positioned-offscreen-right` | warning | Absolute/fixed element extends past right edge |
| `large-fixed-element` | warning | Large fixed element may obscure content on mobile |
| `small-font` | warning | Font size below 12px may be hard to read |
| `extreme-font-size` | warning | Very large or small font may cause issues |
| `wide-table` | error | Table wider than viewport |
| `table-not-scrollable` | warning | Wide table without horizontal scroll wrapper |

**Example:**
```javascript
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`)
```

---

## capturePerformanceMetrics

Capture comprehensive performance metrics including CLS, long tasks, and resource timing.

```javascript
window.__devtool.capturePerformanceMetrics()
```

**Returns:**
```javascript
{
  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:**
```javascript
const perf = window.__devtool.capturePerformanceMetrics()

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

```typescript
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:

```html
<!-- 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

```typescript
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

```javascript
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

```javascript
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

```javascript
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

- [Quality Auditing](/api/frontend/quality-auditing) - Frame rate, memory, and Core Web Vitals
- [Layout Diagnostics](/api/frontend/layout-diagnostics) - Basic overflow and stacking detection
- [Accessibility](/api/frontend/accessibility) - Built-in a11y checks
- [Performance Monitoring](/use-cases/performance-monitoring) - Performance use cases
