Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
aa67de7
πŸ€– Add Storybook and Chromatic visual regression testing
ammario Oct 6, 2025
e97acb3
πŸ€– Fix markdown and shell script formatting
ammario Oct 6, 2025
b1854e0
πŸ€– Add Storybook stories for tool messages and custom FileRead renderer
ammario Oct 6, 2025
d3cbe53
Fix formatting
ammario Oct 6, 2025
957c684
πŸ€– Add robust error handling for diff parsing in FileEditToolCall
ammario Oct 6, 2025
b6b5543
Format FileEditToolCall
ammario Oct 6, 2025
554794c
πŸ€– Add Emotion CacheProvider for consistent styling in Storybook
ammario Oct 6, 2025
4fc8bd5
Add @emotion/cache as explicit dependency for Storybook
ammario Oct 6, 2025
dcf8029
πŸ€– Fix Chromatic emotion/styled bundling issue
ammario Oct 6, 2025
38e9958
πŸ€– Fix emotion babel plugin configuration for Chromatic
ammario Oct 6, 2025
329f93c
πŸ€– Remove exclude pattern from emotion React plugin
ammario Oct 6, 2025
09e3b1a
πŸ€– Force Babel transformation on all file types for emotion
ammario Oct 6, 2025
786e10d
πŸ€– WIP: Convert styled components to CSS modules for Chromatic compati…
ammario Oct 7, 2025
7d4a2ac
πŸ€– Convert FileReadToolCall to CSS modules
ammario Oct 7, 2025
de9baa9
πŸ€– Prepare CSS modules for ProposePlanToolCall (WIP)
ammario Oct 7, 2025
4934f23
πŸ€– Add dedupe config for emotion packages in Storybook
ammario Oct 7, 2025
59e66fa
πŸ€– Document Chromatic emotion/styled issue and attempted solutions
ammario Oct 7, 2025
e59d09d
πŸ€– Add PR #38 status report with decision options
ammario Oct 7, 2025
72334c9
Fix Chromatic compatibility by removing styled shim
ammario Oct 7, 2025
cd43476
Clean up temp diagnostic files
ammario Oct 7, 2025
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
72 changes: 72 additions & 0 deletions .github/CHROMATIC_SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Chromatic Visual Regression Testing

This repository uses Chromatic for automated visual regression testing of Storybook components.

## Setup Complete βœ…

The following has been configured:

- βœ… Chromatic CLI installed (`chromatic@13.3.0`)
- βœ… GitHub Actions workflow (`.github/workflows/chromatic.yml`)
- βœ… Configuration file (`chromatic.config.json`)
- βœ… Project token added to GitHub Secrets
- βœ… Documentation updated

## How It Works

### On Pull Requests
1. Chromatic captures snapshots of all Storybook stories
2. Compares snapshots to the baseline from `main`
3. Posts visual diffs as PR comments
4. Build passes (won't block merge even with visual changes)
5. Developers review changes in Chromatic UI

### On Main Branch
- Changes are automatically accepted as the new baseline
- All future PRs compare against this baseline

## TurboSnap Optimization

**TurboSnap** is enabled to speed up builds:
- Only captures snapshots for changed stories
- Typical build time: ~30 seconds
- Requires full git history (`fetch-depth: 0`)

## Commands

```bash
# Run Chromatic locally (requires CHROMATIC_PROJECT_TOKEN env var)
bun run chromatic

# Build Storybook
bun run build-storybook

# Run Storybook dev server
bun run storybook
```

## Configuration Files

- `.github/workflows/chromatic.yml` - CI workflow
- `chromatic.config.json` - Chromatic settings
- `STORYBOOK.md` - Full Storybook documentation

## Accessing Chromatic

Visit https://www.chromatic.com/ and log in with your GitHub account to:
- View detailed visual diffs
- Approve/reject changes
- See build history
- Manage baselines

## Complements E2E Tests

Chromatic works alongside your existing e2e tests:
- **Chromatic**: Tests visual appearance of isolated components
- **E2E tests**: Test behavior and user interactions
- **No overlap**: Different concerns with minimal maintenance burden

## Free for Open Source

This project qualifies for Chromatic's free open source plan with unlimited builds.

33 changes: 33 additions & 0 deletions .github/workflows/chromatic.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Chromatic

on:
push:
branches: [main]
pull_request:
branches: ["**"]

jobs:
chromatic:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Required for Chromatic TurboSnap

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest

- name: Install dependencies
run: bun install --frozen-lockfile

- name: Publish to Chromatic
uses: chromaui/action@latest
with:
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
buildScriptName: build-storybook
onlyChanged: true # Only test changed stories (TurboSnap)
exitZeroOnChanges: true # Don't fail on visual changes
autoAcceptChanges: main # Auto-accept changes on main branch
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,8 @@ docs/mermaid.min.js
# Profiling
**.cpuprofile
profile.txt

*storybook.log
storybook-static
chromatic-*.log
build-storybook.log
54 changes: 54 additions & 0 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { StorybookConfig } from '@storybook/react-vite';
import type { UserConfig } from 'vite';
import react from '@vitejs/plugin-react';

const config: StorybookConfig = {
"stories": [
"../src/**/*.mdx",
"../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"
],
"addons": [
"@chromatic-com/storybook",
"@storybook/addon-docs",
"@storybook/addon-onboarding",
"@storybook/addon-a11y",
"@storybook/addon-vitest"
],
"framework": {
"name": "@storybook/react-vite",
"options": {}
},
async viteFinal(config: UserConfig) {
// Remove any existing Vite React plugins that Storybook registers
config.plugins = (config.plugins || []).filter((plugin) => {
if (!plugin) return true;
const pluginName = Array.isArray(plugin) ? plugin[0]?.name : plugin.name;
return !pluginName?.includes('vite:react');
});

// Re-register the React plugin with Emotion configuration
config.plugins.push(
react({
exclude: [/\.stories\.(t|j)sx?$/, /node_modules/],
jsxImportSource: '@emotion/react',
babel: {
plugins: ['@emotion/babel-plugin'],
},
})
);

// Pre-bundle Emotion packages to reduce cold start time
config.optimizeDeps = {
...config.optimizeDeps,
include: [
...(config.optimizeDeps?.include || []),
'@emotion/react',
'@emotion/styled',
'@emotion/cache',
],
};

return config;
},
};
export default config;
6 changes: 6 additions & 0 deletions .storybook/manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { addons } from '@storybook/manager-api';
import { themes } from '@storybook/theming';

addons.setConfig({
theme: themes.dark,
});
72 changes: 72 additions & 0 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type { Preview } from '@storybook/react-vite'
import { themes } from '@storybook/theming';
import { Global, css } from '@emotion/react';
import { CacheProvider } from '@emotion/react';
import createCache from '@emotion/cache';
import { GlobalColors } from '../src/styles/colors';
import { GlobalFonts } from '../src/styles/fonts';
import React from 'react';

// Create emotion cache for consistent styling
const emotionCache = createCache({ key: 'cmux' });

// Base styles matching the app
const globalStyles = css`
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
font-family: var(--font-primary);
font-size: 14px;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: var(--color-text);
}

code {
font-family: var(--font-monospace);
}
`;

const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
backgrounds: {
default: 'dark',
values: [
{
name: 'dark',
value: 'hsl(0 0% 12%)',
},
{
name: 'light',
value: '#ffffff',
},
],
},
docs: {
theme: themes.dark,
},
},
decorators: [
(Story) => (
<CacheProvider value={emotionCache}>
<GlobalColors />
<GlobalFonts />
<Global styles={globalStyles} />
<Story />
</CacheProvider>
),
],
};

export default preview;
109 changes: 109 additions & 0 deletions STORYBOOK.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Storybook

This project uses [Storybook](https://storybook.js.org/) for component development and documentation.

## Running Storybook

To start Storybook in development mode:

```bash
bun run storybook
```

This will start Storybook on http://localhost:6006

## Building Storybook

To build a static version of Storybook:

```bash
bun run build-storybook
```

The output will be in the `storybook-static` directory.

## Creating Stories

Stories are located next to their components with the `.stories.tsx` extension.

Example structure:

```
src/components/Messages/
β”œβ”€β”€ AssistantMessage.tsx
└── AssistantMessage.stories.tsx
```

### Example Story

```typescript
import type { Meta, StoryObj } from "@storybook/react";
import { MyComponent } from "./MyComponent";

const meta = {
title: "Category/MyComponent",
component: MyComponent,
parameters: {
layout: "padded",
},
tags: ["autodocs"],
} satisfies Meta<typeof MyComponent>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
// component props
},
};
```

## Current Stories

- **Messages/AssistantMessage**: Various states of assistant messages including streaming, partial, with models, etc.

## Configuration

- `.storybook/main.ts` - Main Storybook configuration
- `.storybook/preview.tsx` - Preview configuration with global styles and dark theme for docs
- `.storybook/manager.ts` - Manager (UI chrome) configuration with dark theme

## Theme

Storybook is configured with a dark theme to match the application:

- Dark UI chrome (sidebar, toolbar, etc.)
- Dark documentation pages
- Dark canvas background (`hsl(0 0% 12%)`) matching the app's `--color-background`

## Visual Regression Testing with Chromatic

This project uses [Chromatic](https://www.chromatic.com/) for automated visual regression testing.

### How it works

- **On PRs**: Chromatic captures snapshots of all stories and compares them to the baseline
- **On main**: Changes are automatically accepted as the new baseline
- **TurboSnap**: Only changed stories are tested, making builds fast (~30s typical)

### Running Chromatic locally

```bash
bun run chromatic
```

You'll need a `CHROMATIC_PROJECT_TOKEN` environment variable set.

### CI Integration

Chromatic runs automatically in CI via `.github/workflows/chromatic.yml`:

- Runs on all PRs and pushes to main
- Visual diffs are shown inline in PR comments
- Won't fail the build on visual changes (for review)

### Configuration

- `chromatic.config.json` - Chromatic settings (TurboSnap, skip patterns, etc.)
- See [Chromatic docs](https://www.chromatic.com/docs/) for more options
9 changes: 4 additions & 5 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"@ai-sdk/openai": "^2.0.40",
"@anthropic-ai/sdk": "^0.63.1",
"@dqbd/tiktoken": "^1.0.21",
"@emotion/cache": "^11.14.0",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"ai": "^5.0.56",
Expand Down Expand Up @@ -1959,7 +1960,7 @@

"style-to-object": ["style-to-object@1.0.9", "", { "dependencies": { "inline-style-parser": "0.2.4" } }, "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw=="],

"stylis": ["stylis@4.3.6", "", {}, "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ=="],
"stylis": ["stylis@4.2.0", "", {}, "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="],

"sumchecker": ["sumchecker@3.0.1", "", { "dependencies": { "debug": "^4.1.0" } }, "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg=="],

Expand Down Expand Up @@ -2167,10 +2168,6 @@

"@emotion/babel-plugin/source-map": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="],

"@emotion/babel-plugin/stylis": ["stylis@4.2.0", "", {}, "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="],

"@emotion/cache/stylis": ["stylis@4.2.0", "", {}, "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="],

"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],

"@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
Expand Down Expand Up @@ -2295,6 +2292,8 @@

"mdast-util-mdx-jsx/parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="],

"mermaid/stylis": ["stylis@4.3.6", "", {}, "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ=="],

"mermaid/uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],

"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
Expand Down
Loading
Loading