Reading Validation Output
Understanding Validation Output
Designer Agent Kit — documentation written for AI agents, readable by humans.
How Validation Works
jay-stack validate runs static analysis on .jay-html files. It parses each template and checks it against rules from installed validator plugins (SEO, a11y, design-system).
Key concept: static analysis sees the template, not the rendered page. The validator checks what's written in the .jay-html file — it cannot see:
- Dynamic content from
{bindings}(e.g.,{post.content}may contain images and headings) - Data fetched at render time from services
- Content generated by other tools or scripts
What the Validator Sees
The validator processes each .jay-html file with:
- Page template — your HTML, bindings, and inline styles
- Full-stack component content —
<jay:SiteHeader />,<jay:DocsSidebar />etc. are flattened into the page before validation - Headless component tags —
<jay:product-widget>inline templates are visible - Linked CSS files —
<link rel="stylesheet" href="...">referenced from<head>are read and validated (e.g., for CSS@importof external URLs) - Inline
<style>blocks — CSS in<head>is parsed for rule violations
So a warning about an <img> might come from a full-stack component's template, not your page template. A CSS warning might come from a linked stylesheet.
Errors vs Warnings
- Errors block the build. They must be fixed — there is no way to suppress them.
- Warnings must be either fixed or explicitly suppressed. Do not ignore warnings — each one has a clear resolution path (add the missing attribute, or suppress via
<script type="application/jay-validations">).
Broken internal links are errors (DL#210). validate resolves every <a href> against the project's
static routes + public assets. An href that resolves to nothing is an error: a placeholder (href="#" or
empty), a typo, or a link to a page/asset that does not exist. Links that match a dynamic route pattern
(e.g. /blog/[slug]) are not checked here — the concrete slug list isn't known at validate time — so they
never false-error. External URLs, mailto:/tel:, in-page #fragments, and {binding} hrefs are skipped.
Fix the path, use <button> for JS-driven anchors, or suppress per-page (see the jay-stack table below).
One design-system rule is a hard error, not a warning: REGION-CSS-NO-REF (DL#209) — a <jay:X> region
whose template= ships CSS but which has no ref=. Without a ref the CSS has no scope anchor, so sync
silently drops it; the fix is to add a ref (see design-system-guide.md → The shape of a region's CSS).
It is unsuppressible because the alternative would leak the region's styles across the whole page.
How to Read Warnings
Each warning has:
- Message — what was found and why it matters
- Suggestion — how to fix it, and how to suppress it if the warning is intentional
- Element — which HTML element triggered it (some include the full tag with attributes)
Acting on Warnings
Check if the element is in your template — search your
.jay-htmlfile for the element. If it's not there, it comes from a full-stack component.For full-stack component warnings — fix them in the component's
.jay-htmlfile (e.g.,src/components/site-header/site-header.jay-html), not in the page that imports it.For dynamic content warnings — if the warning is about content inside a
{binding}(like{post.content}), you can't fix it in the template. The content is generated at render time. These are usually false positives.Don't loop — if a warning can't be fixed in any template file, it's a validator limitation. Move on.
Common Scenarios
"Image without loading attribute" from a component
The image is in a full-stack component (e.g., sidebar icons). Fix it in the component's .jay-html, not the page.
"Heading level skipped" with dynamic content
Your page has <h1> and a component has <h3>, but the dynamic content (markdown) provides <h2> at render time. If the heading hierarchy is correct in the rendered page, fix the component to use the right level — don't restructure the page.
"No fetchpriority=high" on a text-only page
Your page has no large images in the template. Small icons (under 200px) don't need fetchpriority. This warning only fires for large images.
Warnings on component files
Components are fragments — they don't need <h1>, <main>, <title>, or <meta>. These page-level rules are skipped for files in src/components/. If you still see them, check the file path.
Suppressing Warnings
Each warning's suggestion tells you exactly what to add or change to suppress it. The general suppression mechanisms are:
Add the missing attribute
Most warnings are suppressed by adding the attribute the rule checks for. The suggestion tells you which attribute and what values are accepted. For example, loading="lazy" or loading="eager" both suppress the image loading warning.
Design system: inline comment
For design-system token warnings, add /* design-system: allow */ as a comment on the same CSS line:
padding: 96px 0; /* design-system: allow */
Page-level validation overrides
For page-level warnings that can't be fixed by adding an attribute (e.g., "no LCP image" on a text-first page), use a <script type="application/jay-validations"> tag in the <head>:
<head>
<script type="application/jay-validations">
seo:
no-lcp-image: true
</script>
</head>
The YAML body is keyed by plugin name. Each plugin defines its own suppressible rules. This tag is a build-time directive — it's parsed during validation and never rendered in the page output.
Multiple plugins can be configured in one tag:
<script type="application/jay-validations">
seo:
no-lcp-image: true
design-system:
allow-undefined-vars: true
allow-viewport-height: true
</script>
Design-system suppression keys include allow-undefined-vars, allow-viewport-height (top-level
viewport-height containers — see jay-html-styling.md), allow-custom-breakpoints,
allow-no-reduced-motion, and allow-font-no-fallback.
Prefer-design-system-elements warnings (jay-stack namespace)
The core jay-stack validate emits four warnings that nudge you to reuse UI through design-system elements
(see design-system-guide.md). Suppress the legitimate case:
| Rule | How to suppress |
|---|---|
REGION-NOT-LINKED |
jay-validations="REGION-NOT-LINKED" on the application/jay-headless import, or jay-stack: allow-inline-region: [Contract] |
REGION-OVERRIDE-NON-CONTENT |
jay-validations="REGION-OVERRIDE-NON-CONTENT" on the import |
COMPONENT-NO-TEMPLATE |
jay-stack: allow-no-template: [Contract] |
NO-DESIGN-SYSTEM |
jay-stack: allow-no-design-system: true |
| Broken internal link (DL#210) | jay-stack: allow-broken-links: true (per-page — skips all link checks for that page) |
<script type="application/jay-validations">
jay-stack:
allow-inline-region: [Card]
allow-no-template: [MetricsProvider]
allow-no-design-system: true
</script>
When you can't suppress
If a warning comes from dynamic content ({post.content}) or a generated file, you can't suppress it in the template. This is a validator limitation — the warning is a false positive. Don't loop trying to fix it.
Design-system scorecard (metrics to optimize)
Below the warnings, validate prints a report-only scorecard (DL#207). It is never a pass/fail — it
emits no warnings and never affects the exit code. It turns "prefer design-system elements" from a nudge into
two numbers you can deliberately raise:
- Tag coverage — of every contract tag/ref a page's regions could bind, how many it actually binds.
- Design-system coverage % — what share of a page's DOM elements come from
template=-backed design-system regions (vs. hand-authored markup). Higher = more of the page is built from the design system. - Reuse — how many catalogued templates are used more than once across the project. A design system whose templates are each used once isn't really a design system.
The healthy target: most of each page is composed of design-system elements, and each template is reused more than once. When building a page, prefer linking an existing template (raising coverage and reuse) over hand-authoring markup.
By default the scorecard prints one-line totals for the whole project:
📦 Tag coverage: 100% (43/43 tags used across 10 page(s))
📊 Design-system scorecard: 80% element coverage (74/92), 2 of 5 catalogued templates reused > 1
Run validate -v for per-page details.
Run with -v / --verbose to expand the per-page breakdown (coverage per file, which tags are unused,
per-template reuse counts, and ⚠ low flags on pages under 50% coverage).
Two validation tiers
Validation comes in two tiers. Know which one you're running.
Tier 1 — always on (jay-stack validate)
The fast, template-only gate. It reads your .jay-html files and route scan only — no build needed — so it's
cheap enough for the hot agent loop and runs on every validate. Everything described above is Tier 1.
For links, Tier 1 (DL#210) resolves every hand-authored <a href> against the project's static routes + public
assets and errors on: broken internal links (typos, missing pages), degenerate # / empty placeholder
links, and self-links (a page linking to its own route). Links that match a dynamic route pattern
(/design-log/[...slug]) are deferred — the concrete slug set isn't known without a build.
Tier 2 — opt-in, against a build (jay-stack validate --from-build)
Deep validation against an existing build's output (DL#211). It reads
build/v<version>/backend/route-manifest.json plus the per-instance *.cache.json slow-render results — it does
not run any render, so it's cheap, but it requires a prior jay-stack build. If no build is found it's a
single actionable error (run jay-stack build first, or omit --from-build). Run it pre-deploy / in CI.
--from-build adds three checks, all against the concrete URLs the build actually produced:
- Deferred dynamic-slug links (ERROR) — the template links Tier 1 could only defer (because they matched a
dynamic pattern) are now resolved against the real URL set. A link to a dynamic route with a slug the build
never generated (e.g.
/design-log/wix/index) is caught here. - Broken links in rendered content (WARNING) —
<a href>found inside the slow-rendered ViewState (markdown bodies, descriptions) that resolve to a URL the build does not produce. Relative hrefs are resolved against the instance's own URL. These are warnings, not errors, because content often doubles as repo/GitHub docs where repo-relative links like../pkg/foo.tsare legitimately correct. - Per-instance meta/SEO (mostly WARNING) — resolves each route's
headMetatemplate against each instance's slow ViewState and validates the concrete<title>/<meta name="description">. Empty title/description, a title > 60 chars, or a description > 160 chars → warning. A leftover unresolved{binding}— a field never produced at any phase — → error. Note (DL#189): a field bound to the fast/interactive phase is legitimately empty at slow render, so emptiness is only ever a warning, never an error.
If the build is older than your current source files, a staleness warning is emitted ("validated against a build from <timestamp>; source has changed since") suggesting a rebuild.
Tier-2 findings print under their own 📦 build-output (--from-build) section, separate from 📦 jay-stack (core).
Suppressing Tier-2 findings
Per-page, via the same <script type="application/jay-validations"> block under the jay-stack: key:
allow-broken-links: true— suppresses link findings (both template and rendered-content links) for that page.allow-meta-issues: true— suppresses meta/SEO findings for that page.
<script type="application/jay-validations">
jay-stack:
allow-broken-links: true
allow-meta-issues: true
</script>
Running Validation
jay-stack validate # Tier 1: all pages (+ one-line scorecard totals)
jay-stack validate -v # add per-page tag coverage + scorecard detail
jay-stack validate --strict # treat warnings as errors
jay-stack validate --from-build # add Tier 2: validate against the highest build/v* output
jay-stack validate --from-build --build-dir <dir> # point Tier 2 at a specific build backend dir or build root
Validation runs automatically during jay-stack build. Warnings don't block the build; errors do (with --strict).
About this document
This page is part of the Jay Stack Agent Kit — documentation generated from the framework source and written primarily for AI agents. The language and structure are optimized for machine consumption — expect precise, specification-style prose rather than narrative documentation. Learn more about the Agent Kit →