Web Performance: the first load of a route
Diagnose and fix what a user sees on a route's first load. Two territories:
- Hand-wired loading -
@font-face written by hand, fonts pulled from a
hosted CDN (Google Fonts), native <img>, resource hints in your own
document head. No framework layer is doing it for you, so you must - and
must verify it yourself.
- The rendering path itself - streamed shells, Suspense fallbacks and the
skeleton-to-content swap, hydration timing, router-level pending states.
Here the framework's rendering model is the mechanism, and the lever is
where you place boundaries and what the fallback reserves.
Where a framework does automate the fix (Next's next/font/next/image,
Astro's Fonts API), defer to that layer's own output - but app code
wrapping it stays in scope (references/framework-automation.md).
Out of scope
This skill owns the first-load experience of a route - everything the user
sees between requesting a route and its settled first view, whoever produced
the HTML. A soft navigation counts as the next route's first load, and a
Suspense-boundary placement counts even though it shapes TTFB. Still not here:
- Bundle-size analysis (tree-shaking, chunk budgets, dependency audits).
Naming a large hydration bundle as a C1 cause is in scope; auditing and
shrinking it is not.
- Steady-state interaction (INP) tuning - interaction latency after the
route has settled. Hydration timing is in scope only where it changes what
the user sees arriving.
- SEO - the head is shared plumbing; title/canonical/robots concerns
belong elsewhere (the status-freeze soft-404 note in static-vs-ssr.md is a
first-load fact, not SEO advice).
- Backend latency beyond the TTFB-vs-skeleton-vs-blank boundary
trade-off - making the query faster is not this skill.
If the ask is one of those, say so and stop rather than stretching a loading
fix to fit.
The core loop: symptom -> cause -> fix -> proof
You almost always arrive with a symptom the user saw (or a screenshot), not
a metric. So the spine is diagnostic. For each symptom: name the cause, apply
the fix, then prove it cold-cache - an unverified perf fix is a guess.
First ask: how does the HTML reach the browser - fixed at build (static
prerender), rendered per request (SSR), or streamed (a shell, then
flushes)? - per route, not per site (per hole under Cache Components). The
axis picks how you verify and which build-time gates exist; whether a symptom
class is possible hinges on client-reconciliation JS, not on the axis ->
references/static-vs-ssr.md. Then:
- Identify the symptom ->
references/symptoms.md (decision tree). START HERE.
- Apply the matching fix ->
references/{critical-css,fonts,hosted-fonts,images,resource-hints}.md.
- Prove it ->
references/verify.md. This step is not optional. Half the value
of a loading fix is the repeatable check that it actually landed.
Arriving with only a score. A PSI or Lighthouse complaint ("performance is
62") names no symptom, so it cannot enter the tree at step 1. Convert it first:
references/verify.md section 0 pulls the two halves of a PSI page apart (a
trailing 28-day CrUX field p75 and one throttled lab run), answers the
fixed-it-but-unchanged case, and maps each lab insight audit onto a symptom
class so the arrival rejoins the loop above.
Tag each fix by the Web Vital it moves as a secondary index - useful when
the ask does arrive as "improve CLS", and for knowing which fixes trade
against each other. Which reference owns the lever:
| Vital (LCP by phase) |
Symptom leaves |
Owning references |
| LCP - discovery: bytes requested late |
C2 |
images.md, resource-hints.md |
| LCP - priority: requested, queued behind other work |
C2 |
resource-hints.md, images.md |
| LCP - render: bytes arrived, paint gated |
C1, C3, B7 |
critical-css.md, fonts.md (text LCP), symptoms.md B7, static-vs-ssr.md (fallback box) |
| CLS |
A1-A5 |
fonts.md (metric fallbacks), images.md (reservation), static-vs-ssr.md (fallback dimensions) |
| FCP, and the TTFB it inherits |
C1, B7 |
critical-css.md, static-vs-ssr.md (boundary placement) |
| INP - on arrival only |
A4, B6 |
symptoms.md; steady-state INP is out of scope |
The phase split on LCP is this skill's routing, not a metric definition: ask
whether the resource was requested late, served late, or painted late,
and the row names the file.
Triage root question: does layout MOVE, does only APPEARANCE change, or
does NOTHING appear yet? That routes the whole diagnosis - the full tree,
causes and fixes live in references/symptoms.md.
Sanity checks the obvious fix tends to miss
- Cache-hit-before-hydration: a fix that needs attached JS (an
onLoad
fade, a JS-decoded placeholder) fails when a cached image wins the race
against hydration - and with no-JS. Prefer CSS + eager loading; check
degradation (prefers-reduced-motion, Slow-3G) before calling it done.
- Reveal-gating: if everything fades in, ask what actually paints first.
CSS that ships content at
opacity: 0 until client JS reveals it turns
first paint into a JS race, never paints with no-JS, and delays LCP
(opacity-0 content is excluded from the metric). The entrance must be an
enhancement, not the delivery mechanism (symptoms.md B7). But un-gating the
reveal can make LCP fire without moving the score - the paint may still be
bound elsewhere (render-blocking CSS); A/B the vital before shipping a change
that costs something, e.g. a fidelity deviation (verify.md 5a).
- Edge/privacy leak: where the response is assembled per request, pre-auth
anonymity limits what you may preload or inline - 103 Early Hints can replay
cached preload URLs ahead of an auth check (resource-hints.md). Collapses
under full static prerender.
- Bytes vs blocking vs main-thread: "make it smaller" (subset/compress),
"make it not block" (preload/inline/reorder) and "get it off the main
thread" (defer/split) are different fixes; name which one you are applying.
- Measure before promising a ratio: the "300KB -> 20KB" subsetting win
assumes an unsubset source; an already-subset file has most of it banked.
Measure the real artifact first (fonts.md).
- Trade-off regression: eager/preload/fetchpriority are zero-sum on
bandwidth - after a fix, re-check the vital you might have regressed
(verify.md).
Boundaries and cross-references
- Where Next automates the row, defer to
next/font/next/image output - but
still inspect app code wrapping it (fade wrappers, raw font-family
re-declarations); see references/framework-automation.md.
- Embedded / host-owned surfaces (MCP widgets, Devvit iframes, CSP-forced
single-file): the host owns
<head>/headers, so hint/cache levers don't
apply - see the boundary note in references/static-vs-ssr.md.
- Animation design (easing, entrance curves, reduced-motion gating, stagger)
is the
web-animation-design skill; this skill owns the loading/decode
timing that determines whether there is a real image to animate - and
whether a reveal animation is allowed to gate first paint at all
(symptoms.md B7).
will-change, transitions, tabular-nums, text-wrap live in the
make-interfaces-feel-better skill.
- On React stacks, the DOM resource-hint APIs (
preload, preconnect,
prefetchDNS) are tabulated in the vercel-react-best-practices skill;
this skill adds the framework-agnostic why (crossOrigin/CORS, exact-file
matching, ordering/priority). The wider border with that skill: it owns
steady-state React performance (re-render work, request waterfalls, bundle
size); this skill owns what the user sees on a route's first paint,
including streamed reveals and hydration-timing flashes.
References
Diagnose:
references/symptoms.md - the diagnostic decision tree (spine). START HERE.
Fix:
references/critical-css.md - the "inline critical CSS, defer the rest" fix
B3 and C1 both prescribe: when a render-blocking stylesheet is really the
cause, beasties as the default post-build pass (and what used-CSS extraction
does not know about the fold), framework-native inlining and hand-inlining
as the escape hatches, nothing on streamed HTML, the print-media async
pattern, and blocking="render" as the deliberate inverse.
references/fonts.md - self-hosted font loading: per-weight preload,
crossOrigin (attribute and the ACAO response header), exact-file (?url)
matching, metric-matched fallbacks, font-display, variable fonts,
subsetting (incl. CJK unicode-range chunking).
references/hosted-fonts.md - fonts from a hosted CDN (Google Fonts):
preconnect pair, display= param, @import chains, why gstatic woff2 can't
be hand-preloaded, migrate-to-self-host; Adobe Fonts (Typekit): JS kit vs
CSS embed, dashboard-only font-display, the three-preconnect set,
self-host-not-licensed; Cloudflare Fonts' edge rewrite; CSP
font-src/style-src blocks as permanent fallback.
references/images.md - eager/lazy, decode timing, priority/discovery, CLS
reservation, responsive srcset/<picture>, Astro anti-patterns, GIF->video,
LQIP, content-visibility.
references/resource-hints.md - preload/preconnect ordering & priority,
crossOrigin, exact-file matching, budget, 103 Early Hints, repeat-view cache
headers + bfcache (with stack-specific subsections for
Vite/Cloudflare/TanStack).
Decide where:
references/static-vs-ssr.md - the fixed-at-build vs rendered-per-request vs
streamed axis (per route; per hole under Cache Components): which verify tier
applies, the streamed mode's commitment points (shell vs flush, fallback
dimensions, boundary placement, status freeze, how to tell a route streams),
per-route hybrids, embedded surfaces.
references/framework-automation.md - what a framework's font/image layer
automates <-> the hand-rolled equivalent, and the wrapping-code carve-out.
references/next.md - where each generic fix lands on a Next route
(next/font, metadata vs viewport, next/script, route CSS, next/image,
dynamic with ssr: false) plus the Next-only PPR/streamed-route probes.
references/tanstack.md - TanStack Router/Start: no font/image layer, so
the hand-wired half applies in full; head()/scripts() routing and its
dedupe/order footguns, pending-state (pendingMs/pendingMinMs) jank,
what a deep link to an ssr: false route ships, defaultPreload as the
next route's lever, Early Hints, CSS discovery by import style.
Prove:
references/verify.md - how to prove a fix cold-cache - and section 0, the
entry point for a score-only (PSI/Lighthouse) arrival: Tier 0 asserts on the
static dist/*.html bytes; Tier 1 boots the route for SSR - with 3c scoping
the same invariants to a streamed route's shell and probing the flush
timeline; shared CLS probe, filmstrip/visual metrics for defects that move no
vital (4e), measurement-tool gotchas, and a local Lighthouse A/B across the
change (5a). The lens no other loading skill carries.
Templates (read-as-reference, brand-agnostic - adapt per project):
scripts/check-dist.mjs - Tier-0 build-output guard: preload budget range,
crossorigin, preload<->@font-face href match, non-blocking font-display on
fetched faces, no stylesheet link, metric fallback presence, subset byte
ceilings, scoped glyph coverage, immutable + public-font cache headers
(block-scoped). Wire into CI after the build.
scripts/check-head.mjs - Tier-1 booted-route guard: fetch a route (or pipe
HTML in) and assert the same head invariants on rendered bytes.
scripts/check-stream.mjs - Tier-1 streamed-route guard: read a booted
route's body flush by flush, assert the head invariants on the shell only,
print the flush timeline with React's boundary/swap markers, and flag a head
split across flushes (verify.md 3c).
scripts/font-subset.config.mjs - the single shared coverage module the subset
generator and check-dist.mjs both import, so the shipped woff2 and the
assertion can't drift.
scripts/lh-ab.mjs - local Lighthouse A/B between two git refs (build ->
serve dist -> median-of-N -> delta): prove a costly fix moves the targeted
vital before shipping, no deploy. Corroboration / decision aid, not a gate
(verify.md 5a).
Maintenance (skill authors, not users of the skill):
scripts/check-currency.mjs (EXECUTE) + scripts/currency-claims.json - the
registry of this skill's version-dated claims and how to re-check each one.
Run at revision time (needs network, never a gate); a webstatus entry that
gained a browser since its verified date means re-verify that claim in its
file.
evals/ holds the behaviour eval set (see writing-skills); it is intentionally
not routed from the workflow above.
1---2name: web-perf3description: Diagnoses and fixes what a user sees on a route's first load - font-swap flashes, image decode pop-in, layout shift (CLS), slow LCP/FCP, blank-then-paint, hydration/theme flips, streamed-SSR skeletons that flash or pop, Lighthouse/PageSpeed complaints - on static (Astro/SSG), Vite SPA, SSR, or streamed routes (Next.js App Router/PPR, TanStack Start); a soft navigation is that route's first load. Use on "flash", "shimmer", "pop", "jump", "flicker", "skeleton", or slow first paint; when the LCP is webfont text or content fades in after JS; for font loading (self-hosted, Google Fonts, Adobe Fonts, next/font), image loading (next/image), Suspense boundary placement, skeleton swaps, subsetting fixed copy, resource hints, metric fallbacks; or to assert first-load invariants on built HTML, a booted route, or a streamed shell. Not for bundle-size analysis, steady-state INP, SEO, or backend latency past the TTFB-vs-skeleton trade-off; where a framework automates the fix, defer to its output but inspect wrapping code.4---5
6# Web Performance: the first load of a route
7
8Diagnose and fix what a user *sees* on a route's first load. Two territories:
9
10- **Hand-wired loading** - `@font-face` written by hand, fonts pulled from a
11 hosted CDN (Google Fonts), native `<img>`, resource hints in your own
12 document head. No framework layer is doing it for you, so you must - and
13 must verify it yourself.
14- **The rendering path itself** - streamed shells, Suspense fallbacks and the
15 skeleton-to-content swap, hydration timing, router-level pending states.
16 Here the framework's rendering model *is* the mechanism, and the lever is
17 where you place boundaries and what the fallback reserves.
18
19Where a framework *does* automate the fix (Next's `next/font`/`next/image`,
20Astro's Fonts API), defer to that layer's own output - but app code
21*wrapping* it stays in scope (`references/framework-automation.md`).
22
23## Out of scope
24
25This skill owns **the first-load experience of a route** - everything the user
26sees between requesting a route and its settled first view, whoever produced
27the HTML. A soft navigation counts as the next route's first load, and a
28Suspense-boundary placement counts even though it shapes TTFB. Still not here:
29
30- **Bundle-size analysis** (tree-shaking, chunk budgets, dependency audits).
31 Naming a large hydration bundle as a C1 *cause* is in scope; auditing and
32 shrinking it is not.
33- **Steady-state interaction (INP) tuning** - interaction latency after the
34 route has settled. Hydration timing is in scope only where it changes what
35 the user sees arriving.
36- **SEO** - the head is shared plumbing; title/canonical/robots concerns
37 belong elsewhere (the status-freeze soft-404 note in static-vs-ssr.md is a
38 first-load fact, not SEO advice).
39- **Backend latency** beyond the TTFB-vs-skeleton-vs-blank boundary
40 trade-off - making the query faster is not this skill.
41
42If the ask is one of those, say so and stop rather than stretching a loading
43fix to fit.
44
45## The core loop: symptom -> cause -> fix -> proof
46
47You almost always arrive with a **symptom the user saw** (or a screenshot), not
48a metric. So the spine is diagnostic. For each symptom: name the cause, apply
49the fix, then **prove it cold-cache** - an unverified perf fix is a guess.
50
51First ask: **how does the HTML reach the browser - fixed at build (static
52prerender), rendered per request (SSR), or streamed (a shell, then
53flushes)?** - per route, not per site (per *hole* under Cache Components). The
54axis picks how you *verify* and which build-time gates exist; whether a symptom
55class is *possible* hinges on client-reconciliation JS, not on the axis ->
56`references/static-vs-ssr.md`. Then:
57
581. Identify the symptom -> `references/symptoms.md` (decision tree). START HERE.
592. Apply the matching fix ->
60 `references/{critical-css,fonts,hosted-fonts,images,resource-hints}.md`.
613. Prove it -> `references/verify.md`. This step is not optional. Half the value
62 of a loading fix is the repeatable check that it actually landed.
63
64**Arriving with only a score.** A PSI or Lighthouse complaint ("performance is
6562") names no symptom, so it cannot enter the tree at step 1. Convert it first:
66`references/verify.md` section 0 pulls the two halves of a PSI page apart (a
67trailing 28-day CrUX field p75 and one throttled lab run), answers the
68fixed-it-but-unchanged case, and maps each lab insight audit onto a symptom
69class so the arrival rejoins the loop above.
70
71Tag each fix by the **Web Vital it moves** as a secondary index - useful when
72the ask does arrive as "improve CLS", and for knowing which fixes trade
73against each other. Which reference owns the lever:
74
75| Vital (LCP by phase) | Symptom leaves | Owning references |
76| --- | --- | --- |
77| LCP - discovery: bytes requested late | C2 | `images.md`, `resource-hints.md` |
78| LCP - priority: requested, queued behind other work | C2 | `resource-hints.md`, `images.md` |
79| LCP - render: bytes arrived, paint gated | C1, C3, B7 | `critical-css.md`, `fonts.md` (text LCP), `symptoms.md` B7, `static-vs-ssr.md` (fallback box) |
80| CLS | A1-A5 | `fonts.md` (metric fallbacks), `images.md` (reservation), `static-vs-ssr.md` (fallback dimensions) |
81| FCP, and the TTFB it inherits | C1, B7 | `critical-css.md`, `static-vs-ssr.md` (boundary placement) |
82| INP - on arrival only | A4, B6 | `symptoms.md`; steady-state INP is out of scope |
83
84The phase split on LCP is this skill's routing, not a metric definition: ask
85whether the resource was *requested* late, *served* late, or *painted* late,
86and the row names the file.
87
88**Triage root question**: does layout MOVE, does only APPEARANCE change, or
89does NOTHING appear yet? That routes the whole diagnosis - the full tree,
90causes and fixes live in `references/symptoms.md`.
91
92## Sanity checks the obvious fix tends to miss
93
94- **Cache-hit-before-hydration**: a fix that needs attached JS (an `onLoad`
95 fade, a JS-decoded placeholder) fails when a cached image wins the race
96 against hydration - and with no-JS. Prefer CSS + eager loading; check
97 degradation (`prefers-reduced-motion`, Slow-3G) before calling it done.
98- **Reveal-gating**: if everything fades in, ask what actually paints first.
99 CSS that ships content at `opacity: 0` until client JS reveals it turns
100 first paint into a JS race, never paints with no-JS, and delays LCP
101 (opacity-0 content is excluded from the metric). The entrance must be an
102 enhancement, not the delivery mechanism (symptoms.md B7). But un-gating the
103 reveal can make LCP *fire* without moving the score - the paint may still be
104 bound elsewhere (render-blocking CSS); A/B the vital before shipping a change
105 that costs something, e.g. a fidelity deviation (verify.md 5a).
106- **Edge/privacy leak**: where the response is assembled per request, pre-auth
107 anonymity limits what you may preload or inline - 103 Early Hints can replay
108 cached preload URLs ahead of an auth check (resource-hints.md). Collapses
109 under full static prerender.
110- **Bytes vs blocking vs main-thread**: "make it smaller" (subset/compress),
111 "make it not block" (preload/inline/reorder) and "get it off the main
112 thread" (defer/split) are different fixes; name which one you are applying.
113- **Measure before promising a ratio**: the "300KB -> 20KB" subsetting win
114 assumes an unsubset source; an already-subset file has most of it banked.
115 Measure the real artifact first (fonts.md).
116- **Trade-off regression**: eager/preload/fetchpriority are zero-sum on
117 bandwidth - after a fix, re-check the vital you might have regressed
118 (verify.md).
119
120## Boundaries and cross-references
121
122- Where Next automates the row, defer to `next/font`/`next/image` output - but
123 still inspect app code wrapping it (fade wrappers, raw `font-family`
124 re-declarations); see `references/framework-automation.md`.
125- Embedded / host-owned surfaces (MCP widgets, Devvit iframes, CSP-forced
126 single-file): the host owns `<head>`/headers, so hint/cache levers don't
127 apply - see the boundary note in `references/static-vs-ssr.md`.
128- Animation design (easing, entrance curves, reduced-motion gating, stagger)
129 is the `web-animation-design` skill; this skill owns the loading/decode
130 timing that determines whether there is a real image to animate - and
131 whether a reveal animation is allowed to gate first paint at all
132 (symptoms.md B7).
133- `will-change`, transitions, tabular-nums, text-wrap live in the
134 `make-interfaces-feel-better` skill.
135- On React stacks, the DOM resource-hint APIs (`preload`, `preconnect`,
136 `prefetchDNS`) are tabulated in the `vercel-react-best-practices` skill;
137 this skill adds the framework-agnostic *why* (crossOrigin/CORS, exact-file
138 matching, ordering/priority). The wider border with that skill: it owns
139 steady-state React performance (re-render work, request waterfalls, bundle
140 size); this skill owns what the user sees on a route's first paint,
141 including streamed reveals and hydration-timing flashes.
142
143## References
144
145**Diagnose:**
146
147- `references/symptoms.md` - the diagnostic decision tree (spine). START HERE.
148
149**Fix:**
150
151- `references/critical-css.md` - the "inline critical CSS, defer the rest" fix
152 B3 and C1 both prescribe: when a render-blocking stylesheet is really the
153 cause, beasties as the default post-build pass (and what used-CSS extraction
154 does *not* know about the fold), framework-native inlining and hand-inlining
155 as the escape hatches, nothing on streamed HTML, the print-media async
156 pattern, and `blocking="render"` as the deliberate inverse.
157- `references/fonts.md` - self-hosted font loading: per-weight preload,
158 crossOrigin (attribute and the ACAO response header), exact-file (`?url`)
159 matching, metric-matched fallbacks, `font-display`, variable fonts,
160 subsetting (incl. CJK unicode-range chunking).
161- `references/hosted-fonts.md` - fonts from a hosted CDN (Google Fonts):
162 preconnect pair, `display=` param, `@import` chains, why gstatic woff2 can't
163 be hand-preloaded, migrate-to-self-host; Adobe Fonts (Typekit): JS kit vs
164 CSS embed, dashboard-only `font-display`, the three-preconnect set,
165 self-host-not-licensed; Cloudflare Fonts' edge rewrite; CSP
166 `font-src`/`style-src` blocks as permanent fallback.
167- `references/images.md` - eager/lazy, decode timing, priority/discovery, CLS
168 reservation, responsive `srcset`/`<picture>`, Astro anti-patterns, GIF->video,
169 LQIP, content-visibility.
170- `references/resource-hints.md` - preload/preconnect ordering & priority,
171 crossOrigin, exact-file matching, budget, 103 Early Hints, repeat-view cache
172 headers + bfcache (with stack-specific subsections for
173 Vite/Cloudflare/TanStack).
174
175**Decide where:**
176
177- `references/static-vs-ssr.md` - the fixed-at-build vs rendered-per-request vs
178 streamed axis (per route; per hole under Cache Components): which verify tier
179 applies, the streamed mode's commitment points (shell vs flush, fallback
180 dimensions, boundary placement, status freeze, how to tell a route streams),
181 per-route hybrids, embedded surfaces.
182- `references/framework-automation.md` - what a framework's font/image layer
183 automates <-> the hand-rolled equivalent, and the wrapping-code carve-out.
184- `references/next.md` - where each generic fix lands on a Next route
185 (next/font, metadata vs viewport, next/script, route CSS, next/image,
186 `dynamic` with `ssr: false`) plus the Next-only PPR/streamed-route probes.
187- `references/tanstack.md` - TanStack Router/Start: no font/image layer, so
188 the hand-wired half applies in full; `head()`/`scripts()` routing and its
189 dedupe/order footguns, pending-state (`pendingMs`/`pendingMinMs`) jank,
190 what a deep link to an `ssr: false` route ships, `defaultPreload` as the
191 next route's lever, Early Hints, CSS discovery by import style.
192
193**Prove:**
194
195- `references/verify.md` - how to prove a fix cold-cache - and section 0, the
196 entry point for a score-only (PSI/Lighthouse) arrival: Tier 0 asserts on the
197 static `dist/*.html` bytes; Tier 1 boots the route for SSR - with 3c scoping
198 the same invariants to a streamed route's shell and probing the flush
199 timeline; shared CLS probe, filmstrip/visual metrics for defects that move no
200 vital (4e), measurement-tool gotchas, and a local Lighthouse A/B across the
201 change (5a). The lens no other loading skill carries.
202
203**Templates (read-as-reference, brand-agnostic - adapt per project):**
204
205- `scripts/check-dist.mjs` - Tier-0 build-output guard: preload budget range,
206 crossorigin, preload<->@font-face href match, non-blocking font-display on
207 fetched faces, no stylesheet link, metric fallback presence, subset byte
208 ceilings, scoped glyph coverage, immutable + public-font cache headers
209 (block-scoped). Wire into CI after the build.
210- `scripts/check-head.mjs` - Tier-1 booted-route guard: fetch a route (or pipe
211 HTML in) and assert the same head invariants on rendered bytes.
212- `scripts/check-stream.mjs` - Tier-1 streamed-route guard: read a booted
213 route's body flush by flush, assert the head invariants on the shell only,
214 print the flush timeline with React's boundary/swap markers, and flag a head
215 split across flushes (verify.md 3c).
216- `scripts/font-subset.config.mjs` - the single shared coverage module the subset
217 generator and `check-dist.mjs` both import, so the shipped woff2 and the
218 assertion can't drift.
219- `scripts/lh-ab.mjs` - local Lighthouse A/B between two git refs (build ->
220 serve `dist` -> median-of-N -> delta): prove a *costly* fix moves the targeted
221 vital before shipping, no deploy. Corroboration / decision aid, not a gate
222 (verify.md 5a).
223
224**Maintenance (skill authors, not users of the skill):**
225
226- `scripts/check-currency.mjs` (EXECUTE) + `scripts/currency-claims.json` - the
227 registry of this skill's version-dated claims and how to re-check each one.
228 Run at revision time (needs network, never a gate); a webstatus entry that
229 gained a browser since its `verified` date means re-verify that claim in its
230 file.
231
232`evals/` holds the behaviour eval set (see writing-skills); it is intentionally
233not routed from the workflow above.