loading-story
Capture timed screenshots during page load, overlay with network waterfall data, and produce a visual narrative of the loading experience with annotated filmstrip.
What this skill does
# Loading Story
Capture the complete loading experience of a web page as a visual narrative. Collect performance milestones (First Paint, First Contentful Paint, Largest Contentful Paint), take screenshots at key moments, gather network waterfall data, and produce an annotated filmstrip that tells the story of what the user sees at each stage of loading.
## When to Use
- Diagnosing slow page loads by visualizing what the user sees at each milestone
- Comparing loading experiences between pages, deployments, or network conditions
- Presenting loading performance to stakeholders with visual evidence
- Identifying render-blocking resources by correlating network requests with paint events
- Optimizing perceived performance by understanding the visual loading sequence
## Prerequisites
- **Playwright MCP server** connected and available
- **ImageMagick**: The `convert` and `montage` commands must be available (`apt install imagemagick`, `brew install imagemagick`)
Verify prerequisites:
```bash
convert --version | head -1
montage --version | head -1
```
## Workflow
### Step 1: Install Performance Observers
Before navigating, use `browser_evaluate` to install a PerformanceObserver on the current page that will capture paint and LCP events. This must be done via `addInitScript` so it runs before the page loads.
```javascript
async (page) => {
await page.context().addInitScript(() => {
window.__loadingStoryMilestones = [];
const paintObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
window.__loadingStoryMilestones.push({
name: entry.name,
startTime: Math.round(entry.startTime),
type: 'paint'
});
}
});
paintObserver.observe({ type: 'paint', buffered: true });
const lcpObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
window.__loadingStoryMilestones.push({
name: 'largest-contentful-paint',
startTime: Math.round(entry.startTime),
type: 'lcp',
element: entry.element ? entry.element.tagName : 'unknown',
size: entry.size
});
}
});
lcpObserver.observe({ type: 'largest-contentful-paint', buffered: true });
});
return 'Performance observers installed via addInitScript.';
}
```
### Step 2: Navigate to the Target URL
Use `browser_navigate` to go to the target URL. The performance observers from Step 1 will automatically capture paint events as they occur.
### Step 3: Wait for Full Page Load
Use `browser_wait_for` to allow the page to fully load. Wait at least 3-5 seconds for LCP to fire on most pages, or wait for a specific element that indicates full load.
### Step 4: Harvest Performance Milestones
Use `browser_evaluate` to collect all captured milestones along with Navigation Timing data.
```javascript
async (page) => {
const data = await page.evaluate(() => {
const nav = performance.getEntriesByType('navigation')[0];
const milestones = window.__loadingStoryMilestones || [];
// Add navigation timing milestones
const timingMilestones = [
{ name: 'domContentLoaded', startTime: Math.round(nav.domContentLoadedEventStart), type: 'timing' },
{ name: 'loadEvent', startTime: Math.round(nav.loadEventStart), type: 'timing' },
{ name: 'domInteractive', startTime: Math.round(nav.domInteractive), type: 'timing' },
{ name: 'responseStart (TTFB)', startTime: Math.round(nav.responseStart), type: 'timing' }
];
const all = [...milestones, ...timingMilestones]
.filter(m => m.startTime > 0)
.sort((a, b) => a.startTime - b.startTime);
return {
milestones: all,
url: window.location.href,
totalLoadTime: Math.round(nav.loadEventEnd - nav.startTime)
};
});
return JSON.stringify(data, null, 2);
}
```
### Step 5: Capture Network Waterfall
Use `browser_network_requests` to get the complete network waterfall. Note the request timings, sizes, and types to correlate with visual milestones.
Set `includeStatic: true` to capture all resources including images, fonts, and scripts.
### Step 6: Take Screenshots at Milestone Intervals
Use `browser_run_code` to replay the navigation while capturing screenshots at timed intervals corresponding to the milestones discovered in Step 4. This creates a visual record of what the user sees at each stage.
```javascript
async (page) => {
const framesDir = '/tmp/playwright-toolkit/loading-story/frames';
const fs = require('fs');
fs.mkdirSync(framesDir, { recursive: true });
// Define capture intervals (in ms from navigation start)
// Adjust these based on milestones from Step 4
const capturePoints = [0, 100, 200, 500, 800, 1000, 1500, 2000, 3000, 5000];
const labels = [];
for (let i = 0; i < capturePoints.length; i++) {
const delay = i === 0 ? 0 : capturePoints[i] - capturePoints[i - 1];
if (i === 0) {
// Navigate fresh
page.goto('TARGET_URL_HERE', { waitUntil: 'commit' }).catch(() => {});
await new Promise(r => setTimeout(r, 50));
} else {
await new Promise(r => setTimeout(r, delay));
}
const framePath = `${framesDir}/frame-${String(i).padStart(4, '0')}.png`;
await page.screenshot({ path: framePath });
labels.push(`${capturePoints[i]}ms`);
}
// Write labels file
fs.writeFileSync(`${framesDir}/labels.txt`, labels.join('\n'));
return `Captured ${capturePoints.length} frames in ${framesDir}`;
}
```
### Step 7: Annotate and Create Filmstrip
Run the `waterfall-annotate.sh` script to annotate each frame with its timestamp and milestone label, then montage them into a horizontal filmstrip.
```bash
bash /home/noreddine/.claude/plugins/marketplaces/binaryPetsClaude/plugins/playwright-toolkit/skills/loading-story/scripts/waterfall-annotate.sh \
"/tmp/playwright-toolkit/loading-story/frames" \
"/tmp/playwright-toolkit/loading-story/filmstrip.png" \
"/tmp/playwright-toolkit/loading-story/frames/labels.txt"
```
### Step 8: Read the Filmstrip
Use the `Read` tool to visually inspect the annotated filmstrip at `/tmp/playwright-toolkit/loading-story/filmstrip.png`.
### Step 9: Compose the Narrative Report
Write a narrative that describes the loading experience from the user's perspective, incorporating all collected data:
- **Timeline**: At each milestone timestamp, describe what the user sees
- **Network correlation**: Which resources were loading/completed at each visual state
- **Bottlenecks**: Identify render-blocking resources or long gaps between milestones
- **Recommendations**: Suggest optimizations based on the observed loading sequence
Example narrative format:
> "At 0ms the browser begins navigation. At 180ms (TTFB) the first byte arrives after a 180ms server response time. At 350ms First Paint occurs -- the user sees a white screen with the background color applied. At 520ms First Contentful Paint fires as the header and navigation render, while 3 JavaScript bundles (total 450KB) are still downloading. At 1200ms the hero image appears as Largest Contentful Paint, bringing the page to a visually complete state. The main thread remains blocked until 1800ms when the remaining JS executes and the page becomes interactive at domInteractive."
## Interpreting Results
- **TTFB (Time to First Byte)**: Server response time. Values > 600ms suggest server-side optimization needed.
- **First Paint (FP)**: When the browser first renders anything (even a background color). Long gap between TTFB and FP suggests render-blocking resources.
- **First Contentful Paint (FCP)**: When meaningful content first appears. Should be < 1.8s for good user experience.
- **Largest Contentful Paint (LCP)**: When the largest visible content element renders. Should be < 2.5s. The `element` and `size` fields identify what triggered LCP.
- **domInteractive**: When the DOM is ready for interaction. Large gap between FCP and domInteractive suggesRelated in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.