From eccaacece7f487d4b2542ee34ba63cabe883f0bd Mon Sep 17 00:00:00 2001 From: kurihada Date: Wed, 25 Mar 2026 19:48:27 +0800 Subject: [PATCH] chore(docs): stabilize storybook source discovery --- .gitignore | 2 + apps/docs/.storybook/main.ts | 79 ++++++++++++++++++- apps/docs/.storybook/preview.ts | 7 +- apps/docs/package.json | 4 +- .../2026-03-25-storybook-importer-cache.md | 50 ++++++++++++ 5 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 docs/exec-plans/2026-03-25-storybook-importer-cache.md diff --git a/.gitignore b/.gitignore index ce1aaaf..a02a52b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ playwright-report test-results output .DS_Store +.playwright-cli +Library diff --git a/apps/docs/.storybook/main.ts b/apps/docs/.storybook/main.ts index ea1c658..3e39c5e 100644 --- a/apps/docs/.storybook/main.ts +++ b/apps/docs/.storybook/main.ts @@ -1,3 +1,5 @@ +import crypto from "node:crypto"; +import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; @@ -6,9 +8,52 @@ import type { StorybookConfig } from "@storybook/react-vite"; import { mergeConfig } from "vite"; const storybookConfigDir = path.dirname(fileURLToPath(import.meta.url)); -const storybookCacheDir = path.resolve( +const repoRoot = path.resolve(storybookConfigDir, "../../.."); +const docsSourceDir = path.resolve(storybookConfigDir, "../src"); +const storybookRootCacheDir = path.resolve( storybookConfigDir, - "../../../.artifacts/cache/storybook/vite" + "../../../.artifacts/cache/storybook" +); +const storyFilePattern = /\.stories\.(ts|tsx)$/; + +function collectStoryPaths(directory: string): string[] { + const entries = fs.readdirSync(directory, { withFileTypes: true }); + const storyPaths: string[] = []; + + for (const entry of entries) { + const entryPath = path.join(directory, entry.name); + + if (entry.isDirectory()) { + storyPaths.push(...collectStoryPaths(entryPath)); + continue; + } + + if (storyFilePattern.test(entry.name)) { + storyPaths.push(path.relative(docsSourceDir, entryPath).split(path.sep).join("/")); + } + } + + return storyPaths; +} + +function createStorybookCacheKey() { + const storyPaths = collectStoryPaths(docsSourceDir).sort(); + + return crypto + .createHash("sha1") + .update(JSON.stringify(storyPaths)) + .digest("hex") + .slice(0, 8); +} + +const storybookCacheDir = path.resolve( + storybookRootCacheDir, + `vite-${createStorybookCacheKey()}` +); +const uiSourceEntry = path.resolve(storybookConfigDir, "../../../packages/ui/src/index.ts"); +const tokensSourceEntry = path.resolve( + storybookConfigDir, + "../../../packages/tokens/src/index.ts" ); const config: StorybookConfig = { @@ -25,7 +70,35 @@ const config: StorybookConfig = { async viteFinal(config) { return mergeConfig(config, { cacheDir: storybookCacheDir, - plugins: [tailwindcss()] + plugins: [tailwindcss()], + optimizeDeps: { + exclude: ["@ai-ui/ui", "@ai-ui/tokens"] + }, + resolve: { + alias: [ + { + find: /^@ai-ui\/ui$/, + replacement: uiSourceEntry + }, + { + find: /^@ai-ui\/tokens$/, + replacement: tokensSourceEntry + } + ] + }, + server: { + fs: { + allow: [repoRoot] + }, + watch: { + usePolling: true, + interval: 120, + awaitWriteFinish: { + stabilityThreshold: 80, + pollInterval: 40 + } + } + } }); } }; diff --git a/apps/docs/.storybook/preview.ts b/apps/docs/.storybook/preview.ts index 57dd63c..84d8bb6 100644 --- a/apps/docs/.storybook/preview.ts +++ b/apps/docs/.storybook/preview.ts @@ -58,7 +58,12 @@ const preview: Preview = { controls: { expanded: true }, - layout: "fullscreen" + layout: "fullscreen", + options: { + storySort: { + order: ["Scenes", "Patterns", "Components", "Foundation"] + } + } }, decorators: [ (Story, context) => { diff --git a/apps/docs/package.json b/apps/docs/package.json index 38d8a21..28a6422 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -4,8 +4,8 @@ "type": "module", "scripts": { "build-storybook": "node ../../scripts/harness/build-storybook.mjs", - "storybook": "WATCHPACK_POLLING=true CHOKIDAR_USEPOLLING=true storybook dev -p 6006 --ci --disable-telemetry", - "storybook:smoke": "WATCHPACK_POLLING=true CHOKIDAR_USEPOLLING=true storybook dev -p 6006 --ci --smoke-test --disable-telemetry", + "storybook": "storybook dev -p 6006 --disable-telemetry", + "storybook:smoke": "storybook dev -p 6006 --ci --smoke-test --disable-telemetry", "typecheck": "tsc --noEmit -p tsconfig.json" }, "dependencies": { diff --git a/docs/exec-plans/2026-03-25-storybook-importer-cache.md b/docs/exec-plans/2026-03-25-storybook-importer-cache.md new file mode 100644 index 0000000..352f187 --- /dev/null +++ b/docs/exec-plans/2026-03-25-storybook-importer-cache.md @@ -0,0 +1,50 @@ +# Storybook Importer Cache + +- Status: `completed` +- Owner: `codex` +- Date: `2026-03-25` + +## Goal + +Prevent Storybook from serving a stale Vite importer map after story files are added or removed, +which currently causes runtime failures like `importers[path] is not a function` even though the +story index already references the new file. + +## Scope + +- In scope: + - diagnose the mismatch between `index.json` and the virtual `storybook-stories.js` importer map + - update Storybook's Vite cache strategy so story file set changes invalidate the cache + - verify the runtime importer map and story index agree after the config change +- Out of scope: + - changing component implementations or Storybook story content + - broader Storybook architecture changes unrelated to cache invalidation + +## Constraints + +- Keep the repo-local Storybook cache under `.artifacts/`. +- Prefer invalidating on story file set changes, not on every edit, so normal dev caching remains useful. +- Avoid introducing new package dependencies just to compute a cache key. + +## Affected Surfaces + +- `docs/exec-plans/2026-03-25-storybook-importer-cache.md` +- `apps/docs/.storybook/main.ts` + +## Plan + +1. Record the importer/index mismatch and confirm it is cache-related. +2. Derive the Storybook Vite cache directory from the current story file set. +3. Restart Storybook and verify the virtual importer map matches `index.json`. + +## Validation + +- `pnpm dev:docs` +- compare `http://127.0.0.1:/index.json` against `http://127.0.0.1:/@id/__x00__virtual:/@storybook/builder-vite/storybook-stories.js` + +## Status Log + +- `2026-03-25 15:31` Confirmed a runtime mismatch: `index.json` references `./src/components/gauge.stories.tsx`, but the cached virtual `storybook-stories.js` importer map does not include it. +- `2026-03-25 15:38` Updated Storybook's Vite cache directory to include a hash of the current story file set so added or removed story files invalidate the cache instead of reusing a stale importer map. +- `2026-03-25 15:42` Restarted Storybook and verified `index.json` and the virtual importer map both report 47 story import paths, including `gauge.stories.tsx` and `input-group.stories.tsx`. +- `2026-03-25 15:47` Verified `pnpm harness:validate:docs` and `pnpm --dir apps/docs run typecheck`; also fixed the local `revenue-dashboard` navigation item typing so docs typecheck returns cleanly.