# Pathrule Pattern: Core Web Vitals (1.0.0)
# ::pathrule:package:core-web-vitals

### [RULE] Give the LCP element priority and remove what blocks it  (path: /src/app)
<!-- scope: folder | priority: high | strict -->

LCP is a race between the browser discovering the largest element and everything else you asked it to do first. Most bad LCP is self-inflicted ordering.

- Identify the LCP element (usually a hero image, sometimes a heading or video poster) and load it eagerly with a high fetch priority. Never `loading="lazy"` above the fold, and never behind a client-side component that must hydrate before the image is requested.
- Serve it at the size it renders, in a modern format, with explicit dimensions, and with a responsive `srcset`. An oversized hero is the single most common LCP regression.
- Preconnect to the origins the critical path needs (image CDN, font host) and preload the fonts used above the fold. Self-host fonts where you can, use `font-display: swap`, and subset them.
- Keep render-blocking work out of the head: inline the critical CSS, defer the rest, and load non-essential scripts with `defer` or after interaction. A third-party tag manager in the head delays every metric.
- Server-render the above-the-fold content. If the first meaningful paint waits for a client fetch, LCP includes that round trip, and no image optimisation will save it.

---

### [RULE] Keep the main thread free so interactions stay under 200ms  (path: /src)
<!-- scope: folder | priority: high | strict -->

INP measures the worst realistic interaction: from the input to the next paint. Anything occupying the main thread at that moment is added to the number.

- Do the minimum inside the handler: update the state that renders feedback, yield to the browser, then do the expensive part. Breaking a long task into chunks (yielding with `scheduler.yield` where available, or a task boundary) turns one blocking second into responsive frames.
- Move genuinely heavy computation off the main thread (a web worker) or to the server. Parsing a large payload, sorting thousands of rows, or running a diff in a click handler is what makes an app feel broken.
- Ship less JavaScript: split by route, load below-the-fold and rarely used components on demand, and audit the bundle for a dependency that costs more than the feature. Hydration cost is proportional to what you sent.
- Keep third-party scripts off the critical path and out of the interaction path. Load them after interaction or in a worker-based sandbox, and measure their main-thread time before accepting them.
- Prefer CSS transitions and transforms over JavaScript animation, and avoid layout thrashing (reading a layout property, writing, then reading again inside a loop).

---

### [MEMORY] Layout shift comes from unreserved space  (path: /src)

CLS is not a mystery metric. It is the sum of content moving after the user could already see it, and each source has a mechanical fix.

- Set explicit `width` and `height` (or `aspect-ratio`) on every image, video, iframe, and embed so the box exists before the resource arrives.
- Reserve space for anything that loads late: ads, consent banners, promotional bars, dynamically injected components. Render a placeholder of the final size rather than letting the element appear and push the page down.
- Never insert content above existing content after paint. If a notification or banner must appear, either reserve its slot in the initial render or overlay it without affecting layout.
- Match fallback and web font metrics (size-adjust and the local fallback descriptors) so the swap does not reflow text, and preload fonts used above the fold.
- Animate with `transform` and `opacity`, which do not trigger layout. Animating `top`, `height`, or `margin` moves everything after the element.

See /src/app for the LCP rule and / for how these are measured.

---

### [MEMORY] Measure the field number, not the lab number  (path: /)

Lab and field metrics answer different questions, and optimising the wrong one is how teams stay red while their scores go green.

- The thresholds are LCP 2.5s, INP 200ms, CLS 0.1, evaluated at the 75th percentile of real visits. That means a quarter of your users can be slower and you still pass, and it also means your fast laptop tells you almost nothing.
- Collect field data continuously: the `web-vitals` library reporting to your own analytics, plus public field data for context. Segment by page template, device class, and country, because one slow template can drag a whole property.
- Use Lighthouse and traces to diagnose a specific regression, not as the target. A lab score can improve while the field regresses (and vice versa) because the lab has no real network, no real CPU throttling, and no real interactions.
- In a single-page app, a route change is not a document load. Instrument soft navigations so the metrics reflect what the user experienced, and remember that a slow client transition never shows up in a naive document-level measurement.
- Treat TTFB as a diagnostic for LCP rather than a goal in itself: it explains where LCP time went (server, redirect chain, cache miss) even though it is not a Core Web Vital.

See /src for the INP rule and /src/app for the LCP rule these numbers grade.

---

### [SKILL] web-vitals-triage  (path: /)

---
name: web-vitals-triage
description: Triage procedure for a failing Core Web Vital. Start from field data, identify the metric and page template, find the specific cause, and fix in order of impact. Use when LCP, INP, or CLS is failing or has regressed.
---

# Web Vitals triage

## 0. Start from the field
1. Which metric fails, on which page template, on which device class, at the 75th percentile?
2. Is it a regression (compare to last week) or a long-standing failure? A regression means bisecting recent deploys.
3. Only then open a lab tool, and reproduce with CPU and network throttling matching the failing segment.

## LCP failing
- [ ] Identify the LCP element in a trace. Is it the element you expected?
- [ ] When is it discovered? Late discovery means it is behind JavaScript, a client fetch, or a lazy attribute.
- [ ] Break down the time: TTFB, resource load delay, resource load time, render delay. Fix the largest slice first.
- [ ] Check size and format against rendered dimensions; check preconnect and font preload; check for render-blocking head resources.

## INP failing
- [ ] Which interaction is worst, and on which element? Field data should name it.
- [ ] Record a performance profile of that interaction; find the long task and its script.
- [ ] Split the handler: paint feedback first, yield, then do the work; move computation to a worker or the server.
- [ ] Audit third-party scripts and hydration cost on that route.

## CLS failing
- [ ] Find the shifting element in a trace, with its shift score and timing.
- [ ] Missing dimensions, late-loading embed, injected banner, or font swap? Each has its own fix.
- [ ] Verify no animation touches layout properties.

## Close the loop
- [ ] Ship the fix, then confirm in field data (allow for the reporting window) rather than in the lab.
- [ ] Add a budget or check so the same regression fails CI next time.

---

### [SKILL] performance-budget-review  (path: /)

---
name: performance-budget-review
description: Pre-merge performance review for frontend changes: bundle and dependency cost, image and font handling, third-party additions, and layout stability. Run on any change that adds JavaScript, images, or third-party scripts.
---

# Performance budget review

## JavaScript
- [ ] Bundle size delta is known and justified; no dependency added for a one-line utility.
- [ ] New code is route-split; below-the-fold and rarely used components load on demand.
- [ ] No new synchronous work in an interaction path; long tasks are chunked.
- [ ] No new client component doing what the server could render.

## Media and fonts
- [ ] Images have explicit dimensions, a modern format, and a correct responsive set.
- [ ] Above-the-fold images are eager and high priority; below-the-fold are lazy.
- [ ] No new font family or weight without measuring the cost; fonts preloaded and subset.

## Third parties
- [ ] Every added script has an owner, a purpose, and a measured main-thread cost.
- [ ] It loads after interaction or off the critical path, and failure to load degrades gracefully.

## Stability
- [ ] Anything that loads late reserves its space.
- [ ] Nothing is inserted above existing content after paint.
- [ ] Animations use transform and opacity only.

## Gate
- [ ] The CI performance budget (bundle size, Lighthouse assertions) still passes.
- [ ] For a risky change, field metrics are checked after release rather than assumed.
