# lint-skin.py vs lint-render.py: What's the Difference for Checking Diagram Output?

> Understand the difference between lint-skin.py and lint-render.py for checking diagram output. Learn how static analysis and headless browser execution differ for diagram design.

- Repository: [Cathryn Lavery/diagram-design](https://github.com/cathrynlavery/diagram-design)
- Tags: comparison
- Published: 2026-09-05

---

**The difference between [`lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/lint-skin.py) and [`lint-render.py`](https://github.com/cathrynlavery/diagram-design/blob/main/lint-render.py) is that [`lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/lint-skin.py) performs static analysis on source HTML markup, while [`lint-render.py`](https://github.com/cathrynlavery/diagram-design/blob/main/lint-render.py) executes the diagram in a headless browser and validates actual rendered output.**

The cathrynlavery/diagram-design repository enforces diagram quality through two complementary linting scripts. Each script catches different failure modes—one inspects what the author wrote, the other verifies what the browser displays.

## What lint-skin.py Checks: Static Markup Analysis

[`scripts/lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/lint-skin.py) parses HTML without executing JavaScript or loading external resources. It enforces **static conformity**: colours, fonts, accessibility, and security rules derivable from markup alone.

### Core Validation Functions

The script implements several specialized checkers:

- **`lint_accessible_svgs`** (line 332): Enforces the accessible-SVG contract—required `<title>`, `<desc>`, `role="img"`, and `viewBox` attributes
- **`allowed_colors`** (line 145): Loads and validates against the colour palette defined in [`skills/diagram-design/references/style-guide.md`](https://github.com/cathrynlavery/diagram-design/blob/main/skills/diagram-design/references/style-guide.md)
- **`lint_text`** (line 558): Runs colour, font-family, and resource-usage checks

### What Violations Trigger Failures

Every reported category causes CI failure:

- `color` — values outside the approved palette
- `font-family` — non-approved typefaces
- `a11y` — missing accessible-SVG elements
- `script` — unsafe inline scripts
- `external-asset` — unauthorized external resources
- `pure-black` — literal `#000000` instead of near-black tones

```bash

# Check a single diagram for static violations

python3 scripts/lint-skin.py skills/diagram-design/assets/example-pie.html

# Validate all examples against baseline

python3 scripts/lint-skin.py --all --baseline

```

## What lint-render.py Checks: Dynamic Rendering Fidelity

[`scripts/lint-render.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/lint-render.py) launches Chromium via Playwright to catch **runtime rendering issues** invisible to static analysis. It validates what actually appears on screen, not what the markup declares.

### The Screenshot Diff Process

The **`measure`** function (line 88) orchestrates the core validation:

1. Loads HTML in headless Chromium (Google Fonts optionally allowed)
2. Captures "authored" viewport screenshot
3. Releases overflow constraints on the SVG and ancestors through staged "releases"
4. Captures "released" screenshot
5. **`compare_release`** (line 76) performs pixel-by-pixel diff

This detects paint that spills outside the SVG bounding box or gets clipped by ancestor elements.

### Failure Categories

Only categories **not** in `NOTE_CATEGORIES` (`{"unmeasurable"}`) break the build. The `"unmeasurable"` note prints for informational purposes but permits CI success.

Additional catches include:
- Runtime JavaScript errors
- Missing local assets (404s)
- Page overflow beyond viewport
- Network-isolation failures

```bash

# Render-time check for clipping and runtime errors

python3 scripts/lint-render.py skills/diagram-design/assets/example-pie.html

# Validate all examples (requires Playwright installation)

python3 scripts/lint-render.py --all

```

## Side-by-Side Comparison

| Dimension | [`lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/lint-skin.py) | [`lint-render.py`](https://github.com/cathrynlavery/diagram-design/blob/main/lint-render.py) |
|-----------|---------------|------------------|
| **Analysis target** | Source HTML markup | Browser-rendered output |
| **Execution environment** | Python `HTMLParser` | Headless Chromium via Playwright |
| **JavaScript execution** | None | Full execution |
| **Primary failure modes caught** | Colour violations, wrong fonts, missing accessibility attributes, unsafe scripts | Clipping, overflow, runtime errors, missing assets |
| **CI failure semantics** | All reported categories fail the build | Only non-note categories fail; `"unmeasurable"` warnings allowed |
| **Self-validation** | Implicit through test cases | Explicit `self_test` suite (line 99) proves detection works |

## When to Use Each Linter

Use **both** in sequence for comprehensive coverage:

1. **Run [`lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/lint-skin.py) first** — catches cheap-to-fix markup errors without browser overhead
2. **Run [`lint-render.py`](https://github.com/cathrynlavery/diagram-design/blob/main/lint-render.py) second** — validates visual fidelity and runtime behaviour

The repository's [`scripts/self_check.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/self_check.py) wraps both for unified CI invocation.

## Key Source Files

| Path | Purpose |
|------|---------|
| [`scripts/lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/lint-skin.py) | Static markup validator implementation |
| [`scripts/lint-render.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/lint-render.py) | Headless-Chromium renderer and pixel-diff logic |
| [`skills/diagram-design/references/style-guide.md`](https://github.com/cathrynlavery/diagram-design/blob/main/skills/diagram-design/references/style-guide.md) | Colour palette and typography authority |
| [`skills/diagram-design/assets/template-motion.html`](https://github.com/cathrynlavery/diagram-design/blob/main/skills/diagram-design/assets/template-motion.html) | Canonical motion controller verified by [`lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/lint-skin.py) |

## Summary

- **[`lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/lint-skin.py)** validates *author intent* through static HTML analysis—colours, fonts, accessibility, and security
- **[`lint-render.py`](https://github.com/cathrynlavery/diagram-design/blob/main/lint-render.py)** validates *browser reality* through rendered screenshot comparison—clipping, overflow, and runtime errors
- Together they ensure diagrams are **semantically correct** and **visually faithful**
- Use `--all --baseline` for [`lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/lint-skin.py) and `--all` for [`lint-render.py`](https://github.com/cathrynlavery/diagram-design/blob/main/lint-render.py) to validate entire example sets

## Frequently Asked Questions

### Can I run lint-render.py without installing Playwright?

No. [`lint-render.py`](https://github.com/cathrynlavery/diagram-design/blob/main/lint-render.py) depends on Playwright to launch Chromium. The static-only [`lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/lint-skin.py) requires no browser infrastructure.

### Why does lint-render.py allow "unmeasurable" notes without failing?

Per the source code, `NOTE_CATEGORIES = {"unmeasurable"}` defines explicitly permitted notes. This accommodates edge cases where overflow detection is technically impossible—such as extremely large or dynamically sizing diagrams—without blocking CI.

### Which linter should I run first in local development?

Start with [`lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/lint-skin.py). It executes faster, requires no browser, and catches markup errors that would also fail in rendering. Fix static issues before investing in render-time validation.

### Do both linters check the same colour palette?

Only [`lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/lint-skin.py) validates colours against [`style-guide.md`](https://github.com/cathrynlavery/diagram-design/blob/main/style-guide.md). [`lint-render.py`](https://github.com/cathrynlavery/diagram-design/blob/main/lint-render.py) operates on rendered pixels and cannot distinguish intended palette colours from arbitrary CSS values—it detects visual problems, not semantic style compliance.