Skip to content

Set up Storybook for the shared component catalog - #36

Draft
stevekriz wants to merge 3 commits into
codepress-mainfrom
codepress/stevekriz/setup-storybook-component-catalog-91f1ea3a
Draft

Set up Storybook for the shared component catalog#36
stevekriz wants to merge 3 commits into
codepress-mainfrom
codepress/stevekriz/setup-storybook-component-catalog-91f1ea3a

Conversation

@stevekriz

@stevekriz stevekriz commented Sep 4, 2026

Copy link
Copy Markdown

Summary

FreeCut's shared UI — the shadcn-style primitives, the inspector property controls, the timeline overlays, the brand marks — only existed inside the running editor, so there was no way to look at a component on its own, see its states side by side, or check a change against the design language without driving the whole app to the right screen. This adds Storybook as that catalog: 153 stories across all 43 shared components, plus a foundations page that shows the colour ramp, type scale, spacing, radii, elevation and easing curves the editor is actually built from. It runs beside the editor in the CodePress preview as a storybook companion, so one checkout hot-reloads both surfaces — edit a component and you see it update in the app and in the catalog at once. There were no design mockups to work from here, so the existing components and src/index.css are the design source: the stories document what ships today rather than proposing anything new, and no component or token was changed.

The repo had no Live Dev Server recipe at all, so one was bootstrapped for the editor frontend first and Storybook attached to it as a companion.

storybook-foundations-colors.png

storybook-ui-button.png

storybook-property-controls.png

storybook-editor-surfaces.png

Technical details

Storybook 10.6.0 on @storybook/react-vite, both exact-pinned in devDependencies and installed with npm against package-lock.json, matching the repo's no-caret policy. Storybook 10 is the first line that peers Vite 8 and vite-plus, which is what the repo is on — nothing was downgraded to accommodate it. Config was hand-authored rather than produced by storybook init, so no caret ranges or unrequested addon collections came along; autodocs is deliberately off and the sidebar lands on the foundations story.

Storybook gets its own Vite config (vite.storybook.config.ts) instead of loading the app's. vite.config.ts is a vite-plus config carrying lint/fmt/staged/test sections, a two-entry build (index.html + headless.html) and a build-only service-worker plugin whose closeBundle reads dist/sw.js — none of which is meaningful for a component catalog, and the last of which would run against a bundle that has no sw.js. The Storybook config is the slim subset the components actually need: @vitejs/plugin-react, @tailwindcss/vite, the @ alias, and the same dedupe: ['react', 'react-dom'] that keeps Radix on one React dispatcher. .storybook/main.ts points the builder at it via framework.options.builder.viteConfigPath.

Preview-origin contracts are wired in both configs. CodePress serves previews from a public proxied origin on 443, which breaks two things that neither storybook build nor a localhost run exercises: the host check, and the HMR client port. .storybook/main.ts sets core.allowedHosts to the production and staging preview suffixes plus the runtime-injected __VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS (all three — a non-empty allowlist disables the allow-all fallback, so listing one environment would 403 the other), and its viteFinal threads CODEPRESS_HMR_PROTOCOL/CODEPRESS_HMR_CLIENT_PORT into server.hmr. builder-vite copies core.allowedHosts into server.allowedHosts, so one declaration covers both the manager websocket and Vite's own host check. vite.config.ts gets the equivalent (allowedHosts: true plus the same HMR env wiring) for the editor frontend — the dockerfile contract requires it for Vite, since Vite's HMR client port is config-only and cannot be passed on the command line.

.storybook/preview.tsx imports @/index.css (Tailwind theme, OKLCH tokens, timeline theme extension, animation utilities) and @/i18n for its initialisation side effect, since Dialog, FloatingPanel, LanguageSwitcher and MotionBakeConfirmationDialog call useTranslation. The global decorator mounts TooltipProvider and puts every story on bg-background — FreeCut is dark-only, and this is the chrome a component sees inside the editor. preview-head.html mirrors index.html's Google Fonts links so the catalog renders in IBM Plex rather than a fallback; manager.ts sets the dark manager theme.

Dev-server recipe was created through bootstrap-dev-server (no recipe existed): one editor frontend at the repo root, thin node:22-bookworm dev image, npm exec --no -- vp dev --host "$CODEPRESS_BIND_HOST" --port "$PORT" (vp dev is vite-plus's alias for the dev server and takes Vite's own flags), and a companions[0] entry labelled storybook on port 6006 — a companion rather than a second frontend, so both surfaces share the MicroVM, checkout and node_modules and a single agent edit hot-reloads both. The exact companion command was run from the frontend's working_dir and confirmed serving (200, bound 0.0.0.0, allowed hosts logged) before it was committed. No staging_backend_url: FreeCut is local-first with no backend, so the CORS and social-auth steps of that bootstrap are genuine no-ops.

Two ratchets were adjusted so the catalog does not distort them. .fallowrc.json ignores **/*.stories.tsx: Storybook loads stories through its own glob, so every story export is unreachable from the app entries by construction, and adding one allowlist entry per story would have hollowed out the dead-code check (ignoring rather than adding them as fallow entries also stops a story from counting as "usage" of an otherwise-dead component). vite.config.ts excludes stories from coverage via [...coverageConfigDefaults.exclude, '**/*.stories.tsx'] — the defaults are spread, not replaced — because the thresholds sit ~2 points under measured coverage and a catalog is not product code.

Story authoring. One file per component, colocated, CSF3 with satisfies Meta<typeof X>; components with required props carry them in meta.args so StoryObj<typeof meta> stays strict. Each file covers the variants and states that matter — every variant/size, disabled, error, loading, long-content/overflow — using real editor content (timecodes, codec presets, clip names) rather than lorem. The foundations story resolves each token through getComputedStyle(document.documentElement), so it reports what index.css actually defines instead of a transcription that can rot.

Changes

  • Add storybook and @storybook/react-vite at exact 10.6.0, plus storybook / build:storybook npm scripts
  • Add .storybook/{main.ts,preview.tsx,preview-head.html,manager.ts} — story glob, preview-origin host allow-listing, HMR client port, global stylesheet + i18n + TooltipProvider decorator, dark manager theme, sidebar order
  • Add vite.storybook.config.ts — slim React + Tailwind + @ alias config so Storybook does not load the app's multi-entry vite-plus build config
  • Add 44 story files: 153 stories across all 26 components/ui, both brand marks, 7 property controls, 7 shared editor surfaces, the marquee overlay, and a foundations page (colour ramp, type scale, spacing, radii, elevation, easing)
  • Create .codepress/dev-server/recipe.json + Dockerfile.editor via bootstrap-dev-server, with Storybook attached as a storybook companion on port 6006
  • Wire vite.config.ts server config to the preview origin (allowedHosts, CODEPRESS_HMR_PROTOCOL/CODEPRESS_HMR_CLIENT_PORT)
  • Exclude **/*.stories.tsx from the fallow dead-code ratchet and from vitest coverage
  • Ignore storybook-static/ build output

Test plan

  • npm install, then npm run build:storybook — the production build completes (only the pre-existing INEFFECTIVE_DYNAMIC_IMPORT warnings from src/i18n/index.ts, which also appear on main).
  • npm run storybook and open http://localhost:6006 — the sidebar opens on Foundations → Design Tokens → Colors, and lists UI, Property Controls, Editor Surfaces and Brand.
  • In Foundations → Colors, confirm each swatch prints the same OKLCH value as src/index.css / src/features/timeline/theme.css (they are read live from :root, so a token edit should change this page).
  • Spot-check the interactive stories: UI/Button → Variants, UI/Combobox → Default (type to filter), UI/Toaster → Playground (fire each toast), Property Controls/SliderInput → Default (click-to-snap and drag past the ends), Property Controls/ColorPicker → With Presets.
  • Edit a component — e.g. change a class in src/components/ui/button.tsx — and confirm the open Button story hot-reloads without a manual refresh.
  • After merging, restart the CodePress preview (adding a companion changes the frontend's warm-resume identity, and the lockfile moved, so the next claim is cold) and use the Design tab's surface switcher to flip between the editor and Storybook.
  • npm run check:unused-exports and npm run test:coverage — neither should regress from the added stories.

Open items

  • AppEyedropperOverlay (src/shared/ui/property-controls/app-eyedropper.tsx) is the only shared component without a story: it samples a live preview canvas through the preview-bridge and playback stores, so there is nothing reviewable to render in isolation. Every other component in components/ui, components/brand, shared/ui, shared/ui/property-controls and shared/marquee is covered (43/44).
  • Hot reload through the public preview origin was verified locally and by config, not by editing a file against the deployed preview subdomain — the companion only gets its own subdomain after this merges and the preview is restarted. Worth one check on the first post-merge preview.
  • UI/WindowPortal → PopOut opens a real second browser window, so it depends on pop-ups being allowed for the Storybook origin; the story surfaces a blocked pop-up rather than failing silently.
  • The catalog surveyed only the shared library. Ad-hoc UI still living inline in feature code (repeated inspector rows, one-off badges and pills under src/features/**) was left alone — extracting it into the library is a separate change, one reviewable batch at a time.
  • vite.config.ts was not wired to CODEPRESS_BASE_PATH: host-routed previews always serve at /, and adding a dynamic base to a config whose service worker and web manifest assume / seemed a worse trade than leaving it.

Authors

Generated with CodePress · View the chat session

codepress-dev Bot and others added 2 commits September 4, 2026 16:33
Sets up Storybook 10 on @storybook/react-vite for the existing component
library, plus the Live Dev Server recipe that runs it as a `storybook`
companion beside the editor preview.

- Storybook gets its own slim Vite config rather than the app's vite-plus
  config, which carries lint/fmt/test sections, a two-entry build and a
  build-only service-worker plugin that has no place in a component catalog.
- The preview origin contracts (host allow-listing, HMR client port) are
  wired in both `.storybook/main.ts` and `vite.config.ts` so hot reload
  survives the CodePress proxy.

Co-authored-by: stevekriz <38476831+stevekriz@users.noreply.github.com>
Covers all 43 shared components — every `components/ui` primitive and
composite, both brand marks, the property-control widgets, and the shared
editor surfaces (playhead marks, shuttle indicator, marquee and pick-whip
overlays, scrollbar overlay) — plus a foundations page that reads the OKLCH
tokens, type scale, spacing, radii, elevation and easing curves live out of
`src/index.css` so it cannot drift from what ships.

Only `AppEyedropperOverlay` is left uncovered: it samples a live preview
canvas through the preview-bridge and playback stores, so there is nothing
reviewable to render in isolation.

Stories are excluded from the fallow dead-code ratchet (Storybook loads them
through its own glob, so their exports are unreachable from the app entries by
construction) and from coverage (a catalog is not product code).

Co-authored-by: stevekriz <38476831+stevekriz@users.noreply.github.com>
@stevekriz stevekriz self-assigned this Sep 4, 2026
@codepress-dev codepress-dev Bot added the cp:ready-for-review CodePress: handoff complete; native review readiness is separate label Sep 4, 2026
`npm run verify:provenance` pins package.json and package-lock.json by
checksum and requires the dependency inventory's direct dependencies to equal
package.json's, so adding storybook and @storybook/react-vite invalidated the
baseline. Refresh both checksums and add the two devDependencies to the
inventory.

Verified with the exact CI sequence from .github/workflows/reproducible-package.yml:
npm ci --ignore-scripts, npm run verify:provenance, then npm run
package:reproducible twice — both runs produced
sha256 b827de7fb540d01e4ac0ed5391acf406779df3182186d39449cdda17054013e5 and
compared byte-identical.

Co-authored-by: stevekriz <38476831+stevekriz@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cp:ready-for-review CodePress: handoff complete; native review readiness is separate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant