Storybook topology
Five Storybook installs across the monorepo — one per app or design-system project — and how visual regression hooks into one of them.
The repo has five Storybook installations, all on Storybook 10 (catalog):
| Install | Stories scope | Visual regression? |
|---|---|---|
apps/web/.storybook | App-level integration stories (composed flows, layouts) | Yes (CI gate) |
apps/admin/.storybook | Admin app components | No |
apps/onboarding/.storybook | Onboarding app components | No |
design-system/ui/.storybook | TDX UI primitives | No |
design-system/opal/.storybook | Opal flow primitives | No |
Each Storybook is independent — separate config, separate build output,
separate dev server. They share styling and decorators through
@todayai-labs/storybook,
the workspace package that exports the common preview + theme + window
primitives.
The shared preview package
@todayai-labs/storybook is consumed via subpath exports:
// .storybook/preview.ts (any of the five)
import '@todayai-labs/storybook/base.css'
export { default } from '@todayai-labs/storybook/preview'This gives every Storybook the same:
- Theme toggle (light / dark / system)
- Background palette switcher
- Window-chrome mock for native-WebView-hosted stories
- React Query devtools (in dev mode only)
When a story needs Next.js mocks (router, navigation, Image component),
apps/web and apps/admin import a separate preset:
// apps/web/.storybook/main.ts
import { withNextMocks } from '@todayai-labs/storybook/next/config'
const config: StorybookConfig = withNextMocks({
// ...
})withNextMocks wires @storybook/addon-themes and friends without each app
having to repeat the configuration.
Why five instead of one
Each Storybook is scoped to a publishing audience:
design-system/uiis the canonical reference for TDX primitives. Designers and product devs look here to pick a component.design-system/opalis the same for Opal flow primitives (the onboarding shells, narrative marks, etc.).apps/webmounts both libraries and adds app-shaped stories — a composed onboarding flow, a chat layout. This is where visual regression happens, because this is where layout matters.apps/adminandapps/onboardingStorybooks exist for the same reason — app-specific composition is easier to inspect locally than in the running app.
Consolidating them into one would mean either losing the publishing-audience boundary (designers wading through admin's CRUD UI) or losing visual regression's tight scope (snapshot diffs covering everything).
Visual regression scope
Only apps/web runs visual regression. The CI Visual Regression check:
- Builds
apps/webStorybook (pnpm storybook:build→apps/web/storybook-static/) - Boots Playwright against the static build
- Runs
*.visual.spec.tsco-located with each story - Compares against committed PNG snapshots (Git LFS)
See Playwright tiers → Visual regression for the full config.
design-system/ui and design-system/opal Storybooks don't have visual
regression yet because their stories are atomic — the layout that goes wrong
is composed-layer behavior, which is what apps/web catches. Atom-level
visual regression is queued as tier-3 improvement.
Commands
# Run a specific Storybook locally
pnpm --filter @todayai-labs/web storybook # apps/web
pnpm --filter @todayai-labs/tdx-ui storybook # design-system/ui
pnpm --filter @todayai-labs/opal storybook # design-system/opal
pnpm --filter @todayai-labs/admin storybook # apps/admin
pnpm --filter @todayai-labs/onboarding storybook # apps/onboarding
# Build static Storybook
pnpm storybook:build # apps/web (CI uses this)
# Run visual regression locally
pnpm test:visual # diff against committed snapshots
pnpm test:visual:update # regenerate snapshotsThe web Storybook is the slowest (~30 s cold start, fast HMR after) because of Agentation and react-scan dev decorators. The two design-system Storybooks are noticeably snappier.
Adding a Storybook to a new package
Most new packages should not add a Storybook. Components belong in
design-system/ui or design-system/opal where they get one of the existing
Storybooks "for free." A new Storybook is justified only when:
- The package is a deployable app with composed-layer stories that don't make sense at the primitive level
- The package is a candidate for visual regression of its own
If you do add one, copy the structure from design-system/ui/.storybook,
import the shared preview from @todayai-labs/storybook, and add Moon tasks
(storybook and build-storybook) to the new project's moon.yml.
Playwright tiers
Five Playwright configs (visual / smoke / full / preview / local) split by what they target and when CI runs them.
MSW handlers and fixtures
MSW (Mock Service Worker) is the mock-mode runtime — handlers + fixtures live under `apps/web/src/lib/msw/` and are also the source of truth for Storybook stories that need network responses.