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

The difference between lint-skin.py and lint-render.py is that lint-skin.py performs static analysis on source HTML markup, while 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 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
  • 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

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

# 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 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 first — catches cheap-to-fix markup errors without browser overhead
  2. Run lint-render.py second — validates visual fidelity and runtime behaviour

The repository's scripts/self_check.py wraps both for unified CI invocation.

Key Source Files

Path Purpose
scripts/lint-skin.py Static markup validator implementation
scripts/lint-render.py Headless-Chromium renderer and pixel-diff logic
skills/diagram-design/references/style-guide.md Colour palette and typography authority
skills/diagram-design/assets/template-motion.html Canonical motion controller verified by lint-skin.py

Summary

  • lint-skin.py validates author intent through static HTML analysis—colours, fonts, accessibility, and security
  • 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 and --all for lint-render.py to validate entire example sets

Frequently Asked Questions

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

No. lint-render.py depends on Playwright to launch Chromium. The static-only 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. 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 validates colours against style-guide.md. 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →