Set up Storybook for the shared component catalog - #36
Draft
stevekriz wants to merge 3 commits into
Draft
Conversation
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>
`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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
storybookcompanion, 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 andsrc/index.cssare 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.
Technical details
Storybook 10.6.0 on
@storybook/react-vite, both exact-pinned indevDependenciesand installed with npm againstpackage-lock.json, matching the repo's no-caret policy. Storybook 10 is the first line that peers Vite 8 andvite-plus, which is what the repo is on — nothing was downgraded to accommodate it. Config was hand-authored rather than produced bystorybook init, so no caret ranges or unrequested addon collections came along;autodocsis 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.tsis a vite-plus config carryinglint/fmt/staged/testsections, a two-entry build (index.html+headless.html) and a build-only service-worker plugin whosecloseBundlereadsdist/sw.js— none of which is meaningful for a component catalog, and the last of which would run against a bundle that has nosw.js. The Storybook config is the slim subset the components actually need:@vitejs/plugin-react,@tailwindcss/vite, the@alias, and the samededupe: ['react', 'react-dom']that keeps Radix on one React dispatcher..storybook/main.tspoints the builder at it viaframework.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 buildnor a localhost run exercises: the host check, and the HMR client port..storybook/main.tssetscore.allowedHoststo 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 itsviteFinalthreadsCODEPRESS_HMR_PROTOCOL/CODEPRESS_HMR_CLIENT_PORTintoserver.hmr.builder-vitecopiescore.allowedHostsintoserver.allowedHosts, so one declaration covers both the manager websocket and Vite's own host check.vite.config.tsgets the equivalent (allowedHosts: trueplus 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.tsximports@/index.css(Tailwind theme, OKLCH tokens, timeline theme extension, animation utilities) and@/i18nfor its initialisation side effect, sinceDialog,FloatingPanel,LanguageSwitcherandMotionBakeConfirmationDialogcalluseTranslation. The global decorator mountsTooltipProviderand puts every story onbg-background— FreeCut is dark-only, and this is the chrome a component sees inside the editor.preview-head.htmlmirrorsindex.html's Google Fonts links so the catalog renders in IBM Plex rather than a fallback;manager.tssets the dark manager theme.Dev-server recipe was created through
bootstrap-dev-server(no recipe existed): oneeditorfrontend at the repo root, thinnode:22-bookwormdev image,npm exec --no -- vp dev --host "$CODEPRESS_BIND_HOST" --port "$PORT"(vp devis vite-plus's alias for the dev server and takes Vite's own flags), and acompanions[0]entry labelledstorybookon port 6006 — a companion rather than a second frontend, so both surfaces share the MicroVM, checkout andnode_modulesand a single agent edit hot-reloads both. The exact companion command was run from the frontend'sworking_dirand confirmed serving (200, bound0.0.0.0, allowed hosts logged) before it was committed. Nostaging_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.jsonignores**/*.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.tsexcludes 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 inmeta.argssoStoryObj<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 throughgetComputedStyle(document.documentElement), so it reports whatindex.cssactually defines instead of a transcription that can rot.Changes
storybookand@storybook/react-viteat exact 10.6.0, plusstorybook/build:storybooknpm scripts.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 ordervite.storybook.config.ts— slim React + Tailwind +@alias config so Storybook does not load the app's multi-entry vite-plus build configcomponents/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).codepress/dev-server/recipe.json+Dockerfile.editorvia bootstrap-dev-server, with Storybook attached as astorybookcompanion on port 6006vite.config.tsserver config to the preview origin (allowedHosts,CODEPRESS_HMR_PROTOCOL/CODEPRESS_HMR_CLIENT_PORT)**/*.stories.tsxfrom the fallow dead-code ratchet and from vitest coveragestorybook-static/build outputTest plan
npm install, thennpm run build:storybook— the production build completes (only the pre-existingINEFFECTIVE_DYNAMIC_IMPORTwarnings fromsrc/i18n/index.ts, which also appear onmain).npm run storybookand open http://localhost:6006 — the sidebar opens on Foundations → Design Tokens → Colors, and lists UI, Property Controls, Editor Surfaces and Brand.src/index.css/src/features/timeline/theme.css(they are read live from:root, so a token edit should change this page).src/components/ui/button.tsx— and confirm the open Button story hot-reloads without a manual refresh.npm run check:unused-exportsandnpm 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 incomponents/ui,components/brand,shared/ui,shared/ui/property-controlsandshared/marqueeis covered (43/44).UI/WindowPortal → PopOutopens 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.src/features/**) was left alone — extracting it into the library is a separate change, one reviewable batch at a time.vite.config.tswas not wired toCODEPRESS_BASE_PATH: host-routed previews always serve at/, and adding a dynamicbaseto a config whose service worker and web manifest assume/seemed a worse trade than leaving it.Authors
Generated with CodePress · View the chat session