Research 022 — Storybook design-system workbench
Status: complete All four [NEEDS VERIFICATION] markers in spec.md have a verdict below. None was refuted, so the spec does not go back to step 1. One finding refined a criterion rather than refuting it: V2's dark-mode mechanism (media-query, not a class toggle) sharpened SC3/P1 #3 wording in the spec — noted there, not a premise change.
Verified against. A discarded install-and-build spike on branch 022-storybook: storybook@10.5.8 and @storybook/react-vite@10.5.8 added as apps/web devDeps, a minimal .storybook/{main,preview}.ts, one Button.stories.tsx, and postcss.config.cjs flipped to array syntax — then fully reverted (git status clean but for the spec templates; the spike does not survive into the tree, per the constitution's discovery-spike rule). Stack under test: Vite 8.1.5 (Rolldown/Oxc), Panda 1.11.5 via @pandacss/dev/postcss, the React Compiler via @rolldown/plugin-babel + reactCompilerPreset({ panicThreshold: 'all_errors' }) (apps/web/vite.config.ts:14-19), Biome 2.5.5. Web facts cited where used. Date: 2026-08-17.
V1 — Does Storybook's Vite builder build against this app's Vite 8 (Rolldown/Oxc)? (spec R1)
Question. R1 assumes a Storybook version whose Vite builder runs on Vite 8.1.5, which is Rolldown/Oxc rather than Rollup+esbuild. If no released builder supports Vite 8, the approach is refuted.
Verdict. Confirmed. storybook build completes on this exact app with @storybook/react-vite 10.5.8 and Vite 8.1.5.
Evidence.
- Spike install added
storybook@10.5.8+@storybook/react-vite@10.5.8(74 packages;pnpm peers checksurfaced no Storybook/Vite/React peer conflict). pnpm --filter @gw2priory/web exec storybook buildprintedStorybook build completed successfully, and its reporter referencedbuild.rolldownOptions.output.codeSplitting— i.e. it ran through Vite 8's Rolldown pipeline, not a fallback. It emittedstorybook-static/assets/Button.stories-*.js.- Web corroboration:
@storybook/react-vitelatest is 10.5.7+ (npm, Aug 2026), requires Vite ≥5 and is documented compatible with Vite 8 (Rolldown-powered).
Caveat. Only the static storybook build was exercised, not the interactive storybook dev server. Both drive the same @storybook/builder-vite, so residual risk is low, but the plan's manual check (SC1) should boot storybook dev once to close it.
V2 — Do Panda's generated CSS, tokens, and the _osDark layer load in the Storybook preview? (spec R2)
Question. R2 needs the preview to apply the same Panda styling the app resolves — token custom properties, recipe classes, and the dark-mode layer — so components don't render unstyled.
Verdict. Confirmed, with a dark-mode mechanism refinement. Importing ../src/index.css into .storybook/preview.ts (with array-syntax PostCSS — F1) puts the full Panda output into the preview bundle. Dark mode is media-query-driven, so it follows prefers-color-scheme, not a toolbar class toggle.
Evidence.
- The built
storybook-static/assets/iframe-*.css(23 kB) contains, by grep:--colors-primaryand--colors-surface(token custom properties), everybutton--variant_{solid,outline,ghost}andbutton--size_{sm,md}(recipe classes), and a@media (prefers-color-scheme: dark)block. _osDarkcompiles to@media (prefers-color-scheme: dark)(panda.config.ts:43-72definesurface,text.strong, etc. with_osDark;design-system.md§Semantic tokens). So the dark values render when the browser reports dark — via the OS setting or DevTools "Emulate CSS prefers-color-scheme: dark" (Chrome/Edge).
Caveat / refinement. A class/data-attribute addon (@storybook/addon-themes withThemeByClassName) toggles a selector and would not trigger _osDark; adding a _dark class variant is explicitly rejected by design-system.md (dead weight — nothing sets the class). So the workbench does not get a one-click in-toolbar dark toggle for free; dark is viewed via prefers-color-scheme emulation. This refined SC3/P1 #3 in the spec ("under prefers-color-scheme: dark") and keeps the addon out (ponytail: DevTools already emulates the media feature; no bespoke addon earns its place for a local tool).
V3 — Does Biome reject *.stories.tsx, and does a scoped override admit it without dropping other rules? (spec R4)
Question. useFilenamingConvention reads the pre-dot segment, so Button.stories.tsx is checked as Button — not an export in a CSF file, not camelCase. R4 assumes a scoped override fixes it the way the __tests__ override does, without silently dropping the app-wide naming rule or the import bans.
Verdict. Confirmed. The current config rejects the story; a apps/web/**/*.stories.tsx override adding PascalCase admits it, and the import bans still bind because overrides are per-rule.
Evidence.
- Under the committed
biome.json,biome check src/shared/ui/Button.stories.tsxerrored:lint/style/useFilenamingConvention — The filename should be in camelCase or equal to the name of an export. - Adding an override
{"includes": ["apps/web/**/*.stories.tsx"], … "filenameCases": ["export", "camelCase", "PascalCase"]}after the__tests__override,biome checkpassed:Checked 1 file … No fixes applied.(ButtonmatchesPascalCase.)
Caveat / consequence. The override only names useFilenamingConvention; the apps/web/src/**noRestrictedImports ban on **/styled-system/** still applies to a story (a later override replaces only the rule it names — react.md). So a story cannot import styled-system — layout inside a story uses plain elements or a Storybook decorator, never css(). This matches R3/SC2's "imports nothing from styled-system" and is a real authoring constraint for the plan.
V4 — Does the React Compiler run in Storybook's build, and are CSF files safe from panicThreshold and Vitest? (spec R5)
Question. R5 needs the app pipeline to stay green: the React Compiler build must not break, stories must typecheck, and Vitest must not treat stories as tests.
Verdict. Confirmed. @storybook/builder-vite loads the app's vite.config.ts, so the React Compiler runs in Storybook too and the build still passes; stories are typechecked and are not swept by Vitest.
Evidence.
- Compiler runs in Storybook: the built
storybook-static/assets/Button.stories-*.jscontainsuseMemoCache— the React Compiler runtime — soreactCompilerPresetfromvite.config.tsapplied, andstorybook buildstill completed. Consequence:panicThreshold: 'all_errors'protection extends to Storybook — a bail-out would failstorybook build, same aspnpm build. - Vitest excludes stories:
apps/web/vitest.config.ts:20include: ['src/**/*.test.ts', 'src/**/*.test.tsx']—*.stories.tsxdoes not match. - Stories are typechecked:
apps/web/tsconfig.jsonincludessrc/**/*.tsx;tsc -p tsconfig.json --noEmitpassed with the spike story present. So story files must be type-clean.
Caveat. CSF meta/story exports are plain objects, not components/hooks, so the compiler has nothing to transform in the story file itself; any bail-out risk lives in the imported components, which the app build already compiles. No viteFinal to strip the compiler is needed — defaults build clean (ponytail: leave it).
F1 — Storybook's Vite builder requires array-syntax PostCSS, and it is backward-compatible with the app build
postcss.config.cjs is object syntax today (plugins: { '@pandacss/dev/postcss': {} }). Panda's official Storybook guide requires array syntax (plugins: [require('@pandacss/dev/postcss')()]) for the Vite builder. In the spike, flipping to array syntax kept pnpm --filter @gw2priory/web build and tsc green — so one shared postcss.config.cjs (array syntax) serves both the app and Storybook; no Storybook-specific PostCSS config is needed.
Why it matters. R2. The implementation changes postcss.config.cjs to array syntax as a prerequisite, and its comment (which currently cites object syntax) is updated.
Refuted claims
None. Every marker confirmed. V2's dark-mode finding refined SC3/P1 #3 wording (media-query mechanism) but did not falsify a premise, so the spec did not return to step 1.
Graduation
Findings that outlive this feature, for step 6 to move into docs/architecture/ (checklist, not memory):
design-system.md§"Interim surface" is rewritten: Storybook is the catalogue home; the integration facts replace the/ui-stopgap note — array-syntax PostCSS (F1), preview importssrc/index.css(V2), the compiler runs in the Storybook build (V4), dark mode is viewed viaprefers-color-schemeemulation and deliberately has no class toggle (V2), and stories cannot importstyled-system(V3).react.md— the*.stories.tsxBiome filename override (V3) joins the naming-convention notes; the Vitestinclude/ tsconfig-includes-stories facts (V4) are worth a line so a later reader knows stories typecheck but never run as tests.