Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ include fmt.mk
.PHONY: test test-unit test-integration test-watch test-coverage test-e2e
.PHONY: dist dist-mac dist-win dist-linux
.PHONY: docs docs-build docs-watch
.PHONY: storybook storybook-build test-storybook
.PHONY: storybook storybook-build test-storybook screenshot-storybook
.PHONY: benchmark-terminal
.PHONY: ensure-deps
.PHONY: check-eager-imports check-bundle-size check-startup
Expand Down Expand Up @@ -252,6 +252,9 @@ storybook-build: node_modules/.installed src/version.ts ## Build static Storyboo
test-storybook: node_modules/.installed ## Run Storybook interaction tests (requires Storybook to be running or built)
@bun x test-storybook

screenshot-storybook: node_modules/.installed ## Screenshot all Storybook stories
@bun scripts/screenshot-storybook.ts

## Benchmarks
benchmark-terminal: ## Run Terminal-Bench with the cmux agent (use TB_DATASET/TB_SAMPLE_SIZE/TB_ARGS to customize)
@TB_DATASET=$${TB_DATASET:-terminal-bench-core==0.1.1}; \
Expand Down
31 changes: 31 additions & 0 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@eslint/js": "^9.36.0",
"@octokit/rest": "^22.0.0",
"@playwright/test": "^1.56.0",
"@storybook/addon-essentials": "^8.6.14",
"@storybook/addon-interactions": "^8.6.14",
Expand Down Expand Up @@ -398,6 +399,30 @@

"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],

"@octokit/auth-token": ["@octokit/auth-token@6.0.0", "", {}, "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w=="],

"@octokit/core": ["@octokit/core@7.0.5", "", { "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.2", "@octokit/request": "^10.0.4", "@octokit/request-error": "^7.0.1", "@octokit/types": "^15.0.0", "before-after-hook": "^4.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q=="],

"@octokit/endpoint": ["@octokit/endpoint@11.0.1", "", { "dependencies": { "@octokit/types": "^15.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-7P1dRAZxuWAOPI7kXfio88trNi/MegQ0IJD3vfgC3b+LZo1Qe6gRJc2v0mz2USWWJOKrB2h5spXCzGbw+fAdqA=="],

"@octokit/graphql": ["@octokit/graphql@9.0.2", "", { "dependencies": { "@octokit/request": "^10.0.4", "@octokit/types": "^15.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-iz6KzZ7u95Fzy9Nt2L8cG88lGRMr/qy1Q36ih/XVzMIlPDMYwaNLE/ENhqmIzgPrlNWiYJkwmveEetvxAgFBJw=="],

"@octokit/openapi-types": ["@octokit/openapi-types@26.0.0", "", {}, "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA=="],

"@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@13.2.1", "", { "dependencies": { "@octokit/types": "^15.0.1" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-Tj4PkZyIL6eBMYcG/76QGsedF0+dWVeLhYprTmuFVVxzDW7PQh23tM0TP0z+1MvSkxB29YFZwnUX+cXfTiSdyw=="],

"@octokit/plugin-request-log": ["@octokit/plugin-request-log@6.0.0", "", { "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q=="],

"@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@16.1.1", "", { "dependencies": { "@octokit/types": "^15.0.1" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-VztDkhM0ketQYSh5Im3IcKWFZl7VIrrsCaHbDINkdYeiiAsJzjhS2xRFCSJgfN6VOcsoW4laMtsmf3HcNqIimg=="],

"@octokit/request": ["@octokit/request@10.0.5", "", { "dependencies": { "@octokit/endpoint": "^11.0.1", "@octokit/request-error": "^7.0.1", "@octokit/types": "^15.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-TXnouHIYLtgDhKo+N6mXATnDBkV05VwbR0TtMWpgTHIoQdRQfCSzmy/LGqR1AbRMbijq/EckC/E3/ZNcU92NaQ=="],

"@octokit/request-error": ["@octokit/request-error@7.0.1", "", { "dependencies": { "@octokit/types": "^15.0.0" } }, "sha512-CZpFwV4+1uBrxu7Cw8E5NCXDWFNf18MSY23TdxCBgjw1tXXHvTrZVsXlW8hgFTOLw8RQR1BBrMvYRtuyaijHMA=="],

"@octokit/rest": ["@octokit/rest@22.0.0", "", { "dependencies": { "@octokit/core": "^7.0.2", "@octokit/plugin-paginate-rest": "^13.0.1", "@octokit/plugin-request-log": "^6.0.0", "@octokit/plugin-rest-endpoint-methods": "^16.0.0" } }, "sha512-z6tmTu9BTnw51jYGulxrlernpsQYXpui1RK21vmXn8yF5bp6iX16yfTtJYGK5Mh1qDkvDOmp2n8sRMcQmR8jiA=="],

"@octokit/types": ["@octokit/types@15.0.1", "", { "dependencies": { "@octokit/openapi-types": "^26.0.0" } }, "sha512-sdiirM93IYJ9ODDCBgmRPIboLbSkpLa5i+WLuXH8b8Atg+YMLAyLvDDhNWLV4OYd08tlvYfVm/dw88cqHWtw1Q=="],

"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],

"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
Expand Down Expand Up @@ -988,6 +1013,8 @@

"baseline-browser-mapping": ["baseline-browser-mapping@2.8.16", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-OMu3BGQ4E7P1ErFsIPpbJh0qvDudM/UuJeHgkAvfWe+0HFJCXh+t/l8L6fVLR55RI/UbKrVLnAXZSVwd9ysWYw=="],

"before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="],

"better-opn": ["better-opn@3.0.2", "", { "dependencies": { "open": "^8.0.4" } }, "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ=="],

"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
Expand Down Expand Up @@ -1436,6 +1463,8 @@

"extsprintf": ["extsprintf@1.4.1", "", {}, "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA=="],

"fast-content-type-parse": ["fast-content-type-parse@3.0.0", "", {}, "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg=="],

"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],

"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
Expand Down Expand Up @@ -2632,6 +2661,8 @@

"unist-util-visit-parents": ["unist-util-visit-parents@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw=="],

"universal-user-agent": ["universal-user-agent@7.0.3", "", {}, "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A=="],

"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],

"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@eslint/js": "^9.36.0",
"@octokit/rest": "^22.0.0",
"@playwright/test": "^1.56.0",
"@storybook/addon-essentials": "^8.6.14",
"@storybook/addon-interactions": "^8.6.14",
Expand Down Expand Up @@ -105,10 +106,10 @@
"electron-builder": "^24.6.0",
"electron-devtools-installer": "^4.0.0",
"electron-mock-ipc": "^0.3.12",
"escape-html": "^1.0.3",
"eslint": "^9.36.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
"escape-html": "^1.0.3",
"jest": "^30.1.3",
"mermaid": "^11.12.0",
"playwright": "^1.56.0",
Expand Down
243 changes: 243 additions & 0 deletions scripts/README-screenshot-storybook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
# Storybook Screenshot Tool

Automatically capture screenshots of all Storybook stories for visual documentation and PR reviews.

## Features

- 📸 Screenshots all Storybook stories automatically
- 🔄 Starts Storybook server automatically (or uses existing instance)
- 📊 Generates a JSON manifest with metadata
- 🎨 Organized output by component
- 🔗 Includes links to Storybook URLs
- 📤 GitHub integration helpers

## Quick Start

```bash
# Screenshot all stories
make screenshot-storybook

# Or run directly
bun scripts/screenshot-storybook.ts
```

Screenshots are saved to `artifacts/storybook-screenshots/`

## Output Structure

```
artifacts/storybook-screenshots/
├── manifest.json # Metadata about all screenshots
├── components-modal--default.png # Screenshot files
├── components-modal--with-actions.png
└── ...
```

### Manifest Format

```json
{
"timestamp": "2025-10-21T12:00:00.000Z",
"viewport": { "width": 1280, "height": 720 },
"storybookUrl": "http://localhost:6006",
"screenshots": [
{
"id": "components-modal--default",
"title": "Components/Modal",
"name": "Default",
"filename": "components-modal--default.png",
"url": "http://localhost:6006/iframe.html?id=components-modal--default&viewMode=story"
}
]
}
```

## Usage with GitHub

### Option 1: Commit Screenshots to Repo

The simplest approach for small teams:

```bash
# Take screenshots
make screenshot-storybook

# Commit them
git add artifacts/storybook-screenshots/
git commit -m "📸 Add Storybook screenshots"
git push
```

Then reference them in PRs:

```markdown
## Visual Changes

### Modal Component
![Modal](./artifacts/storybook-screenshots/components-modal--default.png)
```

### Option 2: Manual Upload to PR

For one-off reviews:

1. Take screenshots: `make screenshot-storybook`
2. Open your PR on GitHub
3. Drag and drop images from `artifacts/storybook-screenshots/` into a comment
4. GitHub will automatically host and display them

### Option 3: External Image Hosting

For teams that don't want to commit images:

```bash
# Take screenshots
make screenshot-storybook

# Upload to your preferred service (imgur, cloudinary, etc.)
# Then post links in your PR
```

## Configuration

### Environment Variables

- `STORYBOOK_URL` - Custom Storybook URL (default: `http://localhost:6006`)
- `GITHUB_TOKEN` - For GitHub API integration (future feature)

### Custom Viewport

Edit `scripts/screenshot-storybook.ts`:

```typescript
const VIEWPORT = { width: 1920, height: 1080 };
```

## Advanced Usage

### Screenshot Specific Stories

Currently screenshots all stories. To filter specific stories, modify the `getStories()` function:

```typescript
const stories = await getStories(page);
const filtered = stories.filter(s => s.title.startsWith('Components/'));
```

### Different Viewports

To capture mobile and desktop views:

```typescript
const viewports = [
{ name: 'mobile', width: 375, height: 667 },
{ name: 'desktop', width: 1280, height: 720 },
];

for (const viewport of viewports) {
await context.setViewportSize(viewport);
// Take screenshot...
}
```

### Visual Regression Testing

Combine with a visual regression tool like [Percy](https://percy.io/) or [Chromatic](https://www.chromatic.com/):

```typescript
// In your CI pipeline
await screenshotAllStories();
// Upload to Percy/Chromatic
```

## CI Integration

Add to `.github/workflows/storybook-screenshots.yml`:

```yaml
name: Storybook Screenshots

on:
pull_request:
paths:
- 'src/components/**/*.tsx'
- 'src/components/**/*.stories.tsx'

jobs:
screenshot:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- run: make screenshot-storybook
- uses: actions/upload-artifact@v4
with:
name: storybook-screenshots
path: artifacts/storybook-screenshots/
```

## Troubleshooting

### Storybook won't start

Make sure dependencies are installed:

```bash
bun install
```

### Screenshots are blank

Some stories may need more time to render. Increase the wait time in `waitForStorybook()`:

```typescript
await page.waitForTimeout(1000); // Increase from 500ms
```

### Port already in use

If port 6006 is in use:

```bash
STORYBOOK_URL=http://localhost:6007 make screenshot-storybook
```

Or kill the existing process:

```bash
lsof -ti:6006 | xargs kill
```

## Development

The script uses:
- **Playwright** - Browser automation
- **Storybook** - Component documentation
- **Bun** - Runtime and package manager

To modify the script:

1. Edit `scripts/screenshot-storybook.ts`
2. Test locally: `bun scripts/screenshot-storybook.ts`
3. Commit changes

## Future Enhancements

Potential improvements:

- [ ] Direct GitHub API upload (requires image hosting solution)
- [ ] Multiple viewport support
- [ ] Theme switching (light/dark mode)
- [ ] Parallel screenshot capture
- [ ] Visual diff with previous screenshots
- [ ] Integration with Percy/Chromatic
- [ ] Configurable wait times per story
- [ ] Screenshot only changed stories

## Related

- [Storybook Documentation](https://storybook.js.org/)
- [Playwright Documentation](https://playwright.dev/)
- [Visual Regression Testing Guide](https://storybook.js.org/docs/react/writing-tests/visual-testing)

Loading
Loading