Today Platform Web — Dev Docs
ToolchainTesting

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):

InstallStories scopeVisual regression?
apps/web/.storybookApp-level integration stories (composed flows, layouts)Yes (CI gate)
apps/admin/.storybookAdmin app componentsNo
apps/onboarding/.storybookOnboarding app componentsNo
design-system/ui/.storybookTDX UI primitivesNo
design-system/opal/.storybookOpal flow primitivesNo

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/ui is the canonical reference for TDX primitives. Designers and product devs look here to pick a component.
  • design-system/opal is the same for Opal flow primitives (the onboarding shells, narrative marks, etc.).
  • apps/web mounts 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/admin and apps/onboarding Storybooks 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:

  1. Builds apps/web Storybook (pnpm storybook:buildapps/web/storybook-static/)
  2. Boots Playwright against the static build
  3. Runs *.visual.spec.ts co-located with each story
  4. 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 snapshots

The 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:

  1. The package is a deployable app with composed-layer stories that don't make sense at the primitive level
  2. 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.

On this page