brainstorming
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or planning anything new, in software or out of it (a talk, a business, a renovation).
tracing-with-chromeUse when running a Chrome lab trace for a specific CWV bottleneck — investigating LCP/INP/CLS with browser tools after RUM has identified the target phase, element, and cause. Typically called by the cwv-superpower orchestrator at Step 3. Requires either chrome-devtools-mcp (preferred) or claude-in-chrome (fallback).
Independent GitHub snapshot · Automated classification · Original documentation governs
Called by the cwv-superpower orchestrator at Step 3, or invoked directly when the user has a specific URL, metric, and suspected bottleneck to investigate.
Inputs:
Chrome investigates the specific bottleneck phase identified by RUM. Not all phases — just the one that matters. The trace also collects visual evidence for the report: network waterfall, page filmstrip, INP interaction timeline.
This skill needs a Chrome browser MCP. Two are supported:
claude mcp add chrome-devtools npx chrome-devtools-mcp@latest
claude --chrome; JS-based timing, no native throttling).If neither is available, the orchestrator skips this skill and reports RUM-only findings.
Pick ONE path at the start of the trace based on which Chrome MCP is connected. Do not switch mid-trace. Do not free-choose tools — use the named tool list for the chosen path.
Path A — chrome-devtools-mcp (preferred). Probe: performance_start_trace exists. Native phase breakdowns, no PerformanceObserver scripts.
Path B — claude-in-chrome (fallback). Probe: javascript_tool and navigate exist on MCP server claude-in-chrome. Same evidence shape; timing captured via PerformanceObserver scripts injected through javascript_tool. Print this warning at the start of the trace:
⚠️ Using claude-in-chrome fallback. For native phase breakdowns and CPU/network throttling, install chrome-devtools-mcp:
claude mcp add chrome-devtools npx chrome-devtools-mcp@latest
Neither available. Return early with lab_unavailable: true. The orchestrator handles the RUM-only fallback.
Three non-negotiable rules:
RUM found the issue — it IS there. If Chrome shows fast timing on the dev machine, that's expected. The dev machine is faster than a median mobile user on a mid-range Android on a 3G connection. The trace shows the SEQUENCE and STRUCTURE of the problem, not the user's timing. Never conclude "the issue doesn't exist" based on lab results.
Never say "I couldn't reproduce the issue." The issue is confirmed by hundreds or thousands of real user sessions. Chrome is here to show WHY it happens, not IF it happens.
Fuzzy element matching — two steps:
section.hero > img.css-x7k2m3 means "the image inside the hero section." Find that image regardless of class names. Class names may be generated, minified, or changed between builds.Print these ↳ sub-status lines as you execute each action:
↳ Navigating to page...
↳ Capturing performance trace...
↳ Analyzing [waterfall/flamechart/filmstrip]...
inpel attribution.Guided by the bottleneck phase from the diagnosis module. Focus on the bottleneck — don't investigate all phases.
<script> tags in <head> delaying HTML parsing?<img> tags?<link rel="preload"> for this resource?fetchpriority="high"? Does it have loading="lazy" (harmful)?background-image, the preload scanner cannot discover it — this is the root causeContent-Length) and transferred size (with compression)Content-Type — JPEG/PNG vs WebP/AVIF?)x-cache, cf-cache-status, x-vercel-cache, age)Content-Encoding: br or gzip)display, opacity)?inpel — click the button, type in the input, tap the linkThen investigate based on the bottleneck phase:
lurl from CoreDashsetTimeout/setInterval)? Script evaluation? Framework hydration?loading/dom-interactive, look specifically for script evaluation tasks (the page was still booting up when the user interacted)localStorage operationsdocument.querySelectorAll('*').length in the Console — values >1,500 are a concerncontent-visibility: auto on off-screen sections could reduce the layout scopeIn the Performance panel, look for red "Layout Shift" markers in the Experience row. Click each to see which element moved and the shift score. Then investigate based on the cause pattern from the diagnosis module:
width and height HTML attributesaspect-ratio on the element or its containertop, left, width, height, margin, padding)Both paths feed the same downstream artifacts: a waterfallData array, filmstrip frames, INP timeline JSON. Report templates do not change between paths.
Tools used (verbatim, all from MCP server chrome-devtools): performance_start_trace, performance_stop_trace, performance_analyze_insight, list_network_requests, get_network_request, take_screenshot, click, fill, wait_for, emulate_cpu, emulate_network, resize_page.
Throttling (mobile): Before tracing, call emulate_cpu with a 4x slowdown and emulate_network with Fast 3G. Use resize_page for the mobile viewport. For desktop, skip throttling.
Trace + phase breakdown:
performance_start_trace with { reload: true, autoStop: true }. This navigates to the target, reloads with throttling applied, records the trace, and stops automatically when the page is idle. The response includes insights with native LCP / FCP / CLS phase breakdowns.performance_analyze_insight with the relevant insight name (LCPBreakdown, RenderBlocking, CLSCulprits, DocumentLatency, etc.) — returns the phase share and contributing resources without manual math.Network waterfall (for SVG waterfall in report):
list_network_requests to enumerate resources (paginate as needed; the report needs the top 15-20 by start time).get_network_request with the URL to get the full timing breakdown.waterfallData shape: { name, start, dns, connect, tls, ttfb, download, size, isLcp }. Identify the LCP resource from the trace insights and set isLcp: true. Keep only the top 15-20 by start time.Filmstrip:
take_screenshot at four moments: immediately after navigation, at FCP, at LCP, and at load+2s. Use the trace's timing markers to know when, or wait_for between screenshots. The trace's reload: true already navigates — no separate navigate_page call needed.INP interaction timeline:
performance_start_trace (without reload).click with the inpel selector, or fill for input events.performance_stop_trace.performance_analyze_insight with the relevant interaction insight to get input delay / processing / presentation breakdown. No manual PerformanceObserver needed.Element screenshot: Call take_screenshot with the problem element scrolled into view.
Print the degradation warning at the start of the trace:
⚠️ Using claude-in-chrome fallback. For native phase breakdowns and CPU/network throttling, install chrome-devtools-mcp:
claude mcp add chrome-devtools npx chrome-devtools-mcp@latest
Tools used (verbatim, all from MCP server claude-in-chrome): navigate, javascript_tool, computer (action screenshot), read_network_requests, read_console_messages, find, resize_window.
Throttling note: claude-in-chrome cannot throttle CPU or network. Only viewport sizing is available via resize_window. Lab timing on this path will be optimistic vs real users — interpret the trace for STRUCTURE (which resources, in which order, blocking what), not timing. RUM remains the source of truth for timing.
Network waterfall (for SVG waterfall in report):
navigate with the target URL.read_network_requests to get all HTTP requests with timing.javascript_tool with this script to get per-resource timing breakdown:
JSON.stringify(performance.getEntriesByType('resource').map(e => ({
name: e.name.split('/').pop().split('?')[0] || e.name,
start: Math.round(e.startTime),
dns: Math.round(e.domainLookupEnd - e.domainLookupStart),
connect: Math.round(e.connectEnd - e.connectStart),
tls: Math.round(e.secureConnectionStart > 0 ? e.connectEnd - e.secureConnectionStart : 0),
ttfb: Math.round(e.responseStart - e.requestStart),
download: Math.round(e.responseEnd - e.responseStart),
size: e.transferSize,
isLcp: false
})))
javascript_tool for the document's navigation timing:
JSON.stringify((function(n) { return {
name: 'document', start: 0,
dns: Math.round(n.domainLookupEnd - n.domainLookupStart),
connect: Math.round(n.connectEnd - n.connectStart),
tls: Math.round(n.secureConnectionStart > 0 ? n.connectEnd - n.secureConnectionStart : 0),
ttfb: Math.round(n.responseStart - n.requestStart),
download: Math.round(n.responseEnd - n.responseStart),
size: 0, isLcp: false
}; })(performance.getEntriesByType('navigation')[0]))
javascript_tool for the LCP time and resource URL:
JSON.stringify(await new Promise(r => new PerformanceObserver(l => {
var entries = l.getEntries(); var last = entries[entries.length - 1];
r({ time: Math.round(last.startTime), url: last.url || '', element: last.element?.tagName });
}).observe({ type: 'largest-contentful-paint', buffered: true })))
isLcp: true on the entry matching the LCP resource URL. Keep top 15-20 by start time.Filmstrip:
navigate to the target URL.computer with action screenshot immediately — captures the blank/loading state. Label "0.0s".javascript_tool to wait for FCP and return its time:
await new Promise(r => new PerformanceObserver(l => r()).observe({ type: 'paint', buffered: true }));
Math.round(performance.getEntriesByType('paint').find(e => e.name === 'first-contentful-paint')?.startTime || 0)
Then call computer screenshot. Label with the FCP time.javascript_tool to wait for LCP:
await new Promise(r => setTimeout(r, 3000));
Math.round((await new Promise(r => new PerformanceObserver(l => {
var e = l.getEntries(); r(e[e.length-1].startTime);
}).observe({ type: 'largest-contentful-paint', buffered: true }))))
Then computer screenshot. Label with the LCP time.computer screenshot. Label "Loaded".If timed screenshots are not possible (page loads too fast), capture at minimum ONE fully-loaded screenshot.
INP interaction timeline:
javascript_tool to set up observers:
window.__cwvEvents = []; window.__cwvTasks = [];
new PerformanceObserver(l => l.getEntries().forEach(e => window.__cwvEvents.push({
name: e.name, start: Math.round(e.startTime),
inputDelay: Math.round(e.processingStart - e.startTime),
processing: Math.round(e.processingEnd - e.processingStart),
presentation: Math.round(e.startTime + e.duration - e.processingEnd),
duration: Math.round(e.duration)
}))).observe({ type: 'event', buffered: true, durationThreshold: 16 });
new PerformanceObserver(l => l.getEntries().forEach(e => window.__cwvTasks.push({
start: Math.round(e.startTime), duration: Math.round(e.duration)
}))).observe({ type: 'longtask', buffered: true });
find with the inpel selector or descriptive text to locate the element, then either click via computer (mouse coordinates from find) or trigger directly with javascript_tool: document.querySelector('SELECTOR').click().javascript_tool to collect:
JSON.stringify({ events: window.__cwvEvents, longTasks: window.__cwvTasks })
Console messages: Call read_console_messages to surface any web-vitals.js debug output, framework warnings, or errors during the trace.
Element screenshot: Use computer (action screenshot) with the problem element scrolled into view.
After completing the trace, print ✅ Confirmed: [one-sentence key finding from trace]
Chrome trace findings:
- Path: [chrome-devtools-mcp / claude-in-chrome (fallback)]
- URL visited: [URL]
- Emulation: [mobile Fast 3G 4x CPU / desktop no throttling / fallback: viewport only]
- Bottleneck phase confirmed: [PHASE] — [what Chrome showed]
- Root cause evidence: [2-3 sentences describing what was seen]
- Key measurement: [specific timing or gap that proves the bottleneck]
- Evidence captured: [filmstrip screenshots: N frames, waterfall data: N resources, element screenshot]