Skip to content

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 check surfaced no Storybook/Vite/React peer conflict).
  • pnpm --filter @gw2priory/web exec storybook build printed Storybook build completed successfully, and its reporter referenced build.rolldownOptions.output.codeSplitting — i.e. it ran through Vite 8's Rolldown pipeline, not a fallback. It emitted storybook-static/assets/Button.stories-*.js.
  • Web corroboration: @storybook/react-vite latest 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-primary and --colors-surface (token custom properties), every button--variant_{solid,outline,ghost} and button--size_{sm,md} (recipe classes), and a @media (prefers-color-scheme: dark) block.
  • _osDark compiles to @media (prefers-color-scheme: dark) (panda.config.ts:43-72 define surface, 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.tsx errored: 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 check passed: Checked 1 file … No fixes applied. (Button matches PascalCase.)

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-*.js contains useMemoCache — the React Compiler runtime — so reactCompilerPreset from vite.config.ts applied, and storybook build still completed. Consequence: panicThreshold: 'all_errors' protection extends to Storybook — a bail-out would fail storybook build, same as pnpm build.
  • Vitest excludes stories: apps/web/vitest.config.ts:20 include: ['src/**/*.test.ts', 'src/**/*.test.tsx'] — *.stories.tsx does not match.
  • Stories are typechecked: apps/web/tsconfig.json includes src/**/*.tsx; tsc -p tsconfig.json --noEmit passed 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 imports src/index.css (V2), the compiler runs in the Storybook build (V4), dark mode is viewed via prefers-color-scheme emulation and deliberately has no class toggle (V2), and stories cannot import styled-system (V3).
  • react.md — the *.stories.tsx Biome filename override (V3) joins the naming-convention notes; the Vitest include / tsconfig-includes-stories facts (V4) are worth a line so a later reader knows stories typecheck but never run as tests.