Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
26a8851
feat: add webperf-snippets CLI (MVP)
nucliweb Apr 29, 2026
6421eef
refactor: remove getEntriesByType shim now that snippets use buffered…
nucliweb Apr 29, 2026
b74874e
feat(cli): add viewport presets with mobile as default
nucliweb Apr 29, 2026
2484f2e
test(cli): add Vitest suite with unit and E2E tests
nucliweb Apr 29, 2026
0d2890d
ci: add CLI unit and E2E tests to CI pipeline
nucliweb Apr 29, 2026
e67984b
ci: remove redundant cli npm ci step — cli is a workspace
nucliweb Apr 29, 2026
2673310
ci: add npm publish workflow and document release process
nucliweb Apr 29, 2026
fdb45d2
feat(cli): add fonts snippet with alias and detailed terminal output
nucliweb Apr 30, 2026
7a5aa42
refactor(cli): rename LCP-Sub-Parts to LCP-Subparts to match main
nucliweb Apr 30, 2026
2356cac
Add font as snippet in the help documentation
nucliweb May 1, 2026
b68a51f
feat(cli): add audit workflow with deterministic structural checks
nucliweb May 4, 2026
d81bb7b
feat(cli): add --verbose flag, fix critical-css items display, add CL…
nucliweb May 4, 2026
23f87e2
chore: sync schema docs, skill references, MDX pages and snippets
nucliweb May 4, 2026
2371ecf
fix: ci consistency and npm publish configuration
nucliweb May 4, 2026
0efcd89
ci: require manual approval before npm publish
nucliweb May 4, 2026
ff3519d
docs(cli): add viewport, verbose, workflow options and snippet aliase…
nucliweb May 4, 2026
d694381
docs(cli): add hero image
nucliweb May 4, 2026
7824832
docs: apply webperf-docs-reviewer rules to Find-render-blocking-resou…
nucliweb May 4, 2026
a550503
docs(cli): update title, fix em-dashes, align tables
nucliweb May 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 92 additions & 21 deletions .claude/skills/webperf-core-web-vitals/references/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,15 @@ Scripts that read DOM or `performance.getEntriesByType()` directly. Return JSON
console.log(`TTFB: ${value}ms (${rating})`);

// Agent output
return { script: "TTFB", status: "ok", metric: "TTFB", value, unit: "ms", rating,
thresholds: { good: 800, needsImprovement: 1800 } };
return {
script: "TTFB",
status: "ok",
metric: "TTFB",
value,
unit: "ms",
rating,
thresholds: { good: 800, needsImprovement: 1800 },
};
})();
```

Expand Down Expand Up @@ -137,11 +144,12 @@ return {
script: "INP",
status: "tracking",
message: "INP tracking active. Interact with the page then call getINP() for results.",
getDataFn: "getINP"
getDataFn: "getINP",
};
```

**Agent workflow for tracking scripts:**

```
1. evaluate_script(INP.js) → { status: "tracking", getDataFn: "getINP" }
2. (user interacts with the page)
Expand All @@ -167,6 +175,7 @@ Keep the existing `async () => {}` wrapper. Add a `return` statement with struct
### Core Web Vitals

#### LCP

```json
{
"script": "LCP",
Expand All @@ -179,14 +188,16 @@ Keep the existing `async () => {}` wrapper. Add a `return` statement with struct
"details": {
"element": "img.hero",
"elementType": "Image",
"url": "https://example.com/hero.jpg",
"url": "https://web.dev/hero.jpg",
"sizePixels": 756000
}
}
```

#### CLS

Returns buffered CLS immediately and keeps tracking. Always call `getCLS()` after interactions to get an updated value.

```json
{
"script": "CLS",
Expand All @@ -200,9 +211,11 @@ Returns buffered CLS immediately and keeps tracking. Always call `getCLS()` afte
"getDataFn": "getCLS"
}
```

`getCLS()` returns the same shape with the latest accumulated value.

#### INP (tracking)

```json
{
"script": "INP",
Expand All @@ -211,7 +224,9 @@ Returns buffered CLS immediately and keeps tracking. Always call `getCLS()` afte
"getDataFn": "getINP"
}
```

`getINP()` returns:

```json
{
"script": "INP",
Expand All @@ -228,17 +243,24 @@ Returns buffered CLS immediately and keeps tracking. Always call `getCLS()` afte
}
}
```

If no interactions yet, `getINP()` returns `status: "error"` with `getDataFn: "getINP"` — retry after user interaction.

`getINPDetails()` returns the full sorted interaction list (array of up to 15 entries). Use when `getINP()` shows poor INP and you need to identify patterns across multiple slow interactions:

```json
[
{ "formattedName": "click → button.submit", "duration": 450, "startTime": 1200,
"phases": { "inputDelay": 120, "processingTime": 280, "presentationDelay": 50 } }
{
"formattedName": "click → button.submit",
"duration": 450,
"startTime": 1200,
"phases": { "inputDelay": 120, "processingTime": 280, "presentationDelay": 50 }
}
]
```

#### LCP-Subparts

```json
{
"script": "LCP-Subparts",
Expand All @@ -263,6 +285,7 @@ If no interactions yet, `getINP()` returns `status: "error"` with `getDataFn: "g
```

#### LCP-Trail

```json
{
"script": "LCP-Trail",
Expand All @@ -277,13 +300,20 @@ If no interactions yet, `getINP()` returns `status: "error"` with `getDataFn: "g
"finalElement": "img.hero",
"candidates": [
{ "index": 1, "selector": "h1", "time": 800, "elementType": "Text block" },
{ "index": 2, "selector": "img.hero", "time": 1240, "elementType": "Image", "url": "hero.jpg" }
{
"index": 2,
"selector": "img.hero",
"time": 1240,
"elementType": "Image",
"url": "hero.jpg"
}
]
}
}
```

#### LCP-Image-Entropy

```json
{
"script": "LCP-Image-Entropy",
Expand All @@ -300,13 +330,23 @@ If no interactions yet, `getINP()` returns `status: "error"` with `getDataFn: "g
}
},
"items": [
{ "url": "hero.jpg", "width": 1200, "height": 630, "fileSizeBytes": 156000, "bpp": 1.65, "isLowEntropy": false, "lcpEligible": true, "isLCP": true }
{
"url": "hero.jpg",
"width": 1200,
"height": 630,
"fileSizeBytes": 156000,
"bpp": 1.65,
"isLowEntropy": false,
"lcpEligible": true,
"isLCP": true
}
],
"issues": []
}
```

#### LCP-Video-Candidate

```json
{
"script": "LCP-Video-Candidate",
Expand All @@ -318,7 +358,7 @@ If no interactions yet, `getINP()` returns `status: "error"` with `getDataFn: "g
"thresholds": { "good": 2500, "needsImprovement": 4000 },
"details": {
"isVideo": true,
"posterUrl": "https://example.com/hero.avif",
"posterUrl": "https://web.dev/hero.avif",
"posterFormat": "avif",
"posterPreloaded": true,
"fetchpriorityOnPreload": "high",
Expand All @@ -332,6 +372,7 @@ If no interactions yet, `getINP()` returns `status: "error"` with `getDataFn: "g
### Loading

#### TTFB

```json
{
"script": "TTFB",
Expand All @@ -345,6 +386,7 @@ If no interactions yet, `getINP()` returns `status: "error"` with `getDataFn: "g
```

#### TTFB-Sub-Parts

```json
{
"script": "TTFB-Sub-Parts",
Expand All @@ -369,6 +411,7 @@ If no interactions yet, `getINP()` returns `status: "error"` with `getDataFn: "g
```

#### Find-render-blocking-resources

```json
{
"script": "Find-render-blocking-resources",
Expand All @@ -380,12 +423,20 @@ If no interactions yet, `getINP()` returns `status: "error"` with `getDataFn: "g
"byType": { "link": 2, "script": 1 }
},
"items": [
{ "type": "link", "url": "https://example.com/style.css", "shortName": "style.css", "responseEndMs": 450, "durationMs": 200, "sizeBytes": 45000 }
{
"type": "link",
"url": "https://web.dev/style.css",
"shortName": "style.css",
"responseEndMs": 450,
"durationMs": 200,
"sizeBytes": 45000
}
]
}
```

#### Script-Loading

```json
{
"script": "Script-Loading",
Expand All @@ -399,7 +450,15 @@ If no interactions yet, `getINP()` returns `status: "error"` with `getDataFn: "g
"thirdPartyBlockingCount": 1
},
"items": [
{ "url": "https://example.com/app.js", "shortName": "app.js", "strategy": "blocking", "location": "head", "party": "first", "sizeBytes": 85000, "durationMs": 120 }
{
"url": "https://web.dev/app.js",
"shortName": "app.js",
"strategy": "blocking",
"location": "head",
"party": "first",
"sizeBytes": 85000,
"durationMs": 120
}
],
"issues": [
{ "severity": "error", "message": "2 blocking scripts in <head>" },
Expand All @@ -411,6 +470,7 @@ If no interactions yet, `getINP()` returns `status: "error"` with `getDataFn: "g
### Interaction

#### Interactions (tracking)

```json
{
"script": "Interactions",
Expand All @@ -421,6 +481,7 @@ If no interactions yet, `getINP()` returns `status: "error"` with `getDataFn: "g
```

#### Input-Latency-Breakdown (tracking)

```json
{
"script": "Input-Latency-Breakdown",
Expand All @@ -431,7 +492,9 @@ If no interactions yet, `getINP()` returns `status: "error"` with `getDataFn: "g
```

#### Layout-Shift-Loading-and-Interaction

Immediately returns buffered CLS data, plus exposes summary function for ongoing tracking.

```json
{
"script": "Layout-Shift-Loading-and-Interaction",
Expand All @@ -453,7 +516,9 @@ Immediately returns buffered CLS data, plus exposes summary function for ongoing
```

#### Long-Animation-Frames

Returns buffered LoAF data immediately. Ongoing tracking continues.

```json
{
"script": "Long-Animation-Frames",
Expand All @@ -471,7 +536,9 @@ Returns buffered LoAF data immediately. Ongoing tracking continues.
```

#### Long-Animation-Frames-Script-Attribution

Returns buffered LoAF data immediately (do not wait for a timer):

```json
{
"script": "Long-Animation-Frames-Script-Attribution",
Expand All @@ -485,13 +552,12 @@ Returns buffered LoAF data immediately (do not wait for a timer):
"framework": { "durationMs": 30, "count": 1 }
}
},
"items": [
{ "file": "app.js", "category": "first-party", "durationMs": 180, "count": 3 }
]
"items": [{ "file": "app.js", "category": "first-party", "durationMs": 180, "count": 3 }]
}
```

#### Scroll-Performance (tracking)

```json
{
"script": "Scroll-Performance",
Expand All @@ -510,7 +576,9 @@ Returns buffered LoAF data immediately (do not wait for a timer):
```

#### LongTask

Returns buffered long tasks immediately. Ongoing tracking continues.

```json
{
"script": "LongTask",
Expand All @@ -529,6 +597,7 @@ Returns buffered long tasks immediately. Ongoing tracking continues.
### Media

#### Image-Element-Audit (async)

```json
{
"script": "Image-Element-Audit",
Expand Down Expand Up @@ -567,32 +636,34 @@ Returns buffered long tasks immediately. Ongoing tracking continues.
}
],
"issues": [
{ "severity": "warning", "message": "img.thumbnail: Missing width/height attributes (CLS risk)" }
{
"severity": "warning",
"message": "img.thumbnail: Missing width/height attributes (CLS risk)"
}
]
}
```

#### Video-Element-Audit

Same shape as Image-Element-Audit but for video elements.

#### SVG-Embedded-Bitmap-Analysis

```json
{
"script": "SVG-Embedded-Bitmap-Analysis",
"status": "ok",
"count": 2,
"items": [
{ "url": "icon.svg", "hasBitmap": true, "bitmapType": "image/png", "sizeBytes": 4500 }
],
"issues": [
{ "severity": "warning", "message": "2 SVG files contain embedded bitmaps" }
]
"items": [{ "url": "icon.svg", "hasBitmap": true, "bitmapType": "image/png", "sizeBytes": 4500 }],
"issues": [{ "severity": "warning", "message": "2 SVG files contain embedded bitmaps" }]
}
```

### Resources

#### Network-Bandwidth-Connection-Quality

```json
{
"script": "Network-Bandwidth-Connection-Quality",
Expand Down
Loading
Loading