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", andviewBoxattributesallowed_colors(line 145): Loads and validates against the colour palette defined inskills/diagram-design/references/style-guide.mdlint_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 palettefont-family— non-approved typefacesa11y— missing accessible-SVG elementsscript— unsafe inline scriptsexternal-asset— unauthorized external resourcespure-black— literal#000000instead 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:
- Loads HTML in headless Chromium (Google Fonts optionally allowed)
- Captures "authored" viewport screenshot
- Releases overflow constraints on the SVG and ancestors through staged "releases"
- Captures "released" screenshot
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:
- Run
lint-skin.pyfirst — catches cheap-to-fix markup errors without browser overhead - Run
lint-render.pysecond — 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.pyvalidates author intent through static HTML analysis—colours, fonts, accessibility, and securitylint-render.pyvalidates browser reality through rendered screenshot comparison—clipping, overflow, and runtime errors- Together they ensure diagrams are semantically correct and visually faithful
- Use
--all --baselineforlint-skin.pyand--allforlint-render.pyto 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →