Skip to content

Commit 195ea86

Browse files
committed
build: set up Storybook and Chromatic for the report UI
Adds a root .storybook config (React + Tailwind v4 via viteFinal) with stories for the report App, plus the storybook/build-storybook/chromatic scripts and dev dependencies. The report's styles.css is reused so Storybook shares the same Tailwind compilation. tsconfig now includes the .storybook dot-directory (so it is typechecked and visible to the eslint project service), and storybook-static is ignored by git, prettier, and eslint.
1 parent 6972a3d commit 195ea86

10 files changed

Lines changed: 402 additions & 8 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ dist
77
dist-release
88
*.tgz
99

10+
# Storybook build output (published to Chromatic in CI)
11+
storybook-static
12+
1013
# dev-only fixture for the report web app
1114
src/report/web/data/fixture.json
1215

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
dist/
22
node_modules/
33
coverage/
4+
storybook-static/
45
claude-tmp/
56
bun.lock
67
.husky/_/

.storybook/main.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { StorybookConfig } from '@storybook/react-vite';
2+
import tailwindcss from '@tailwindcss/vite';
3+
import { mergeConfig } from 'vite';
4+
5+
const config: StorybookConfig = {
6+
stories: ['../src/report/web/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
7+
addons: [],
8+
framework: '@storybook/react-vite',
9+
// The report's production build uses bun-plugin-tailwind, but Storybook runs
10+
// on Vite, so the Tailwind v4 Vite plugin is wired in here rather than via a
11+
// root vite.config.ts (which would also leak into the vitest browser config).
12+
viteFinal: (viteConfig) => mergeConfig(viteConfig, { plugins: [tailwindcss()] }),
13+
};
14+
15+
export default config;

.storybook/preview.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/* The report's stylesheet owns `@import "tailwindcss"`, the design tokens, and
2+
fonts; re-import it so Storybook shares the same Tailwind compilation. The
3+
@source directive lists the report tree explicitly so Tailwind scans the
4+
components and stories for class names regardless of the Vite root. */
5+
6+
@import '../src/report/web/styles.css';
7+
8+
@source '../src/report/web/**/*.{ts,tsx}';

.storybook/preview.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import './preview.css';
2+
import type { Preview } from '@storybook/react-vite';
3+
4+
const preview: Preview = {
5+
parameters: {
6+
controls: {
7+
matchers: {
8+
color: /(background|color)$/i,
9+
date: /Date$/i,
10+
},
11+
},
12+
backgrounds: { disable: true },
13+
options: {
14+
storySort: {
15+
method: 'alphabetical',
16+
},
17+
},
18+
},
19+
};
20+
21+
export default preview;

bun.lock

Lines changed: 291 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

eslint.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const processRestrictedProperties = [
4949
export default defineConfig(
5050
...baseConfig,
5151
{
52-
ignores: ['dist/**', 'node_modules/**', 'coverage/**', 'claude-tmp/**', 'bun.lock'],
52+
ignores: ['dist/**', 'node_modules/**', 'coverage/**', 'storybook-static/**', 'claude-tmp/**', 'bun.lock'],
5353
},
5454
{
5555
rules: {

package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
"prepack": "bun run build:report-web",
3030
"build:report-web": "bun run scripts/build-report-web.ts",
3131
"dev:report-web": "bun ./src/report/web/index.dev.html",
32+
"storybook": "storybook dev -p 6006",
33+
"build-storybook": "storybook build --stats-json",
34+
"chromatic": "chromatic",
3235
"build:darwin-arm64": "bun run build:report-web && bun build --compile --target=bun-darwin-arm64 ./src/index.ts --outfile dist/patchwave-analysis-darwin-arm64",
3336
"build:darwin-x64": "bun run build:report-web && bun build --compile --target=bun-darwin-x64 ./src/index.ts --outfile dist/patchwave-analysis-darwin-x64",
3437
"build:linux-x64": "bun run build:report-web && bun build --compile --target=bun-linux-x64 ./src/index.ts --outfile dist/patchwave-analysis-linux-x64"
@@ -43,6 +46,8 @@
4346
"@contextbridge-ai/prettier-config": "^0.0.0",
4447
"@fontsource/ibm-plex-mono": "^5.2.7",
4548
"@fontsource/ibm-plex-sans": "^5.2.8",
49+
"@storybook/react-vite": "^10.2.14",
50+
"@tailwindcss/vite": "^4.3.0",
4651
"@testing-library/jest-dom": "^6.9.1",
4752
"@testing-library/react": "^16.3.2",
4853
"@vitejs/plugin-react": "^5.1.1",
@@ -52,13 +57,15 @@
5257
"@types/react-dom": "^19",
5358
"@types/semver": "^7.7.1",
5459
"bun-plugin-tailwind": "^0.1.2",
60+
"chromatic": "^15.1.1",
5561
"eslint": "^10.4.0",
5662
"fishery": "^2.4.0",
5763
"globals": "^17.6.0",
5864
"husky": "^9.1.7",
5965
"lint-staged": "^17.0.5",
6066
"prettier": "^3.8.3",
6167
"playwright": "^1.58.2",
68+
"storybook": "^10.2.14",
6269
"tailwindcss": "^4",
6370
"typescript": "^6.0.3",
6471
"typescript-eslint": "^8.59.4",

src/report/web/App.stories.tsx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import type { Meta, StoryObj } from '@storybook/react-vite';
2+
import { toEmbeddedShape } from '../embeddedShape.ts';
3+
import { cveExposureOk, cveExposureScopeMissing, orgOverview, reportBundle } from '../testFactories.ts';
4+
import { App } from './App.tsx';
5+
6+
// A representative report, built from the same fishery factories the tests use.
7+
// Defaults cover most of the page; the overrides here only fill the spots that
8+
// would otherwise render as single-row tables (language mix, top repos by
9+
// severity) so the snapshot exercises the full UI.
10+
const sampleReport = toEmbeddedShape(
11+
reportBundle.build({
12+
orgOverview: orgOverview.build({
13+
topLanguages: [
14+
{ language: 'TypeScript', bytes: 4_200_000, percentage: 58 },
15+
{ language: 'Go', bytes: 1_600_000, percentage: 22 },
16+
{ language: 'Python', bytes: 880_000, percentage: 12 },
17+
{ language: 'Ruby', bytes: 560_000, percentage: 8 },
18+
],
19+
}),
20+
cve: cveExposureOk.build({
21+
topReposBySeverity: [
22+
{ repo: 'acme/api', critical: 1, high: 2, medium: 1, low: 0 },
23+
{ repo: 'acme/web', critical: 0, high: 1, medium: 3, low: 2 },
24+
{ repo: 'acme/billing', critical: 0, high: 0, medium: 2, low: 5 },
25+
],
26+
reposWithSecurityAlertsDisabled: ['acme/legacy-cron'],
27+
}),
28+
}),
29+
);
30+
31+
const meta = {
32+
title: 'Report/App',
33+
component: App,
34+
parameters: {
35+
layout: 'fullscreen',
36+
},
37+
} satisfies Meta<typeof App>;
38+
39+
export default meta;
40+
type Story = StoryObj<typeof meta>;
41+
42+
export const Default: Story = {
43+
args: {
44+
data: sampleReport,
45+
},
46+
};
47+
48+
export const CveScopeMissing: Story = {
49+
args: {
50+
data: { ...sampleReport, cve: cveExposureScopeMissing.build() },
51+
},
52+
};

tsconfig.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,8 @@
2727
"noUnusedParameters": false,
2828
"noPropertyAccessFromIndexSignature": false
2929
},
30+
// `.storybook` is a dot-directory, so the implicit `**/*` include skips it
31+
// (which would also hide it from typecheck and the eslint project service).
32+
"include": ["**/*", ".storybook/**/*"],
3033
"exclude": ["node_modules", "dist", "claude-tmp", "out"]
3134
}

0 commit comments

Comments
 (0)