generate-readme-screenshots
Generate README Screenshots
Purpose
The README screenshots in docs/img/*.webp are not manually captured — they are
deterministically generated from Storybook stories defined in
src/browser/stories/App.readmeScreenshots.stories.tsx. When any of these stories
change visually (detected by Chromatic or local inspection), the corresponding WebP
assets must be regenerated and committed.
Quick Reference
# 1. Build Storybook
make storybook-build
# 2. Serve the static build (keep running in background)
python3 -m http.server 6006 -d storybook-static &
# 3. Capture all screenshots (3800px, WebP quality 90)
bun run scripts/capture-readme-screenshots.ts
# 4. Capture a single story
bun run scripts/capture-readme-screenshots.ts --story CodeReview
Prerequisites
The capture pipeline requires these tools. If any are missing, stop immediately and report the issue to the user with full detail — do not silently skip or produce degraded output.
| Dependency | Purpose | Check command |
|---|---|---|
bun |
Script runtime | bun --version |
playwright |
Headless browser automation | bun -e "require('playwright')" |
sharp |
Image processing (resize, WebP) | bun -e "require('sharp')" |
python3 |
Static file server for Storybook build | which python3 |
Prerequisite Verification
Before running the pipeline, verify all dependencies:
bun --version && \
bun -e "require('playwright'); require('sharp'); console.log('deps ok')" && \
which python3
If any of these fail:
- Do not proceed with screenshot generation.
- Report the exact failing command and error to the user.
- Suggest remediation:
- Missing
playwright:bun add -d playwright && bunx playwright install chromium - Missing
sharp:bun add -d sharp - Missing
python3: suggest installing Python or using an alternative static server - Missing
bun: this is a hard requirement — the entire repo uses bun
- Missing
Architecture
Resolution & Output
| Parameter | Value |
|---|---|
| CSS Viewport | 1900 × 1188 |
| Device Scale Factor (DPR) | 2 |
| Native capture resolution | 3800 × 2376 |
| Output format | WebP (quality 90, lanczos3 resampling) |
| Output directory | docs/img/ |
Story → Screenshot Mapping
| Story Export Name | Output File | Has Interaction |
|---|---|---|
CodeReview |
code-review.webp |
No |
AgentStatusSidebar |
agent-status.webp |
No |
GitStatusPopover |
git-status.webp |
Yes (open dialog + toggle mode) |
PlanMermaidWithCosts |
plan-mermaid.webp |
No |
AutoModeAgentSwitching |
auto-mode.webp |
Yes (open creation card) |
CostsTabRich |
costs-tab.webp |
No |
ContextManagementDialog |
context-management.webp |
Yes (open settings dialog) |
MobileServerMode |
mobile-server-mode.webp |
No |
OrchestrateAgents |
orchestrate-agents.webp |
No |
Key Files
| File | Role |
|---|---|
src/browser/stories/App.readmeScreenshots.stories.tsx |
Story definitions and mock data |
scripts/capture-readme-screenshots.ts |
Playwright + Sharp capture pipeline |
src/browser/stories/mockFactory.ts |
Deterministic mock data factories |
src/browser/stories/mocks/orpc.ts |
Mock ORPC backend (terminal, git, secrets) |
docs/img/*.webp |
Output screenshot assets |
Step-by-Step Procedure
1. Build Storybook
make storybook-build
This produces storybook-static/ in the repo root.
2. Start a Static Server
The capture script expects Storybook at http://localhost:6006. Use a background
bash task (not nohup or & in foreground) so the server survives:
# As a background bash task with ~10min lifetime
python3 -m http.server 6006 -d storybook-static
Verify the server is reachable:
bun -e "const r = await fetch('http://localhost:6006'); console.log(r.status)"
Important:
curlmay succeed whenbun fetchfails (different DNS resolution). Always verify withbun -e "fetch(...)"since the capture script uses bun's fetch.
3. Run the Capture Script
bun run scripts/capture-readme-screenshots.ts --storybook-url http://localhost:6006
The script:
- Iterates all 9 stories sequentially
- Opens a fresh Playwright page per story
- Waits for
networkidle+ 2s stabilization - Runs
playInteractionif defined (with up to 3 retries for flaky interactions) - Captures full-page PNG, then converts to WebP via Sharp
- Writes to
docs/img/<outputFile>
4. Handle Failures
Stories with playInteraction (GitStatusPopover, AutoModeAgentSwitching,
ContextManagementDialog) can be flaky under sequential load due to UI timing.
The script retries each up to 3 times. If failures persist:
# Retry individual failed stories
bun run scripts/capture-readme-screenshots.ts --story GitStatusPopover
bun run scripts/capture-readme-screenshots.ts --story AutoModeAgentSwitching
5. Verify & Commit
# Check all 9 files are present and 3800px wide
for f in code-review agent-status git-status plan-mermaid auto-mode costs-tab context-management mobile-server-mode orchestrate-agents; do
bun -e "const s = require('sharp'); const m = await s('docs/img/${f}.webp').metadata(); console.log('${f}:', m.width + 'x' + m.height)"
done
# Stage and commit
git add docs/img/*.webp
git commit -m "regenerate README screenshots at 3800px"
When to Run This
- Chromatic flags visual changes in any
Docs/README Screenshotsstory - Story data changes in
App.readmeScreenshots.stories.tsxormockFactory.ts - UI component changes that affect the visual appearance of screenshot stories
- Explicit user request to refresh README images
- After rebasing on main when upstream changes affect shared UI components
Troubleshooting
"Storybook is not running"
The capture script verifies Storybook is reachable before starting. Ensure:
make storybook-buildcompleted successfully- The static server is running on port 6006
- Verify with
bun -e "fetch('http://localhost:6006')"(not justcurl)
Interactive stories fail with timeout
Radix popovers/tooltips portal to document.body and require precise hover timing.
The retry logic handles most cases. If persistent:
- Run the failing story individually (
--story <Name>) - Check if the story's mock data or selectors drifted from the actual component
Images look wrong (clipped, misaligned)
- Check viewport constants in
capture-readme-screenshots.ts(1900×1188, DPR 2) - Screenshots use viewport-only capture (not
fullPage) — if content overflows the viewport, the story layout needs adjustment, not the capture script - The
StoryDefinterface supports apostProcesshook for custom Sharp pipelines if a future story needs cropping/compositing, but no current stories use it
Why there is no ProductHero screenshot story
Intentional — README now uses mux-demo.gif as the hero media. The README
screenshot pipeline covers only the 9 docs/img/*.webp assets that remain in
README, starting with code-review.webp.