Skip to content

Commit 4728f69

Browse files
authored
Merge pull request #74 from nucliweb/fix/agent-sync-return-lcp-cls
fix: use buffered observer in agent return path for LCP and CLS snippets
2 parents af5e93b + 00cf403 commit 4728f69

7 files changed

Lines changed: 38 additions & 19 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ A curated collection of JavaScript snippets to measure and debug Web Performance
44

55
[![CI](https://github.com/nucliweb/webperf-snippets/actions/workflows/ci.yml/badge.svg)](https://github.com/nucliweb/webperf-snippets/actions/workflows/ci.yml)
66
[![Release](https://img.shields.io/github/v/release/nucliweb/webperf-snippets)](https://github.com/nucliweb/webperf-snippets/releases)
7-
[![Snippets](https://img.shields.io/badge/snippets-48-0f766e)](https://webperf-snippets.nucliweb.net)
7+
[![Snippets](https://img.shields.io/badge/snippets-49-0f766e)](https://webperf-snippets.nucliweb.net)
88
[![License](https://img.shields.io/github/license/nucliweb/webperf-snippets)](./LICENSE)
99
[![Star History](https://img.shields.io/github/stars/nucliweb/webperf-snippets?style=social)](https://star-history.com/#nucliweb/webperf-snippets&Date)
1010

@@ -87,7 +87,7 @@ This installs skills to `~/.claude/skills/` for use across any project.
8787

8888
| Skill | Snippets | Description |
8989
| ------------------------- | -------- | ---------------------------------------------------------------- |
90-
| `webperf` | 48 | Main entry point for all web performance analysis |
90+
| `webperf` | 49 | Main entry point for all web performance analysis |
9191
| `webperf-core-web-vitals` | 7 | LCP, CLS, INP measurements with detailed breakdowns |
9292
| `webperf-loading` | 29 | TTFB, FCP, script/font analysis, resource hints, service workers |
9393
| `webperf-interaction` | 8 | Long tasks, animation frames, scroll jank, INP debugging |

SKILLS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ A collection of [Agent Skills](https://agentskills.io/) for measuring and debugg
66

77
## Why WebPerf Skills?
88

9-
These skills transform 48 battle-tested JavaScript snippets into agent capabilities for any skills-compatible AI coding assistant:
9+
These skills transform 49 battle-tested JavaScript snippets into agent capabilities for any skills-compatible AI coding assistant:
1010

1111
- **Browser Console Integration**: Run performance measurements directly in Chrome DevTools
1212
- **Real-time Analysis**: Measure actual user experience on live pages
@@ -105,7 +105,7 @@ The main entry point that helps identify the right skill for your performance qu
105105
**What it does:**
106106

107107
- Routes to the appropriate specialized skill
108-
- Provides overview of all 48 available snippets
108+
- Provides overview of all 49 available snippets
109109
- Suggests which skill to use based on your question
110110

111111
### webperf-core-web-vitals

eslint.config.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,10 @@ const browserGlobals = {
7373
encodeURIComponent: "readonly",
7474
caches: "readonly",
7575
CSSRule: "readonly",
76+
CSSStyleDeclaration: "readonly",
7677
NodeFilter: "readonly",
7778
EventTarget: "readonly",
79+
DOMTokenList: "readonly",
7880
};
7981

8082
export default [

skills/webperf/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ metadata:
1313

1414
# WebPerf Snippets Toolkit
1515

16-
A collection of 48 JavaScript snippets for measuring and debugging web performance in Chrome DevTools. Each snippet runs in the browser console and outputs structured, color-coded results.
16+
A collection of 49 JavaScript snippets for measuring and debugging web performance in Chrome DevTools. Each snippet runs in the browser console and outputs structured, color-coded results.
1717

1818
## Quick Reference
1919

snippets/CoreWebVitals/CLS.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// CLS Quick Check
22
// https://webperf-snippets.nucliweb.net
33

4-
(() => {
4+
(async () => {
55
let cls = 0;
66

77
const valueToRating = (score) =>
@@ -63,9 +63,16 @@
6363
""
6464
);
6565

66-
// Synchronous return for agent (buffered entries)
67-
const clsSync = performance.getEntriesByType("layout-shift")
68-
.reduce((sum, e) => !e.hadRecentInput ? sum + e.value : sum, 0);
66+
// Return for agent — collect via buffered observer (getEntriesByType does not
67+
// expose layout-shift entries in Chrome without an active observer).
68+
const clsSync = await new Promise((resolve) => {
69+
let sum = 0;
70+
const obs = new PerformanceObserver((list) => {
71+
for (const e of list.getEntries()) if (!e.hadRecentInput) sum += e.value;
72+
});
73+
obs.observe({ type: "layout-shift", buffered: true });
74+
setTimeout(() => { obs.disconnect(); resolve(sum); }, 0);
75+
});
6976
const clsRating = valueToRating(clsSync);
7077
return {
7178
script: "CLS",

snippets/CoreWebVitals/LCP-Sub-Parts.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// LCP Sub-Parts Analysis
22
// https://webperf-snippets.nucliweb.net
33

4-
(() => {
4+
(async () => {
55
const formatMs = (ms) => `${Math.round(ms)}ms`;
66
const formatPercent = (value, total) => `${Math.round((value / total) * 100)}%`;
77

@@ -204,11 +204,16 @@
204204
console.log("%c📊 LCP Sub-Parts Analysis Active", "font-weight: bold; font-size: 14px;");
205205
console.log(" Waiting for LCP...");
206206

207-
// Synchronous return for agent (buffered entries)
208-
const lcpBuffered = performance.getEntriesByType("largest-contentful-paint");
209-
const lcpEntry = lcpBuffered.at(-1);
207+
// Return for agent — collect via buffered observer (getEntriesByType does not
208+
// expose largest-contentful-paint entries in Chrome without an active observer).
209+
const lcpEntry = await new Promise((resolve) => {
210+
const entries = [];
211+
const obs = new PerformanceObserver((list) => entries.push(...list.getEntries()));
212+
obs.observe({ type: "largest-contentful-paint", buffered: true });
213+
setTimeout(() => { obs.disconnect(); resolve(entries.at(-1) ?? null); }, 0);
214+
});
210215
if (!lcpEntry) {
211-
return { script: "LCP-Sub-Parts", status: "error", error: "No LCP entries yet" };
216+
return { script: "LCP-Sub-Parts", status: "error", error: "No LCP entries buffered" };
212217
}
213218
const navEntrySync = getNavigationEntry();
214219
if (!navEntrySync) {

snippets/CoreWebVitals/LCP.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// LCP Quick Check
22
// https://webperf-snippets.nucliweb.net
33

4-
(() => {
4+
(async () => {
55
const valueToRating = (ms) =>
66
ms <= 2500 ? "good" : ms <= 4000 ? "needs-improvement" : "poor";
77

@@ -83,11 +83,16 @@
8383
console.log("%c⏱️ LCP Tracking Active", "font-weight: bold; font-size: 14px;");
8484
console.log(" LCP may update as larger elements load.");
8585

86-
// Synchronous return for agent (buffered entries)
87-
const lcpEntries = performance.getEntriesByType("largest-contentful-paint");
88-
const lastLcpEntry = lcpEntries.at(-1);
86+
// Return for agent — collect via buffered observer (getEntriesByType does not
87+
// expose largest-contentful-paint entries in Chrome without an active observer).
88+
const lastLcpEntry = await new Promise((resolve) => {
89+
const entries = [];
90+
const obs = new PerformanceObserver((list) => entries.push(...list.getEntries()));
91+
obs.observe({ type: "largest-contentful-paint", buffered: true });
92+
setTimeout(() => { obs.disconnect(); resolve(entries.at(-1) ?? null); }, 0);
93+
});
8994
if (!lastLcpEntry) {
90-
return { script: "LCP", status: "error", error: "No LCP entries yet" };
95+
return { script: "LCP", status: "error", error: "No LCP entries buffered" };
9196
}
9297
const lcpActivationStart = getActivationStart();
9398
const lcpValue = Math.round(Math.max(0, lastLcpEntry.startTime - lcpActivationStart));

0 commit comments

Comments
 (0)