ReferenceToolingGrowingupdated 2026.085 min read
Reference · tooling

The checks

A green build does not mean the pages are fine. This toolchain places one check at each layer, each catching a class of "build green, page broken" silent failure — and two of them look at pages the way a reader does, in a real browser.

five checks · three from the engine, two from this package · all green before a commit

§1Five gates, five layers

CheckShips withLooks atWhen to run
check-contentengine, scripts/check-content.mjsevery md/mdx source filecontent-repo CI, or after writing
check-wikilinksengine, scripts/check-wikilinks.mjsthe [[wikilink]] graphcontent-repo CI, or after renaming a note
check-distengine, scripts/check-dist.mjsthe astro build outputafter every build (postbuild)
ui_probethis package, scripts/ui_probe.mjspages rendered in a real browserafter style/layout changes
contrast_probethis package, scripts/contrast_probe.mjsevery text node's contrast, both themesafter any token change

§2check-content: the source layer

Compiles every md/mdx headed for a page with the exact same dialect as the site — syntax errors and silent deformations (emphasis markers that cannot pair, braces swallowed by MDX evaluation, list markers born from line wrapping, single-line $$, formulas KaTeX cannot render — the same set the kitchen-sink closes with) all turn CI red.

Why this script must come from the engine: a checker whose plugin set differs from the site's is worse than no checker — one missing the math plugin misreads formula braces as JSX expressions, one without GFM waves tables through. The dialect is written once in the engine; site rendering, CMS save validation and this script consume the same one, always.

content repo root
node <engine>/scripts/check-content.mjs . --glob '**/index.{md,mdx}' --math

Beyond compiling, it catches two frontmatter classes of silent loss: an unquoted # in a value (YAML reads it as a comment and silently truncates the rest) and YAML that does not parse at all, reported with its line.

Dead [[wikilinks]] deliberately do not fail the build — a garden must be allowed to link to notes it hasn't grown yet. But link rot still belongs in CI, so the engine ships a lint that resolves every wikilink with the library's own parser and resolution rules (alias, brand, title, locale mirrors) and reports the missing, the ambiguous, and the dubious anchors. --strict turns dead links into a failing exit — that is how this repository's CI runs it:

this garden's link check, as CI runs it
node scripts/check-links.mjs

§4check-dist: the output layer

In the built dist/, every internal reference a reader can click must actually exist. It catches the silent gaps beneath a green build:

  • internal links/assets pointing at files that don't exist (the kind that appears by the dozen after a route reshuffle);
  • in-page anchors pointing at ids that don't exist;
  • doubled locale segments in paths (/en/en/ — the classic result of i18n fallbacks stacking a prefix onto already-prefixed routes);
  • <a> nested inside <a> (the HTML parser closes the outer one early and buttons fall out of their cards);
  • KaTeX error residue (the formula is red text on the page while the build stays green).

This demo wires it into postbuild: a green npm run build means the output check passed too.

site root, after a build (pass --base when the site deploys under a subpath)
node vendor/astro-inkbrush/scripts/check-dist.mjs dist --base ${DEMO_BASE:-/}

§5The render-layer probe

Source and output can both be right while the page is still broken — the classic case being landing cards that no stylesheet rule matches, rendering as one crammed line of bare text: link and anchor checks stay green because they never look at a rendered page. ui_probe drives a real browser over every page in dist at four viewport widths (1440/1024/768/430) and measures: horizontal overflow of the page, elements wider than their container with no scroll box to live in, classes no stylesheet rule styles, skipped heading levels, duplicate ids, images without an alt attribute, in-page anchors and aria-controls that point at nothing. It reports only what a machine can prove — no aesthetic judgment.

needs a local Chrome/Chromium
npm run build
node ../scripts/ui_probe.mjs dist   # serves dist itself; pass a baseUrl to probe a live server

It inspects the whole document — chrome, sidebar and dialogs included. Green means the report's last line reads SAMPLES WITH FINDINGS: 0 (a sample is one route at one width).

§6The contrast probe

The tokens claim AA, so the claim is measured rather than asserted. contrast_probe loads every page of dist in a real browser — light and dark theme, desktop and phone width — and measures every text run rendered in the default state: HTML text, SVG text and the generated text of ::before / ::after, with every <dialog data-probe-open> overlay probed open (and a query typed into a search box found there) on a representative page — the marker is the site's declaration that the dialog is complete as authored. The ground is not read from a stylesheet; the page is rendered with every glyph made transparent, screenshotted, and the pixel under each run is its ground, so gradients, color-mix() tints, translucent layers and the night palette are all measured as they render. A run's foreground carries the accumulated opacity of its element and ancestors, so dimmed text is measured at the strength a reader actually sees. Hover and focus states are reviewed, not probed. Small text is held to 4.5:1, large text (24px, or 18.66px bold) to 3:1; a run whose color cannot be parsed or whose ground cannot be sampled counts as a finding. The report names the page, theme, width, selector, the two colors and the ratio of every run under the bar:

after a build, both themes
node ../scripts/contrast_probe.mjs dist   # PROBE_THEMES / PROBE_WIDTHS narrow the matrix

The style-change routine
Any change to the package's stylesheets or components goes through this demo's build first, then ui_probe and contrast_probe over the output — all green before committing. The demo doubles as the package's test bed.

Titles, sections and body text, in this language.
    ↑↓ · Enter · Escastro-inkstone