diff --git a/.claude/rules/directory-structure.md b/.claude/rules/directory-structure.md index 2d6e124c..271f4481 100644 --- a/.claude/rules/directory-structure.md +++ b/.claude/rules/directory-structure.md @@ -22,6 +22,8 @@ paths: - `routes//-` で始まるディレクトリ (`-components/` `-lib/` `-hooks/`) の中の import は相対パスで書く - 一覧テーブルは `DataTable` (`src/components/parts/`) に列定義と data を渡す。列定義は `createColumnHelper` で `-lib/<画面>-columns.ts` (横断なら `src/features//`) に書き、cell の描画は `-components/` の部品を参照で渡す (ADR-0019) - 部品と画面の取り違えは機械で止まらない。判断軸は役割: registry や自作部品を包んで外見そのものを定義するなら `parts/`、既存の部品を並べて画面を組むだけなら `screens/`。消費者が 1 つでも、外見の定義を担うなら `parts/` (ADR-0020) +- story は部品と同じディレクトリに `<部品>.stories.tsx` で置く。`src/components/ui/` も同じで、baseline の対象外になる (ADR-0006 / ADR-0022) +- story を置けるのは `src/components/` 配下だけ。`.storybook/main.ts` の `stories` がそこしか見ないので、`features/` や `routes/**/-components/` へ置いても無言で対象から外れる (ADR-0022) - className を書きたくなったこと自体は `parts/` への移動理由にならない。静的か動的かを問わない。層の規則から外れるために移すのは逆 (ADR-0020 / ADR-0021) - `routes/` の階層は URL の設計であってドメインの区切りではない。ドメインの画面が 1 つの URL サブツリーに収まる保証は無いので、ドメイン固有の共有部品を `routes/` 側へ置かない (ADR-0012) - route ファイルの rename / 移動時、`createFileRoute` のパス文字列は plugin が自動更新する。手で書き換えない @@ -53,10 +55,13 @@ paths: | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ドメインを跨ぐもの (render の型、a11y、mock) | `src/test/` | | 特定の部品の locator や fixture | 部品と同じディレクトリの `<部品>.test-helpers.ts`。`routes/` 配下では対象と同じ `-components/` か `-lib/` に置き、route ファイル自身の helper は route ファイルの隣に置く | +| story だけが使うロジック | story と同じディレクトリの `<名前>.story-helpers.ts`。`src/lib/` へ置かない。プロダクトコードと読まれ、誰かが import すれば出荷される (ADR-0022) | -- `*.test-helpers.ts` は `vp test` の include に一致せず、coverage からも除外する (`vitest.config.ts`) +- 付随ファイル (`*.test.*` / `*.test-helpers.*` / `*.story-helpers.*` / `*.stories.*`) の種別は `scripts/lib/companion-files.ts` が唯一の定義で、lint の適用外・coverage の除外・registry baseline の突き合わせはそこから導出する。種別を足すときはそこだけを直す +- `*.test-helpers.ts` / `*.story-helpers.ts` は `vp test` の include (`vitest.config.ts`) に一致せず、テストとして収集されない - route ファイルの隣に置いた `*.test-helpers.ts` は `routeFileIgnorePattern` (`vite.config.ts`) が route ファイル扱いから外す。`-` で始まるディレクトリの中は元から除外される -- `*.test-helpers.ts` と `src/test/` をアプリのコードから import しない。型しか引かない helper は build を壊さず、fixture が bundle に入る。lint の `no-restricted-imports` が止め、テストと helper と `src/test/` は対象外 (ADR-0004) +- `*.test-helpers.ts` / `*.story-helpers.ts` / `src/test/` をアプリのコードから import しない。型しか引かない helper は build を壊さず、fixture が bundle に入る。lint の `no-restricted-imports` が止め、テストと story と helper は対象外 (ADR-0004) +- helper のテストの置き方は対象の実行環境で決める。DOM が要るものは `*.test.tsx` (browser project)、純粋なものは `*.test.ts` (unit project) ## shadcn コンポーネント導入時のチェック diff --git a/.claude/rules/styling.md b/.claude/rules/styling.md index 2f2e044d..21802c09 100644 --- a/.claude/rules/styling.md +++ b/.claude/rules/styling.md @@ -2,6 +2,7 @@ paths: - "src/**/*.tsx" - "src/**/*.css" + - ".storybook/**" --- # スタイリング規約 @@ -37,6 +38,7 @@ Why: token 経由なら dark mode 対応とデザイン変更が `styles.css` - 1 層目の `@theme` は import より後ろ、`@theme inline` より前に置く。後ろへ動かすと semantic token まで消える - `black` と `white` だけは 1 層目で再登録してある。registry の overlay が scrim を `bg-black/10` で描いており、消すとモーダルの背景が素通しになる - 2 層目はテストファイルにも効く。字面走査の頃と違って `.test.*` / `.gen.*` の除外は無い +- `styles.css` に `static` を付けない。Storybook のカタログのために `.storybook/preview.css` が同じファイルを `theme(static)` 付きで読み直す。本番の CSS は未参照の宣言を持たない (ADR-0022) ## typography 階層 diff --git a/.mise.toml b/.mise.toml index 952d5e41..33e6e9de 100644 --- a/.mise.toml +++ b/.mise.toml @@ -5,13 +5,25 @@ disable_tools = ["node", "pnpm"] [env] -DEV_PORT = "{{ exec(command='bash scripts/dev-env/derive-dev-port.sh 3000') }}" DB_FILE_NAME = ".data/dev.sqlite" +# port は worktree ごとに分ける。固定すると 2 つ目の worktree が bind に失敗する。 +# 導出はタスクの env に置く。要るのはこの 2 つのタスクだけなのに、トップレベルの [env] へ +# 置くと mise が env を解決するたび (シェル hook 下ではディレクトリへ入るたび) に git の +# サブプロセスが走る。run の引数へ $(...) を直接書く形も採らない。script が実行できなかった +# ときに空文字が渡り、Vite の既定 port で起動して worktree の分離が無言で外れる。タスクの +# env なら、そのタスクを叩いたときだけ走り、シェル hook の有無にも依存しない。script が +# 失敗すればタスクごと止まる (2026-09-20 実測) [tasks.serve] description = "dev server を起動する" +env = { DEV_PORT = "{{ exec(command='bash scripts/dev-env/derive-dev-port.sh 3000') }}" } run = "vp dev --port $DEV_PORT" +[tasks.storybook] +description = "Storybook を起動する" +env = { STORYBOOK_PORT = "{{ exec(command='bash scripts/dev-env/derive-dev-port.sh 6006') }}" } +run = "vp exec storybook dev --port $STORYBOOK_PORT --no-open" + [tasks."db:generate"] description = "Drizzle スキーマから migration を生成する" run = "vp exec drizzle-kit generate" diff --git a/.storybook/main.ts b/.storybook/main.ts new file mode 100644 index 00000000..980357aa --- /dev/null +++ b/.storybook/main.ts @@ -0,0 +1,15 @@ +import type { StorybookConfig } from "@storybook/tanstack-react"; + +const config: StorybookConfig = { + // story は部品と同じディレクトリに置く (ADR-0022)。src/components/ の外は対象にしない + stories: ["../src/components/**/*.stories.@(ts|tsx)"], + addons: ["@storybook/addon-a11y", "@storybook/addon-themes", "@storybook/addon-vitest"], + framework: "@storybook/tanstack-react", + // telemetry は既定で有効で、実行したコマンド・バージョン・addon 一覧・story と + // コンポーネントの件数などを送る (storybook.js.org/docs/configure/telemetry)。 + // このリポジトリは暗黙の挙動を明示で潰す方針なので切る。設定を読む前に出る boot + // イベントだけはこの設定の対象外である + core: { disableTelemetry: true }, +}; + +export default config; diff --git a/.storybook/preview.css b/.storybook/preview.css new file mode 100644 index 00000000..59c45bdd --- /dev/null +++ b/.storybook/preview.css @@ -0,0 +1,19 @@ +/* Storybook だけ theme(static) を掛けて、定義した全トークンを :root へ出させる。 + Tailwind は既定では utility から参照されている変数しか出力しないため、実際に使って + いるトークンでもカタログに現れない。theme() は import 単位で効くので、本番の CSS は + src/styles.css をそのまま読み、未参照の宣言を持たない (ADR-0022)。 + 出典: 参照された変数だけを出力する既定と static は tailwindcss.com/docs/theme + 「Generating all CSS variables」、theme() が import 単位で効くことは + tailwindcss.com/docs/preflight「Disabling Preflight」 + + 代償として Tailwind 既定 theme の未定義トークンが一覧に混ざる + (2026-09-20 実測: Radius に --radius-3xl / --radius-xs、Typography に --font-serif と + --font-weight-* の 6 件。Colors は --color-*: initial が効いていて増えない) */ +@import "../src/styles.css" theme(static); + +/* src/styles.css の source() が見ない場所で utility の class を書くならここに足す。足さないと、 + class を書いた人が生成されないことに気付けない。@source のパスは書いた stylesheet からの + 相対で、scan の対象は CSS entry ごとに閉じる。本番の entry (src/styles.css) は + この行を持たないため、Storybook 用の utility が出荷 CSS へ混ざらない + (tailwindcss.com/docs/detecting-classes-in-source-files「Explicitly registering sources」) */ +@source "."; diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx new file mode 100644 index 00000000..1fe5676c --- /dev/null +++ b/.storybook/preview.tsx @@ -0,0 +1,21 @@ +import { withThemeByClassName } from "@storybook/addon-themes"; +import type { Preview } from "@storybook/tanstack-react"; + +import "./preview.css"; + +const preview: Preview = { + decorators: [ + // styles.css の @custom-variant dark (&:is(.dark *)) に合わせて html へ class を当てる + withThemeByClassName({ + themes: { light: "", dark: "dark" }, + defaultTheme: "light", + parentSelector: "html", + }), + ], + parameters: { + // 違反を警告で留めない。expectNoA11yViolations が持つ「違反ゼロ」と基準を揃える (ADR-0022) + a11y: { test: "error" }, + }, +}; + +export default preview; diff --git a/AGENTS.md b/AGENTS.md index a1d03a27..ebdeb8df 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,6 +18,14 @@ mise run verify # vp check → vp test run → vp build → ビルド成果物 - ブラウザテスト用の chromium は `vp install` では入らない(`playwright` が install スクリプトを持たない)。`vp exec playwright install chromium --only-shell` で取得する - 依存の追加と更新には公開後 3 日の待機が効く(`pnpm-workspace.yaml` の `minimumReleaseAge`)。前倒しの条件は ADR-0005 +## Storybook の skill と tools + +UI と story を触る前に `vp exec storybook skills` を実行し、`stories` skill の手順に従う。**`vp exec storybook --help` の一覧に `skills` と `tools` は出ない**ので、見落としやすい。 + +- 部品の props・API・使い方は `vp exec storybook tools docs list` / `docs show` で答える。ソースや型定義から答えない +- `vp exec storybook tools stories find-by-component` は Storybook を起動してから `--port` で指す。未起動でも走るが結果が空で返り、story が無いのと区別が付かない +- MCP (`@storybook/addon-mcp`) は入れない。理由と起動の要否は ADR-0023 + ## 仕様書・設計判断 - `docs/decisions/` - ADR(インフラ・ツールチェーン等の構造変更に着手する前に必ず参照) diff --git a/README.md b/README.md index b81d6715..3e1e9772 100644 --- a/README.md +++ b/README.md @@ -53,12 +53,14 @@ vp install # 依存パッケージ vp exec playwright install chromium --only-shell # browser mode 用の chromium mise run db:migrate # drizzle/ の migration を DB へ適用する mise run serve # dev server +mise run storybook # 部品とデザイントークンのカタログ ``` - Node.js と pnpm は `vp` が `package.json` から解決して用意する。`mise install` が要るのは tasks と `[env]` のため - 素の `pnpm` を叩くなら `corepack enable` を一度実行する。Vite+ の shim は `node` / `npm` / `npx` / `corepack` までで `pnpm` を含まない - chromium は `vp install` では入らない (`playwright` が install スクリプトを持たない)。未取得のまま `vp test run` すると browser project が落ちる -- dev server の port は worktree ごとに変わる。main checkout は 3000、linked worktree は 3001-3999 (`.mise.toml` の `DEV_PORT`) +- port は worktree ごとに変わる。main checkout は base のまま、linked worktree は base+1 から base+999 (`.mise.toml` の `serve` が base 3000、`storybook` が base 6006) +- Storybook は部品の状態とデザイントークンを並べる。story は部品と同じディレクトリに置き、a11y を axe で自動検査する (ADR-0022) ### 3. 名前を置換する diff --git a/docs/decisions/0002-dev-environment-toolchain.md b/docs/decisions/0002-dev-environment-toolchain.md index 112637af..f6b8f53d 100644 --- a/docs/decisions/0002-dev-environment-toolchain.md +++ b/docs/decisions/0002-dev-environment-toolchain.md @@ -3,6 +3,7 @@ - Status: Accepted - Date: 2026-08-17 - Revised: 2026-09-02 (runtime と package manager の版の出所を `package.json` へ一本化し、mise は tasks と環境変数だけを持つようにした。型検査を tsgolint に委ね `typescript` を依存から外した。アプリ名を環境変数からモジュール定数へ戻した) +- Revised: 2026-09-20 (dev server と Storybook の port の導出を、トップレベルの `[env]` からタスクの `env` へ移した) ## Context @@ -29,13 +30,18 @@ CI は mise を要さない。`.mise.toml` の `[tasks.verify]` と同じ順序 ### 値の置き場所を 3 つに分ける -| 種別 | 置き場所 | 例 | -| -------------------------- | -------------------------------- | --------------------------- | -| 環境で変わらない値 | モジュール定数 | `src/lib/app-name.ts` | -| 環境で変わるが秘密でない値 | `.mise.toml` の `[env]` | `DEV_PORT` / `DB_FILE_NAME` | -| 秘密 | 暗号化して commit + 実行時に復号 | 現時点で該当なし | +| 種別 | 置き場所 | 例 | +| -------------------------- | -------------------------------- | --------------------- | +| 環境で変わらない値 | モジュール定数 | `src/lib/app-name.ts` | +| 環境で変わるが秘密でない値 | `.mise.toml` の `[env]` | `DB_FILE_NAME` | +| 秘密 | 暗号化して commit + 実行時に復号 | 現時点で該当なし | アプリ名のように環境ごとに値が変わらないものは環境変数にしない。 + +毎回の解決にコストがかかる値も `[env]` へ置かない。 +dev server と Storybook の port は worktree ごとに git から導出するため、`[env]` に置くと mise が env を解決するたび (シェル hook の下ではディレクトリへ入るたび) に git のサブプロセスが走る。 +読み手が `serve` と `storybook` のタスクしかいないので、タスクの `env` で導出する。`run` の引数へ `$(...)` を書くと、script が実行できなかったときに空文字が渡って既定 port で起動する。タスクの `env` なら script の失敗がタスクの失敗になる。 +2026-09-20 の実測で `mise hook-env` は 70ms から 28ms になった。タスク側なら、シェル hook を入れていない手元でも port が決まる。 初版 (2026-08-17) は `VITE_APP_NAME` を `.mise.toml` の `[env]` に置いていたが、値の定義・型宣言・未設定の検出・CI への受け渡しが芋づるで要り、CI が mise に依存する原因になっていた。 ### `envDir: false` で Vite の `.env` 読み込みを切る @@ -110,7 +116,7 @@ runtime は Vite+ が同じ宣言から解決して持っているので 2 つ 版の解決を Vite+ へ移した後も、タスクランナーと `[env]` の担い手として mise を採る判断は変わらない。 宣言だけでは効かないので、mise のシェル hook を導入手順に含める。 -hook を入れていない手元では `[env]` が読まれず、`DEV_PORT` と `DB_FILE_NAME` が未設定のまま走る。 +hook を入れていない手元では `[env]` が読まれず、`DB_FILE_NAME` が未設定のまま走る。 ### Vite+ を選ぶ理由 diff --git a/docs/decisions/0006-shadcn-registry-deviation-list.md b/docs/decisions/0006-shadcn-registry-deviation-list.md index fbb229e1..b08586eb 100644 --- a/docs/decisions/0006-shadcn-registry-deviation-list.md +++ b/docs/decisions/0006-shadcn-registry-deviation-list.md @@ -178,6 +178,7 @@ sidebar の依存として CLI が `src/hooks/` へ出力するファイルで - registry コードが依存するパッケージの選定は上流に従う。`class-variance-authority` は 12 コンポーネントが、`tw-animate-css` の `animate-in` / `animate-out` は 7 コンポーネントが使う (2026-09-02 時点、いずれも baseline 側にも同じ import がある)。どちらも更新が細っている (`class-variance-authority` は最終公開 2024-11-26 の 0.7.1 のまま。`tw-animate-css` は 1.4.0 が 2026-02-28 で、リポジトリの最終 push も同日。2026-09-02 確認)。乗り換えは上流が動いたときにしか成立しない。`shadcn/tailwind.css` は `animate-in` を供給しないため、`tw-animate-css` の撤去は registry コードを壊す - registry が `cn` の提供元を `@/lib/utils` から npm の `cn` パッケージへ移したため、`src/lib/utils.ts` を削除して消費側の import を差し替えた。`components.json` の `aliases.utils` は shadcn のスキーマが必須項目にしているので値は残すが、現行 registry は参照しない (`src/lib/utils.ts` が無くても `shadcn add` は成功する。2026-09-11 実測) - baseline を自前で持つのは、上流に生成時点を特定する手段が無いあいだの代替である。shadcn が生成時点の記録や registry item の版数フィールドを持つようになったら、そちらへの移行を検討する (shadcn-ui/ui#10374) +- registry 由来でない付随ファイル (`*.test.*` / `*.stories.*` / `*.test-helpers.*` / `*.story-helpers.*`) は `src/components/ui/` に置いてよい。`shadcn add` の出力に含まれないため baseline を持たず、網羅検査の対象外になる ## 出典 diff --git a/docs/decisions/0020-component-layers.md b/docs/decisions/0020-component-layers.md index 922c8406..ce60f16a 100644 --- a/docs/decisions/0020-component-layers.md +++ b/docs/decisions/0020-component-layers.md @@ -29,6 +29,8 @@ | `src/components/screens/` | 部品を並べて画面を組む共有コンポーネント | 適用する | | `src/components/` (直下) | 上のどれでもないもの。実例は `live-regions.tsx` | 適用する | +`src/components/` の外では `.storybook/` も適用する側に置く。decorator は design system component を包んで `className` を渡す置き場になるため、`src/components/screens/` と同じ扱いにする。`vite.config.ts` の override は `files` に `src/**` と `.storybook/**` の 2 つを持つ (PR #35)。 + 直下を既定として残すのは、役割を決めきれないものの置き場所を無くさないためである。既定を「規則を適用する」側に置くので、著作として扱わせたいときだけ `parts/` を選ぶことになり、判断を省略した新規ファイルは規則が効く安全側に倒れる。 `action/` を `parts/` の下へ移さないのは、分ける軸が違うためである。`parts/` は外見を定義する層で、`action/` は振る舞い (Transition と pending) を与える層である。どちらも design system の著作側なので `no-restyle` の扱いは同じだが、外見の変更と振る舞いの変更は別の理由で起きる。 diff --git a/docs/decisions/0022-storybook-as-state-catalog.md b/docs/decisions/0022-storybook-as-state-catalog.md new file mode 100644 index 00000000..b0154013 --- /dev/null +++ b/docs/decisions/0022-storybook-as-state-catalog.md @@ -0,0 +1,187 @@ +# ADR-0022: Storybook を状態のカタログとして導入し、対話的な部品には play function を書く + +- Status: Accepted +- Date: 2026-09-20 +- 関連: ADR-0002 (ツールチェーン) / ADR-0006 (registry 統制、付随ファイルの扱い) / ADR-0013 (待機) / ADR-0014 (Transition) / ADR-0015 (実イベント) / ADR-0018 (animation) / ADR-0020 (層) / ADR-0021 (className) + +## Context + +部品の状態 (variant / tone / disabled) を並べて見る場所が無く、確認手段はアプリの画面を開くことだけだった。デザイントークンも `src/styles.css` の `@theme` と `:root` が SSOT だが、値を一覧する手段が無く light と dark を並べて比べられなかった。 + +framework の選定は `tanstackStart()` plugin と Storybook の Vite builder の衝突 (storybookjs/storybook#33747) が決める。標準の Vite builder はこの衝突を自分で回避する必要があり、server function を呼ぶ部品の story を組めない。 + +本 ADR は 2 つの spec の決定を統合する。 + +- 2026-09-19 の spec: 導入そのものの決定 +- 2026-09-20 の spec: play function の対象を再検討し、前者の「play を書く対象の判断軸」と「`action/` を対象外とする判断」を置き換えた + +spec はどちらも当時の記録として凍結する。以後の判断は後者を正とする。 + +## Decision + +### 1. framework は TanStack 専用のものを使う + +`tanstackStart()` plugin と衝突しない TanStack 専用 framework を使う。router を memory-backed で自動ラップし、server function を自動 stub する。 + +自動構成が届かない範囲が 2 つある。 + +- TanStack Query は対象外。preview の構成へ手動で置く +- server-only 依存は `__mocks__` で遮断する + +### 2. story は状態のカタログとし、対話的な部品には play function を書く + +story の基本は部品が取りうる状態を並べることで、振る舞いの検証を目的にしない。対象の判断は次の軸による。 + +| 部品の性質 | play を書くか | 理由 | +| ------------------------------------------------ | ------------- | ----------------------------------------------- | +| `args` だけで状態が決まる (寸法・variant・tone) | 書かない | story の control で切り替えられ、検証と重複する | +| 操作を受けて状態が変わる (dialog・form・menu 等) | 書く | 状態遷移そのものが story の対象になる | + +対象の層は `ui/` `action/` `parts/` とし、`screens/` は外す (実画面で見るほうが早い)。当初 `action/` も対象外としていたが、pending 表現に server function の stub が要るという当初の理由は誤りで、決着する Promise を渡すだけで pending の描画と解除が成立した (2026-09-20 実測)。 + +play で書いた検証は既存のブラウザテストから削る。同じ振る舞いを 2 箇所で固定しない。 + +対象の全 case が移れば test ファイルごと削る。locator と文言を持つ `*.test-helpers.ts` は残す (`routes/` のテストが同じものを引く)。 + +移行は story を書く部品に限り、一律移行はしない。ファイルごとに移せるかを実測してから進める。 + +variant の網羅を story の数で表現しない。代表値を story にし、残りは `argTypes` の control で切り替える。 + +story を variant の直積で増やすと、カタログが読み通せない長さになる。 + +### 3. 検証専用の story は `tags: ["!dev"]` でサイドバーから外す + +story の終了状態が他の story と同じ見た目になるものは検証専用として扱い、`tags: ["!dev"]` を付ける。サイドバーの一覧から消えるが、vitest の project 実行では対象に残る (実測: `index.json` の `tags` が `dev` を含まなくなる)。 + +### 4. story に決着しない Promise を置かない + +pending の見た目をカタログに残す目的で、いつまでも解決しない Promise を返す action を書かない。pending を検証する story は決着する Promise を返す action で書く。 + +Storybook の vitest 実行は 1 つの React root へ story を描き替える。決着しない Transition が残ると後続 story の Transition と干渉し、後続 story が pending のまま止まる (2026-09-20 実測)。 + +### 5. play の操作は合成イベントとし、実イベントの規律はブラウザテストが持つ + +play は Storybook の UI 上でも実行されるため CDP を使えず、`storybook/test` の合成イベントで操作する。ADR-0015 が定めた実イベントでの発火の規律はブラウザテスト側がそのまま持ち、play へは移さない。 + +ADR-0015 が禁じた同期 2 連射は play では起きない。`storybook/test` の操作が各手順を await するためである。 + +popup を閉じる play は、閉じた popup の unmount を待ってから終える。待たないと、play の後に走る a11y 検査が ADR-0018 の扱う animate-out の窓に入る。 + +待機は `storybook/test` の `waitFor` で書く。ADR-0013 の retry API は play から呼べない。 + +Storybook の test 実行では ADR-0018 の animation 無効化を適用しない。 + +### 6. トークンは CSS 変数を実測して描く + +`styles.css` を SSOT に保つため、story 側に値を書き写さない。 + +- 値は `getComputedStyle` で解決後のものを読む +- light と dark は `@storybook/addon-themes` の class 切り替えで出し分ける +- テーマごとの読み取り結果を外部ストアにし、`useSyncExternalStore` で購読する。値は React の依存に現れないため、再 render だけでは React Compiler がメモ化した結果を返して止まる +- `key` による remount は採らない。テーマの class は `STORY_RENDERED` 後に当たるため、remount は play function より後に起き、play が作った状態を捨てる (2026-09-20 実測) + +### 6-1. コントラストの検算はこの story で扱わない + +トークンの値を変えた人が閾値を割っていないか確かめる仕組みは要るが、この story では扱わない。2026-09-20 に 2 つの形を試して、どちらも目的を果たさなかった。 + +| 試した形 | なぜ外したか | +| -------------------------------------------- | -------------------------------------------------------------------------------------------------- | +| 全トークンを `--background` と比べる | 画面上で重ならない組み合わせの比が並ぶ。閾値を割ったかどうかの判断に使えない | +| 命名から対を導く (`--X-foreground` と `--X`) | 命名は「画面で重なること」の代理にならない。使われていない対で不合格を出し、実在の対を出さなかった | + +JS で比を計算する形そのものにも無理がある。ブラウザは sRGB 外の `oklch()` を clip し、alpha を持つ背景は下地と合成する。描画されていない値を計算しても実際の見え方と一致しない。 + +実際に重なる組み合わせを実テキストとして描けば、`parameters.a11y.test` の axe がそのまま判定する。新しい依存は要らず、gamut も alpha も正確になる。この形で試したところ、`bg-destructive/20` と `text-destructive` が 3.74:1 で不合格になった。破壊ボタンの hover の実在の組み合わせである。 + +扱いを決めるには `--destructive` の値の再検討が要り、それは別の判断になるため分けた。非テキストの 3:1 (WCAG 1.4.11) を axe が持たない点も併せて扱う。 + +トークンの一覧を CSSOM から読む選択の帰結として、`static` が要る。`inline` は utility へ値を直接埋め込むため、`rounded-*` の utility を書いても対応する変数を読む rule が生まれない。Tailwind は既定で参照されている変数だけを出力するので、実際に使っているトークンでもカタログから消える。 + +どの変数が落ちるかは Tailwind の source scan の結果で決まる。**scan は既定でリポジトリ全体を読み、Markdown も対象にする**ため、ADR や rules に書いた名前が「使用中」と判定されて出力に残っていた。`styles.css` の `@import "tailwindcss" source("../src")` で対象をアプリのソースへ絞る。効果は `source()` を外して `vp build` を 2 回回せば測れる。 + +`@source not` で除外を並べる形は採らない。symlink (`.claude/skills` は `.agents` を指す) と、後から増える置き場を取りこぼす。絞る側を書けば、対象に入れ忘れた場所は utility が生成されないことで気付ける。 + +**`static` は Storybook だけに掛ける。** `.storybook/preview.css` が `src/styles.css` を `@import "../src/styles.css" theme(static);` で読み直す。`theme()` は import 単位で効くため、本番の CSS は `static` の分を持たない。 + +代償は、Tailwind 既定 theme の未定義トークンがカタログに混ざることである。2026-09-20 の実測では Radius に 2 件、Typography に 6 件で、Colors は `--color-*: initial` が効いていて増えない。 + +Tailwind は theme の出力を `@layer theme` に置くため、CSSOM の走査は `@layer` を含むグループ規則を再帰的に辿る必要がある。辿らないと `@layer` の中の `:root` を見落とす。 + +telemetry は `core.disableTelemetry` で切る。既定で有効で、実行したコマンド・バージョン・addon 一覧・story とコンポーネントの件数を送る。このテンプレートから作られる全プロジェクトへ配られる設定なので、`envDir: false` や `disable_tools` と同じく明示で潰す側に揃える。 + +### 7. a11y は `error` で自動検査し、既存のブラウザテストは残す + +`parameters.a11y.test` を `"error"` にする。story を書いた部品は自動で axe の対象になり、検査の範囲が既存より広がる。 + +| 対象 | 役割 | +| -------------------- | ------------------------------------------------------ | +| story | どんな状態があるか。目で見るカタログ | +| 既存のブラウザテスト | その状態が壊れていないか。寸法と色を固定する回帰の防止 | + +役割が違うため両方残す。ADR-0013 / ADR-0015 / ADR-0018 が固めた待機・実イベント・animation 無効化の規律は既存のテストが持ち続ける。 + +### 8. story はコンポーネントと並べ、registry の baseline から除く + +`*.stories.tsx` は部品と同じディレクトリに置く。`src/components/ui/` に置いたものも `*.test.tsx` と同じ「registry 由来でない付随ファイル」として baseline 検査の対象外になる (ADR-0006)。 + +story を置けるのは `src/components/` 配下に限る。`.storybook/main.ts` の `stories` をそこへ絞っているためで、他へ置くと Storybook も vitest の project も拾わず、a11y 検査ごと無言で外れる。範囲を広げるかどうかは、`features/` や `routes/**/-components/` に story を書きたくなった時点で決める。 + +トークンの story は CSS 変数の値を見せる場所で、typography の階層のような class の規範は持たない。`styling.md` の表を story へ写すと片方だけが古くなる。markdown と code を突き合わせる機械検査は持っていない。 + +story は出荷される bundle に入らないため、`no-restricted-imports` の対象からも外す。 + +### 9. 導入は 3 段階に分け、PR を stack にする + +1 度に全部品の story を書かない。段階ごとに PR を分け `gh stack` で積む。 + +| 段階 | 範囲 | +| ---- | ------------------------------------ | +| 1 | 基盤とデザイントークンの story | +| 2 | 外見を定義する `parts/` と `action/` | +| 3 | `ui/` の主要部品 | + +段階 2 と 3 は、対象の層に story があり a11y 検査が通ることを完了条件とする。 + +## 検討した選択肢 + +| 案 | 評価 | 採否 | +| -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| 標準の Vite builder を使う | `tanstackStart()` との衝突を自分で回避することになり、server function を呼ぶ部品の story が組めない | 却下 | +| play function を全部品に一律書く | 公式 (Interaction testing) が一律適用の保守費用を警告しており、`args` で決まる状態の検証とも二重になる | 却下 | +| 「初期状態では中身が見えない部品」を play の対象軸にする | 公式が「複雑で対話的な部品ならさらに踏み込める」と述べる後段を落とし、公式にない軸を独自に作ってしまう | 却下 | +| 既存のブラウザテストを丸ごと story へ移す | portable stories は state 更新を伴う再描画を支援しない。待機・実イベント・animation の規律を移植する動機も無い | 却下 | +| トークンを公式の `ColorPalette` で書く | 色値を MDX へ書き写すため `styles.css` と二重管理になる | 却下 | +| トークンを専用 addon で一覧化する | `styles.css` へ注釈コメントを足す必要があり、Storybook 専用の記述が SSOT に混ざる | 却下 | +| コントラスト比を自前で計算する | 対応する色空間を実装ごと抱える。`oklch()` を読めない実装になり、変換のためにブラウザの色パーサを借りる連鎖が起きた | 却下 | +| 全トークンを単一の背景と比べる | 画面上で重ならない組み合わせの比が並び、閾値を割ったかどうかの判断に使えない | 却下 | +| `styles.css` に `static` を付ける | 未参照の宣言が本番 CSS へ乗り、この template から作られる全プロジェクトが払う。差の測り方は `@theme inline` と `@theme static inline` を入れ替えて `vp build` を 2 回 | 却下 | +| トークン名を `styles.css` のソースから読む | `static` が無いと未出力の変数は `getComputedStyle` で解決できず、名前だけが並ぶ | 却下 | +| `__unstable__loadDesignSystem` でビルド時に列挙する | `@tailwindcss/node` が export するが、名前のとおり安定 API ではないと明示されている | 却下 | +| 検証専用 story をサイドバーへ出したまま置く | 同じ見た目の story が並び、カタログとして読めなくなる (2026-09-20 に 1 部品で実測、9 story 中 4 つが重複) | 却下 | +| 検証専用 story を別ファイルへ分ける | story glob と「部品の隣へ置く」規約の両方を変えることになる | 却下 | +| pending の見た目を決着しない action で作る | 後続 story の Transition を止める (節 4 の実測) | 却下 | +| `action/` を当初どおり対象外に保つ | pending 表現に server function の stub が要るという理由が実測で誤りと判明した | 却下 | +| 状態のカタログに徹し、対話的な部品にだけ play を書く | 外見確認という目的を満たし、検証の二重化も保守費用の増大も避けられる | **採用** | + +## Consequences + +- 部品の状態を並べて見る場所ができ、トークンの一覧も `styles.css` から自動で出る +- story を書いた部品は axe の検査対象になり、検査範囲が既存のブラウザテストより広がる +- テストの実行対象が増え、`vp test run` に storybook project が加わり CI の実行時間が伸びる +- `.stories.tsx` を registry のディレクトリに置くため、ADR-0006 の baseline 検査に除外が 1 種類増える +- Storybook の静的ビルドは検証しない (storybookjs/storybook#33747 が未解決) +- 検証が一部 CDP の実イベントから合成イベントへ移り、backdrop の遮りを含む pointer の忠実さは下がる。一方イベント間に描画が挟まる点は既存のブラウザテストと同じ性質になる +- サイドバーに出る story と出ない story ができ、`tags` の付け忘れでカタログが汚れうる。機械検査は置かず、レビューで見る +- 移行のたびに「移せない case」が出る可能性が残り、段階 2 と 3 の各ファイルで実測が要る +- `storybook/test` の `expect` は vitest の matcher をすべて持つわけではない。ブラウザテストの assertion を story へ機械的に写せない箇所が出る + +## 出典 + +- Storybook: TanStack framework — https://storybook.js.org/docs/get-started/frameworks/tanstack-react +- Storybook: Vitest addon — https://storybook.js.org/docs/writing-tests/integrations/vitest-addon +- Storybook: Interaction testing — https://storybook.js.org/docs/writing-tests/interaction-testing +- Storybook: Themes addon — https://storybook.js.org/docs/essentials/themes +- Storybook: Tags — https://storybook.js.org/docs/writing-stories/tags +- Tailwind CSS: Theme variables (Generating all CSS variables) — https://tailwindcss.com/docs/theme +- Tailwind CSS: Detecting classes in source files — https://tailwindcss.com/docs/detecting-classes-in-source-files +- storybookjs/storybook#33747 (Vite builder と tanstack start plugin の衝突) diff --git a/docs/decisions/0023-storybook-agent-tools-via-cli.md b/docs/decisions/0023-storybook-agent-tools-via-cli.md new file mode 100644 index 00000000..117934a6 --- /dev/null +++ b/docs/decisions/0023-storybook-agent-tools-via-cli.md @@ -0,0 +1,61 @@ +# ADR-0023: Storybook の agent 向けツールは同梱の CLI を使い、MCP addon は入れない + +- Status: Accepted +- Date: 2026-09-20 +- 関連: ADR-0002 (ツールチェーン) / ADR-0022 (Storybook を状態のカタログとして導入) + +## Context + +`storybook@10.6.0` は agent 向けの機構を 2 経路で配っている。本体同梱の CLI (`storybook skills` / `storybook tools`) と、別パッケージの `@storybook/addon-mcp` である。どちらも同じツール群 (`docs list` / `docs show` / `stories changed` / `stories find-by-component` / `stories preview` / `review create` / `test run`) を公開する。 + +公式ドキュメントに載っているのは MCP だけで、CLI は記載がない。一方で CLI は `storybook --help` のコマンド一覧にも出ない。どちらも探さないと見つからない。 + +このリポジトリはテンプレートとして配布され、Storybook の port は worktree ごとに変わる (`.mise.toml` の `storybook` タスクが `derive-dev-port.sh 6006` から導出する)。 + +## Decision + +**同梱の CLI を使い、`@storybook/addon-mcp` は入れない。** + +| # | 決定 | +| --- | ---------------------------------------------------------------------------------------------------------- | +| 1 | UI と story を触る前に `storybook skills` を読む。`stories` skill が定める手順を起点にする | +| 2 | 部品の props・API・使い方は `storybook tools docs list` / `docs show` で答える。ソースや型定義から答えない | +| 3 | `@storybook/addon-mcp` は入れない。MCP の登録が持てる URL は 1 つで、worktree ごとに変わる port へ配れない | +| 4 | CLI の存在と落とし穴は `AGENTS.md` に置く。決定の根拠と実測はこの ADR が持つ | + +### 判断の根拠 + +| 観点 | CLI | MCP | +| ---------------- | ------------------------------------ | ----------------------------------------------------------------- | +| 追加パッケージ | 不要 (本体同梱) | `@storybook/addon-mcp` | +| Storybook の起動 | 多くのツールで不要 (下表) | 全ツールで必要 (HTTP endpoint 経由) | +| 設定 | 不要 | `main.ts` に `componentsManifest: true` + エージェントへ URL 登録 | +| 配布 | clone すれば誰でも同じコマンドが動く | 登録はエージェント側の個人設定 | + +決め手は 3 つ目である。MCP の登録はエージェント側の設定に URL を 1 つ持つ。この repo の Storybook の port は worktree ごとに変わるので、同じ登録を collaborator へ配れない。CLI は `--cwd` / `-c` でプロジェクトを指すため影響を受けない。 + +MCP が優るのは、ツールの説明がエージェントに常時見える点である。CLI は `AGENTS.md` に書いても読み飛ばせば使われない。2026-09-20 のセッションで、一度調べた `storybook skills` の存在を書き残さずに失い、同じ調査を繰り返した。決定 4 はその対策である。 + +### 起動の要否 (2026-09-20 実測) + +port 6006 の Storybook を停止し、`--no-attach` を付けて 1 つずつ実行した。 + +| ツール | 起動 | 備考 | +| ---------------------------------------------------------- | ------------ | -------------------------------- | +| `docs list` / `docs show` / `stories changed` / `test run` | 不要 | | +| `stories preview` / `review create` | 必要 | preview URL と review の発行 | +| `stories find-by-component` | **実質必要** | 起動なしでも走るが結果が空で返る | + +`stories find-by-component` は逆依存グラフを dev server が持つ。未起動だと `no stories found` が返り、story が無いのと区別が付かない。tool の help 自身が「If a component has no matches here, it has no stories yet (say so, don't fabricate)」と書いているため、読んだ側は「story が無い」と報告する。起動して `--port` で指すと距離つきで返る (`page-title.tsx` → `tokens` の 3 story が distance 1)。grep では出せない情報である。 + +## Consequences + +- `storybook --help` に `skills` と `tools` が出ないため、`AGENTS.md` から外れた瞬間に見落とされる。サイズ制限 (`agents-md-maintenance.md`) の中で維持する必要がある +- `write-story` skill は上流の規約で、このリポジトリの決定と食い違う箇所がある (「ALWAYS write a Storybook story for any component written」「Simulate key user flows」)。上限は ADR-0022 が持つ。ADR に上限が書かれていない項目は上流の既定値が入る +- `find-by-component` を使うには Storybook を起動する。起動を省くと silent に空が返る +- MCP へ移るなら、port を固定するか、worktree ごとに登録し直す運用が要る + +## 出典 + +- Storybook: AI — https://storybook.js.org/docs/ai +- `storybook skills` / `storybook tools` の出力 (storybook@10.6.0) diff --git a/docs/decisions/README.md b/docs/decisions/README.md index 047c3953..d3255943 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -28,6 +28,8 @@ | [0019](0019-list-tables-via-tanstack-table-column-definitions.md) | 一覧テーブルは TanStack Table v9 の列定義で組み、描画は registry の Table に残す | Accepted | 2026-09-14 | `createColumnHelper` の列定義を SSOT にし、shadcn Data Table と同じ `DataTable` 部品が registry の `Table` に `FlexRender` で描く。楽観行は行の union で同じ列定義に載せる。楽観表示の設計は ADR-0016 のまま | | [0020](0020-component-layers.md) | `src/components/` を役割で分け、design system の著作と消費の境界をディレクトリで表す | Accepted | 2026-09-19 | `ui/` `action/` `parts/` が著作、`screens/` と直下が消費。`no-restyle` はこの境界を `excludeFiles` で読む。直下は既定で規則が効く側 | | [0021](0021-static-classes-and-shared-appearance.md) | 消費側の className は静的に読める形に保ち、共有する外見は部品で配る | Accepted | 2026-09-19 | `require-static-classes` を層の境界つきで有効化。`cva` の variant 関数は `variantFunctions` へ宣言する。design system の層から外へ class 文字列を配らない | +| [0022](0022-storybook-as-state-catalog.md) | Storybook を状態のカタログとして使い、操作を伴う検証は play function へ移す | Accepted | 2026-09-20 | story は状態の列挙。操作で状態が変わる部品には play を書き、同じ case を既存テストから削る。トークンは CSS 変数を実測して描く | +| [0023](0023-storybook-agent-tools-via-cli.md) | Storybook の agent 向けツールは同梱の CLI を使い、MCP addon は入れない | Accepted | 2026-09-20 | `storybook skills` / `storybook tools` を起点にする。MCP の登録は URL を 1 つしか持てず、worktree ごとに変わる port へ配れない | ## フォーマット規約 diff --git a/package.json b/package.json index 0258baf4..c69fecde 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,10 @@ }, "devDependencies": { "@shadcn/lint": "^0.1.0", + "@storybook/addon-a11y": "10.6.0", + "@storybook/addon-themes": "10.6.0", + "@storybook/addon-vitest": "10.6.0", + "@storybook/tanstack-react": "10.6.0", "@tanstack/devtools-a11y": "^0.2.2", "@tanstack/devtools-vite": "^0.8.5", "@tanstack/router-plugin": "^1.168.35", @@ -54,6 +58,7 @@ "oxc-transform-react": "^0.147.0", "playwright": "^1.63.0", "shadcn": "^4.21.0", + "storybook": "10.6.0", "vite": "catalog:", "vite-plus": "catalog:", "vitest": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bf0772e9..e6e0a30e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -91,7 +91,7 @@ importers: version: 1.41.0(react@19.3.0) nitro: specifier: 3.0.260610-beta - version: 3.0.260610-beta(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(better-sqlite3@13.0.3)(chokidar@5.0.0)(dotenv@17.4.2)(drizzle-orm@0.45.2(@types/better-sqlite3@9.6.0)(better-sqlite3@13.0.3))(jiti@2.7.0) + version: 3.0.260610-beta(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(better-sqlite3@13.0.3)(chokidar@5.0.0)(dotenv@17.4.2)(drizzle-orm@0.45.2(@types/better-sqlite3@9.6.0)(better-sqlite3@13.0.3))(jiti@2.7.0)(lru-cache@11.5.2) react: specifier: ^19.3.0 version: 19.3.0 @@ -114,6 +114,18 @@ importers: '@shadcn/lint': specifier: ^0.1.0 version: 0.1.0(supports-color@7.2.0)(typescript@6.0.3) + '@storybook/addon-a11y': + specifier: 10.6.0 + version: 10.6.0(storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))) + '@storybook/addon-themes': + specifier: 10.6.0 + version: 10.6.0(storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))) + '@storybook/addon-vitest': + specifier: 10.6.0 + version: 10.6.0(@vitest/browser-playwright@4.1.11)(@vitest/browser@4.1.11)(@vitest/runner@4.1.11)(react@19.3.0)(storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)))(vitest@4.1.11) + '@storybook/tanstack-react': + specifier: 10.6.0 + version: 10.6.0(@tanstack/react-router@1.170.32(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@tanstack/react-start@1.168.49(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(crossws@0.4.12(srvx@0.11.22))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.6)(supports-color@7.2.0))(@tanstack/router-core@1.171.27)(@tanstack/start-client-core@1.170.27)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)))(supports-color@7.2.0)(typescript@6.0.3) '@tanstack/devtools-a11y': specifier: ^0.2.2 version: 0.2.2(@types/react@19.3.0)(csstype@3.2.3)(react@19.3.0)(solid-js@1.9.14) @@ -165,6 +177,9 @@ importers: shadcn: specifier: ^4.21.0 version: 4.21.0(supports-color@7.2.0)(typescript@6.0.3) + storybook: + specifier: 10.6.0 + version: 10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) vite: specifier: 'catalog:' version: '@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)' @@ -173,13 +188,16 @@ importers: version: 0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3) vitest: specifier: 'catalog:' - version: 4.1.11(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/browser-preview@4.1.11)(@vitest/coverage-v8@4.1.11)(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) + version: 4.1.11(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/browser-preview@4.1.11(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(vitest@4.1.11))(@vitest/coverage-v8@4.1.11)(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) vitest-browser-react: specifier: ^2.3.0 version: 2.3.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vitest@4.1.11) packages: + '@adobe/css-tools@4.5.0': + resolution: {integrity: sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==} + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -364,12 +382,30 @@ packages: '@drizzle-team/brocli@0.10.2': resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} + '@emnapi/core@1.11.0': + resolution: {integrity: sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==} + '@emnapi/core@1.11.3': resolution: {integrity: sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==} + '@emnapi/core@1.9.2': + resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} + + '@emnapi/runtime@1.11.0': + resolution: {integrity: sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==} + '@emnapi/runtime@1.11.3': resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} + '@emnapi/runtime@1.9.2': + resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + '@emnapi/wasi-threads@1.2.3': resolution: {integrity: sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==} @@ -853,6 +889,15 @@ packages: peerDependencies: hono: ^4 + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0': + resolution: {integrity: sha512-qvsTEwEFefhdirGOPnu9Wp6ChfIwy2dBCRuETU3uE+4cC+PFoxMSiiEhxk4lOluA34eARHA0OxqsEUYDqRMgeQ==} + peerDependencies: + typescript: '>= 4.3.x' + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -863,8 +908,8 @@ packages: resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} @@ -929,6 +974,12 @@ packages: cpu: [arm] os: [android] + '@oxc-parser/binding-android-arm-eabi@0.127.0': + resolution: {integrity: sha512-0LC7ye4hvqbIKxAzThzvswgHLFu2AURKzYLeSVvLdu2TBOYWQDmHnTqPLeA597BcUCxiLqLsS4CJ5uoI5WYWCQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + '@oxc-parser/binding-android-arm-eabi@0.148.0': resolution: {integrity: sha512-pHASv9g5pASxb7akHERZNSkrEqPhFaUix98o7d9hbTpolnnFWl7UiRrcMhCsV1+iVO4/cJwKsbKRJTFNs2tdBQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -941,6 +992,12 @@ packages: cpu: [arm64] os: [android] + '@oxc-parser/binding-android-arm64@0.127.0': + resolution: {integrity: sha512-b5jtVTH6AU5CJXHNdj7Jj9IEiR9yVjjnwHzPJhGyHGPdcsZSzBCkS9GBbV33niRMvKthDwQRFRJfI4a+k4PvYg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + '@oxc-parser/binding-android-arm64@0.148.0': resolution: {integrity: sha512-sg/6Ez0KdAygsu0POELux9wN1Po2CP93WY8eNl4DBKIGprsd4QSHBXOb471Pu9i2OCD5sLkISSb2agZEhVn2Zw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -953,6 +1010,12 @@ packages: cpu: [arm64] os: [darwin] + '@oxc-parser/binding-darwin-arm64@0.127.0': + resolution: {integrity: sha512-obCE8B7ISKkJidjlhv9xRGJPOSDG2Yu6PRga9Ruaz35uintHxbp1Ki/Yc71wx4rj3Edrm0a1kzG1TAwit0wFpg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + '@oxc-parser/binding-darwin-arm64@0.148.0': resolution: {integrity: sha512-yiSJmzGUvCUaJT8X3j40gVcX+ckuHQMuiOtF8DvzTs5+JtB/7XuHFPp4M+vv5u+HlBtDUd4Ks5pyHpWz8mfnkg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -965,6 +1028,12 @@ packages: cpu: [x64] os: [darwin] + '@oxc-parser/binding-darwin-x64@0.127.0': + resolution: {integrity: sha512-JL6Xb5IwPQT8rUzlpsX7E+AgfcdNklXNPFp8pjCQQ5MQOQo5rtEB2ui+3Hgg9Sn7Y9Egj6YOLLiHhLpdAe12Aw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + '@oxc-parser/binding-darwin-x64@0.148.0': resolution: {integrity: sha512-6ZeklaamrMy4H2JmhvcJg6iip59tYILtuLaILxyAHT3l5FDxnI5ihVievAft5ZmAbqtlWHErOi1OpJK8gy1wcA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -977,6 +1046,12 @@ packages: cpu: [x64] os: [freebsd] + '@oxc-parser/binding-freebsd-x64@0.127.0': + resolution: {integrity: sha512-SDQ/3MQFw58fqQz3Z1PhSKFF3JoCF4gmlNjziDm8X02tTahCw0qJbd7FGPDKw1i4VTBZene9JPyC3mHtSvi+wA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + '@oxc-parser/binding-freebsd-x64@0.148.0': resolution: {integrity: sha512-vFsPx+a/qFECPnz/H8nC6x6MDvnWscLTCo/5muojEF54ERUq1kdgbvnWo95YnkhjF9sTIcG/uDxQBh1gffaufQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -989,6 +1064,12 @@ packages: cpu: [arm] os: [linux] + '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0': + resolution: {integrity: sha512-Av+D1MIqzV0YMGPT9we2SIZaMKD7Cxs4CvXSx/yxaWHewZjYEjScpOf5igc8IILASViw4WTnjlwUdI1KzVtDHQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@oxc-parser/binding-linux-arm-gnueabihf@0.148.0': resolution: {integrity: sha512-eOr3M+6iGbbxNL4PSS0VtsyQ2eOUxSBh00BqO22SbolDimPSYsBuLr/LCrZBkiqW2BoabhR6V4R8jrRAay7hjg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1001,6 +1082,12 @@ packages: cpu: [arm] os: [linux] + '@oxc-parser/binding-linux-arm-musleabihf@0.127.0': + resolution: {integrity: sha512-Cs2fdJ8cPpFdeebj6p4dag8A4+56hPvZ0AhQQzlaLswGz1tz7bXt1nETLeorrM9+AMcWFFkqxcXwDGfTVidY8g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@oxc-parser/binding-linux-arm-musleabihf@0.148.0': resolution: {integrity: sha512-58ZKDw0mQRbCNfrd2IDyV4o8T7enzGERJn41BH2tjrZVGyiKiFzcfDicuB7Zcpb/1xIOrObovr8Dja6lZi8dLw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1014,6 +1101,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-arm64-gnu@0.127.0': + resolution: {integrity: sha512-qdOfTcT6SY8gsJrrV92uyEUyjqMGPpIB5JZUG6QN5dukYd+7/j0kX6MwK1DgQj39jtUYixxPiaRUiEN1+0CXgQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-arm64-gnu@0.148.0': resolution: {integrity: sha512-Fnu95O4eZ5i++GPvIzBEZ8y4ddTLR+D9paYa8JRaRk6ZK7nHQiWP5xtrhcPQsXqgat1d7sU/d5rbbI0p1FTHSQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1028,6 +1122,13 @@ packages: os: [linux] libc: [musl] + '@oxc-parser/binding-linux-arm64-musl@0.127.0': + resolution: {integrity: sha512-EoTCZneNFU/P2qrpEM+RHmQwt+CvDkyGESG6qhr7KaegXLZwePfbrkCDfAk8/rhxbDUVGsZILX+2tqPzFtoFWA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + '@oxc-parser/binding-linux-arm64-musl@0.148.0': resolution: {integrity: sha512-3CQy/BMdx7N7H3qrcPxUL+a2CwUZodUcf6oq8iJuNZ9C6Ol1aq3mcWzsgySJ7CHFLvpX21ZDPp1r1X0QLbu/AQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1042,6 +1143,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-ppc64-gnu@0.127.0': + resolution: {integrity: sha512-zALjmZYgxFLHjXeudcDF0xFGNydTAtkAeXAr2EuC17ywCyFxcmQra4w0BMde0Yi/re4Bi4iwEoEXtYN7l6eBLQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-ppc64-gnu@0.148.0': resolution: {integrity: sha512-9LkaYvfiF8hMOw900csAvkf1oxE8XlmMeGowu5BcastSSwV8mKvKRMNU7HsV+ycyj1dQD8pX5qgOw8ja6SJacg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1056,6 +1164,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-riscv64-gnu@0.127.0': + resolution: {integrity: sha512-fPP8M6zQLS7Jz7o9d5ArUSuAuSK3e+WCYVrCpdzeCOejidtZExJ9tjhDrAd3HEPqARBCPmdpqxESPFqy44vkBQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-riscv64-gnu@0.148.0': resolution: {integrity: sha512-2GBiM9h26dR4WJfhoMvnFMnFLf7m/kYs4UMqjvrOfQG4BV1nuTJDH22Zc2MQr3INZF7nSKYQ6xlhD3hQ7A6gug==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1070,6 +1185,13 @@ packages: os: [linux] libc: [musl] + '@oxc-parser/binding-linux-riscv64-musl@0.127.0': + resolution: {integrity: sha512-7IcC4Ao02oGpfnjt+X/oF4U2mllo2qoSkw5xxiXNKL9MCTsTiAC6616beOuehdxGcnz1bRoPC1RQ2f1GQDdN+g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + '@oxc-parser/binding-linux-riscv64-musl@0.148.0': resolution: {integrity: sha512-uPqZexvKJmEgq4mAu36qe2xTfXZE7oyik1R7KtZ5tl8qKlq1U1fIqTFRUEBZqRGvforoTrGIpatRzcoPKO66RA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1084,6 +1206,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-s390x-gnu@0.127.0': + resolution: {integrity: sha512-pbXIhiNFHoqWeqDNLiJ9JkpHz1IM9k4DXa66x+1GTWMG7iLxtkXgE53iiuKSXwmk3zIYmaPVfBvgcAhS583K4Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-s390x-gnu@0.148.0': resolution: {integrity: sha512-9oUHvnTbp7ZraFsTC8PN6XhdhPSSxZumYvixWl7Smi353gEULvK6yV0sXNVrdFMHQeaDKFCi8TgDhNK7/A+Y+Q==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1098,6 +1227,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-x64-gnu@0.127.0': + resolution: {integrity: sha512-MYCguB9RvBvlSd6gbuNI7QwiLoCCAlGnlRJFPrzLI6U1/9wkC/WK6LtBAUln55H1Ctqw45PWmqrobKoMhsYQzQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-x64-gnu@0.148.0': resolution: {integrity: sha512-2qhDSJwKzbSZzF7lDqqk8sr/yXsmwr3PeUa4/nazIF+zFAYz1gVPEfC34GQtGxzJUUmklaYAL63368LEfrMeyw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1112,6 +1248,13 @@ packages: os: [linux] libc: [musl] + '@oxc-parser/binding-linux-x64-musl@0.127.0': + resolution: {integrity: sha512-5eY0B/bxf1xIUxb4NOTvOI3KWtBQfPWYyKAzgcrCt0mDibSZygVpO1Pz8bkeiSZ5Jj9+M09dkggG3H8I5d0Uyg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + '@oxc-parser/binding-linux-x64-musl@0.148.0': resolution: {integrity: sha512-qQoPDZUFV0bh9xA09XydmkjMBpgc1ukJuhMvzQ9QeVmFaHTS9W5TE5CoLmSl3QQyUP9OuHO3x/WPZTIIZPWR3Q==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1125,6 +1268,12 @@ packages: cpu: [arm64] os: [openharmony] + '@oxc-parser/binding-openharmony-arm64@0.127.0': + resolution: {integrity: sha512-Gld0ajrFTUXNtdw20fVBuTQx66FA75nIVg+//pPfR3sXkuABB4mTBhl3r9JNzrJpgW//qiwxf0nWXUWGJSL3UQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + '@oxc-parser/binding-openharmony-arm64@0.148.0': resolution: {integrity: sha512-1UGbaQWEXUCLqAmaR5kwRDjx/R4S5LQKZkM9CHmaHkuKhriOF32aRLfS0jCRNE2yGQJLMEA1z9UucbBVqjXnDw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1136,12 +1285,23 @@ packages: engines: {node: '>=14.0.0'} cpu: [wasm32] + '@oxc-parser/binding-wasm32-wasi@0.127.0': + resolution: {integrity: sha512-T6KVD7rhLzFlwGRXMnxUFfkCZD8FHnb968wVXW1mXzgRFc5RNXOBY2mPPDZ77x5Ln76ltLMgtPg0cOkU1NSrEQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + '@oxc-parser/binding-win32-arm64-msvc@0.120.0': resolution: {integrity: sha512-1T0HKGcsz/BKo77t7+89L8Qvu4f9DoleKWHp3C5sJEcbCjDOLx3m9m722bWZTY+hANlUEs+yjlK+lBFsA+vrVQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] + '@oxc-parser/binding-win32-arm64-msvc@0.127.0': + resolution: {integrity: sha512-Ujvw4X+LD1CCGULcsQcvb4YNVoBGqt+JHgNNzGGaCImELiZLk477ifUH53gIbE7EKd933NdTi25JWEr9K2HwXw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + '@oxc-parser/binding-win32-arm64-msvc@0.148.0': resolution: {integrity: sha512-pWKdzRDNG2+NK4h/V6U/CYERcfYD6u28h5IB/VJVsrZaD3muvE58tUj22lieL5vLZ+XFi1GPv9YXckZbJZ9BLA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1154,6 +1314,12 @@ packages: cpu: [ia32] os: [win32] + '@oxc-parser/binding-win32-ia32-msvc@0.127.0': + resolution: {integrity: sha512-0cwxKO7KHQQQfo4Uf4B2SQrhgm+cJaP9OvFFhx52Tkg4bezsacu83GB2/In5bC415Ueeym+kXdnge/57rbSfTw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + '@oxc-parser/binding-win32-ia32-msvc@0.148.0': resolution: {integrity: sha512-i3p4x+mvwtjcE1J5HM6V7ggsbXiznExN/4MkNyOy3dfXrVV3bnkSfmZxvo6/84qCVX4ShkpNE1SKt9biIF31GQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1166,6 +1332,12 @@ packages: cpu: [x64] os: [win32] + '@oxc-parser/binding-win32-x64-msvc@0.127.0': + resolution: {integrity: sha512-rOrnSQSCbhI2kowr9XxE7m9a8oQXnBHjnS6j95LxxAnEZ0+Fz20WlRXG4ondQb+ejjt2KOsa65sE6++L6kUd+w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@oxc-parser/binding-win32-x64-msvc@0.148.0': resolution: {integrity: sha512-Ye6vB7VQulghWYkYkECOBYFRVEizz4XyRTUAv+t8BuyurhKU7uD0P9eowL+mKG5Mf8MSYx+DI3Cm8SKZvYG7bQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1179,6 +1351,9 @@ packages: '@oxc-project/types@0.120.0': resolution: {integrity: sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg==} + '@oxc-project/types@0.127.0': + resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} + '@oxc-project/types@0.147.0': resolution: {integrity: sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==} @@ -1188,6 +1363,109 @@ packages: '@oxc-project/types@0.149.0': resolution: {integrity: sha512-Efcc+iF0j3Bf67YjEqIqWXbX5XddXoK/Mw4K1/JuXwRCZ8N16VR7iT23nlCc9XrveFVh/E5Rqs2StT0V8v9LdA==} + '@oxc-resolver/binding-android-arm-eabi@11.21.2': + resolution: {integrity: sha512-xQoCRv+gKax9KTdwdaQNnAFOai8neay7g3jExDIORzhbrejwGSJaZNTdOJHR5ziLg2joMxOCMOFMo4zuxza2uQ==} + cpu: [arm] + os: [android] + + '@oxc-resolver/binding-android-arm64@11.21.2': + resolution: {integrity: sha512-HF5oiE2L05yInPYCFD/4uxSrEZW4SuIfn99Y6L1xnJnzl066JR+MJs2rIdstw8A2MPlAKH+13dpFPNycjqzvGg==} + cpu: [arm64] + os: [android] + + '@oxc-resolver/binding-darwin-arm64@11.21.2': + resolution: {integrity: sha512-UX4u49CVCAD8QZNELaW8eMGgMAGwFWYEPbvNsh+3r/gs4NX3KfpiACMVwRQT0EuH3uat9hM5Zl+Ppm9pJD8tgg==} + cpu: [arm64] + os: [darwin] + + '@oxc-resolver/binding-darwin-x64@11.21.2': + resolution: {integrity: sha512-J9xPx7YBkrRmJ+xl561ztnMWEc1aOyjEIxBiGX1dVb3u7bGSnfObfcZk+Pd+uM0HZAPNsQ1xvD8j52A/uOSqNQ==} + cpu: [x64] + os: [darwin] + + '@oxc-resolver/binding-freebsd-x64@11.21.2': + resolution: {integrity: sha512-fRlt7OvSaQkWj6+EDTVxawVxOlqJB2QSnBfkeCyK4RTvsGctbw3BiH2Tb7DzMs7bikc4BRBpvWP5zF9K8b54Zg==} + cpu: [x64] + os: [freebsd] + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.21.2': + resolution: {integrity: sha512-gK+vPUcPQITkGwBKpZGrcDHSlU6eDGl7AQacxS2CEKAZIBHWkOVFeJwLZ4tYnA1acJqRM5lt7yYwPCVqGHIJ7A==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm-musleabihf@11.21.2': + resolution: {integrity: sha512-L/Rgas7SrOKy/z7IH+HxSFRqVO4PuLDKLEGKvnhoCBBq3UJ0YzGBou3qMzaAGgkJwsIvX2pBF+7ojzfUhLiZxQ==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm64-gnu@11.21.2': + resolution: {integrity: sha512-CUEYvlX1Fk7E9kUMzuswru1J9HLxMwnpDeQGjQuI4ZH+iNCoa2X9T+pvyzrbsgl7WnIeTFTlNlHsRfVdf5g9/g==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-arm64-musl@11.21.2': + resolution: {integrity: sha512-ViN1ZibQyxwC67GpoP33oo+S9UyUnkog13vzQb9+v9bCNvrVzJuk0MRdacjtv/9xfRMVF/eqHFyl8YOeykI10Q==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-linux-ppc64-gnu@11.21.2': + resolution: {integrity: sha512-CU1sCqWnhGqYD5I1HedHk5pujrn7ssDkNB/AEQd/pd3D/EojVSgJUlpbafwIbyhia3PgIfkvdFpRPWSALzVumA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-riscv64-gnu@11.21.2': + resolution: {integrity: sha512-jWVyZtIHca4Gb96x7dag+y69vlei7ffjrsveLkmf2ZhqEAz6ZSBnY1GWvgZUaZlwZP62A3xD092BW97Q5VGc+g==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-riscv64-musl@11.21.2': + resolution: {integrity: sha512-LF29obFqNgBUgDX7rmUK7M4D0JQG5LxhYzn3xXmECcHU9aQAdWG7NiY052qybtesEdwHQXKNTWYQ7mTsybNvWg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-linux-s390x-gnu@11.21.2': + resolution: {integrity: sha512-+NYcm+cCHBbtdQQ3A4phQTSuVRYnNHz7wrl9XRAPEovcdoqi0mb1K5ZOl+jN54ZD+q1zz3V0vltbFJmzecJKmw==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-x64-gnu@11.21.2': + resolution: {integrity: sha512-UQqZDdG2r2HhAOsZEgufkIWHPQ886IUyuJQkoZByvzhW8j51R4UNzGBJFkTiTnLhQnggwJRdJgFWK4uY6ZVIMw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-x64-musl@11.21.2': + resolution: {integrity: sha512-3Q9PMRjalWkT6NZ4jfujuqTCFwoWErg3y3BnOgb544B8IMw4PktiwWOigMfOHNRLMghZeJ7hpfpZf4CP7rV7Og==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-openharmony-arm64@11.21.2': + resolution: {integrity: sha512-Eljeq3ndtyKhM+Es8LITi4Zl2htzuRZcrPMF3kMCsrILztvU6AjZ3FuEhHHKobPUB9rMpzLpJP5bDqXI7r+iog==} + cpu: [arm64] + os: [openharmony] + + '@oxc-resolver/binding-wasm32-wasi@11.21.2': + resolution: {integrity: sha512-HGDbNsIywqc4LxU38+CTJNnB/6BF7rheWOJ259b4eE0aEnelYblC8x+1tEd63bp31fYne63RQz9Jb4yVnC7Yig==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@oxc-resolver/binding-win32-arm64-msvc@11.21.2': + resolution: {integrity: sha512-iWx25CBEgH49iE9q5coEGI/jb1jl5kkCY9z6U5Og67xCkQ/WFMDc2J5U78+AE91SUxM2NqSLhJC8/PLfWnImww==} + cpu: [arm64] + os: [win32] + + '@oxc-resolver/binding-win32-x64-msvc@11.21.2': + resolution: {integrity: sha512-VPoCAhKvCQTlG7vxqaBXcmuvbh77BfnGXj8g0pbvVXpm1F/R8rDVwqIWcEbMzrI1JvJlm8v7T9uMVQb6UctMRg==} + cpu: [x64] + os: [win32] + '@oxc-transform-react/binding-android-arm-eabi@0.147.0': resolution: {integrity: sha512-VRKQm1E8GA0yqii9t9f7EAOn3Y7wikAPjSdUDVQlfZx+p29MRG72oaVnS8vRjug2/a6wy8wrjlDiYi0fgvks3g==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1707,6 +1985,15 @@ packages: '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + '@rollup/pluginutils@5.4.0': + resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} @@ -1756,6 +2043,108 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@storybook/addon-a11y@10.6.0': + resolution: {integrity: sha512-Z/CZor/F8H74/+IoeLroEQXQoVxvmjVtUgATJ95KPBolHxMaHLXLsYU0wosIiiORxmfRCnpen5pQl1n4+pBWQw==} + peerDependencies: + storybook: ^10.6.0 + + '@storybook/addon-themes@10.6.0': + resolution: {integrity: sha512-PJtRtaCzKCAcouEqkHevMwv0aeQNsYWab4K/qszNGuSyFXxdcPQguKwmbB6Ajd/FxW+Of/mLXjRgpjrGVSgm1g==} + peerDependencies: + storybook: ^10.6.0 + + '@storybook/addon-vitest@10.6.0': + resolution: {integrity: sha512-GY2SwSXLNgyMqjQ59J7YTkEQTHdEbBMggOvbbuhuPUBhWo97OGSDCmn1UW3nXDZvkoWyWhF9T9628m1uei7FbA==} + peerDependencies: + '@vitest/browser': ^3.0.0 || ^4.0.0 + '@vitest/browser-playwright': ^4.0.0 + '@vitest/runner': ^3.0.0 || ^4.0.0 + storybook: ^10.6.0 + vitest: 4.1.11 + peerDependenciesMeta: + '@vitest/browser': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/runner': + optional: true + vitest: + optional: true + + '@storybook/builder-vite@10.6.0': + resolution: {integrity: sha512-7zt8tYB6dnSO8fbjUz91MHexGDpf9HrLlA3FAmlVb+2ZL3eWUs499L3I6BDUymcWu7oW5WCBJlB8ERj8ZLLzig==} + peerDependencies: + storybook: ^10.6.0 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + + '@storybook/global@5.0.0': + resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==} + + '@storybook/icons@2.1.0': + resolution: {integrity: sha512-Fxh9vYpX9bQqFeHRiY8h2ApeRGDzRSMLwJwNZ/AIRqnyOKHxRKL+yFe+ctEkVJmuptRE9u1Hrn8ZZNHyfDKKNg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@storybook/react-dom-shim@10.6.0': + resolution: {integrity: sha512-ZE0l4xL6d00rKFeJhRDuh0t3r+/2ffhx47lIyaSWrTD5kuM3nDjKDxCIWwX4E3gLV+oNX8E1prXRMuEWJUJwJg==} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react-dom': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.6.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@storybook/react-vite@10.6.0': + resolution: {integrity: sha512-nc1dqnmjGOcDJvPCkAn4Hmu1gi7dNHHoibNmlBp0d1adosejELZxuNjHBkPl/KxZjbzP3jljQHnHB/guxd8e2g==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.6.0 + typescript: '>= 4.9.x' + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + + '@storybook/react@10.6.0': + resolution: {integrity: sha512-0xfYYPRHcr5ta1hmXJCSezH4JGuUHJ+xVF9f1j+Y/MWEMOQE4wnaKWKsk/HLrDjZkT5hKfvmogt9b0yduP6zEQ==} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react-dom': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.6.0 + typescript: '>= 4.9.x' + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + typescript: + optional: true + + '@storybook/tanstack-react@10.6.0': + resolution: {integrity: sha512-Eim3drtxLbwrj0iL7HVJ99qCosD2z9fAhaNCkaUsmXUHfOJ0XpWXfBR1jtx++DzBJizh7IWs//no4twMRq95jQ==} + peerDependencies: + '@tanstack/react-router': ^1.168.10 + '@tanstack/react-start': ^1.167.16 + '@tanstack/router-core': ^1.168.9 + '@tanstack/start-client-core': ^1.167.9 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.6.0 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@tanstack/react-start': + optional: true + '@tanstack/start-client-core': + optional: true + '@tailwindcss/node@4.3.3': resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==} @@ -2229,6 +2618,10 @@ packages: resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + '@testing-library/react@16.3.3': resolution: {integrity: sha512-Uo193NgQbPMz6lrrhtRQQFcMC6Re/ELLFbbuVL30WDlZxlpZf9/lMHTAVxPRLw1q1iu9OJmR1c2BLiENRstdBg==} engines: {node: '>=18'} @@ -2259,6 +2652,18 @@ packages: '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/better-sqlite3@9.6.0': resolution: {integrity: sha512-ZEEwBSgMu7GYJOynoagg5X9JbxfL6dTJDsgViJIqh67jV44kyOr9RXfmFjLK5rzC4MWssP06t9hu/JwGDnUbCg==} @@ -2268,6 +2673,9 @@ packages: '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/doctrine@0.0.9': + resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} + '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} @@ -2285,6 +2693,9 @@ packages: '@types/react@19.3.0': resolution: {integrity: sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==} + '@types/resolve@1.20.6': + resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==} + '@types/validate-npm-package-name@4.0.2': resolution: {integrity: sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==} @@ -2369,6 +2780,9 @@ packages: '@vitest/browser': optional: true + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + '@vitest/expect@4.1.11': resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==} @@ -2383,6 +2797,9 @@ packages: vite: optional: true + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + '@vitest/pretty-format@4.1.11': resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==} @@ -2392,9 +2809,15 @@ packages: '@vitest/snapshot@4.1.11': resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==} + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/spy@4.1.11': resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==} + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@vitest/utils@4.1.11': resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==} @@ -2507,6 +2930,9 @@ packages: cpu: [x64] os: [win32] + '@webcontainer/env@1.1.1': + resolution: {integrity: sha512-6aN99yL695Hi9SuIk1oC88l9o0gmxL1nGWWQ/kNy81HigJ0FoaoTXpytCj6ItzgyCEwA9kF1wixsTuv5cjsgng==} + '@yuku-codegen/binding-android-arm64@0.9.5': resolution: {integrity: sha512-jrOY5WM+AaAqkv51fHP1x28ifto4WgcZVzodLUyMU1jMWn5Sq+VciRdk/n8E0Ey5w4p4cWWE+m5GfXWOYh7Kzw==} cpu: [arm64] @@ -2772,6 +3198,10 @@ packages: caniuse-lite@1.0.30001810: resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==} + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} @@ -2780,6 +3210,10 @@ packages: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + chokidar@5.0.0: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} @@ -2874,6 +3308,9 @@ packages: srvx: optional: true + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -2932,6 +3369,10 @@ packages: babel-plugin-macros: optional: true + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} @@ -2968,9 +3409,16 @@ packages: resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} engines: {node: '>=0.3.1'} + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dot-prop@6.0.1: resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} engines: {node: '>=10'} @@ -3088,6 +3536,10 @@ packages: emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + empathic@2.0.1: + resolution: {integrity: sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==} + engines: {node: '>=14'} + encodeurl@2.0.0: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} @@ -3171,9 +3623,16 @@ packages: engines: {node: '>=4'} hasBin: true + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} @@ -3310,7 +3769,11 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} - goober@2.1.19: + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + + goober@2.1.19: resolution: {integrity: sha512-U7veizMqxyKlM58+Z5j2ngJBH/r9siDmxpvNxSw0PylF6WQvrASJEZrxh1hidRBJc2jqoBVSyOban5u8m+6Rxg==} peerDependencies: csstype: ^3.0.10 @@ -3391,6 +3854,10 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -3405,6 +3872,10 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + is-docker@2.2.1: resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} engines: {node: '>=8'} @@ -3545,6 +4016,9 @@ packages: engines: {node: '>=6'} hasBin: true + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + jsonfile@6.2.1: resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} @@ -3718,6 +4192,13 @@ packages: resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} engines: {node: '>=18'} + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -3733,6 +4214,9 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magic-string@1.4.1: + resolution: {integrity: sha512-8lyCu36ErXR0J9uaGKlKQoiLZKmtI63YGLE8G2o9jyRPdr4X47LusSOwgOJOzcVtp81fTAAjxR7BwKz682Jhow==} + magicast@0.5.4: resolution: {integrity: sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==} @@ -3783,6 +4267,10 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + minimatch@10.2.6: resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} engines: {node: 18 || 20 || >=22} @@ -3790,6 +4278,10 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} @@ -3896,6 +4388,10 @@ packages: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} + open@10.2.0: + resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} + engines: {node: '>=18'} + open@11.0.2: resolution: {integrity: sha512-RWqF+pBSkqecEvCKOn8QYhaNdRMJDZRIrlS/7rTDdLHaPcfXGCZ/h8zb413NfvdeAV0MR7T1yJcA34/q+CSm1Q==} engines: {node: '>=20'} @@ -3912,10 +4408,17 @@ packages: resolution: {integrity: sha512-WyPWZlcIm+Fkte63FGfgFB8mAAk33aH9h5N9lphXVOHSXEBFFsmYdOBedVKly363aWABjZdaj/m9lBfEY4wt+w==} engines: {node: ^20.19.0 || >=22.12.0} + oxc-parser@0.127.0: + resolution: {integrity: sha512-bkgD4qHlN7WxLdX8bLXdaU54TtQtAIg/ZBAfm0aje/mo3MRDo3P0hZSgr4U7O3xfX+fQmR5AP04JS/TGcZLcFA==} + engines: {node: ^20.19.0 || >=22.12.0} + oxc-parser@0.148.0: resolution: {integrity: sha512-syxUKHeUll89RIABQADcI7sikYrwyssvA6gj4phSSIPezKVM8yMaLAiLLSc7fmzVvrwybfFGFbW5zme9sX87rg==} engines: {node: ^20.19.0 || >=22.12.0} + oxc-resolver@11.21.2: + resolution: {integrity: sha512-w5tLwYN3Zo24w5EeWJjJWZOwhYqTtC8PS2B1tIt7BZUuqTIcU07sQValbDw+rq7+AuAGzOHklgK+ifsy4lpXfw==} + oxc-transform-react@0.147.0: resolution: {integrity: sha512-u3u8qZFlcQPawQx/kvMnK6csGPAQW5JJ4WWsmqYb3djL4SHfvsu5ftHCTkWgzdYkGfIjq5aSoBetVXfPb1ZiXw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3993,12 +4496,23 @@ packages: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} engines: {node: '>=12'} + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + path-to-regexp@8.4.2: resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -4094,6 +4608,15 @@ packages: '@types/react': optional: true + react-docgen-typescript@2.4.0: + resolution: {integrity: sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg==} + peerDependencies: + typescript: '>= 4.3.x' + + react-docgen@8.0.3: + resolution: {integrity: sha512-aEZ9qP+/M+58x2qgfSFEWH1BxLyHe5+qkLNJOZQb5iGS017jpbRnoKhNRrXPeA6RfBrZO5wZrT9DMC1UqE1f1w==} + engines: {node: ^20.9.0 || >=22} + react-dom@19.3.0: resolution: {integrity: sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==} peerDependencies: @@ -4114,6 +4637,10 @@ packages: resolution: {integrity: sha512-mFAyJq9vUbSTARLZUvAEf1z3YxlvAwswbmxMx2mPA/MSm4KmpwvwvhsH/NIrZhyOuwD60Lzyw2qh83uCbgTPYw==} engines: {node: '>= 4'} + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -4128,6 +4655,11 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} + hasBin: true + restore-cursor@5.1.0: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} @@ -4296,6 +4828,21 @@ packages: resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} engines: {node: '>=18'} + storybook@10.6.0: + resolution: {integrity: sha512-H48X6AURAFTpfN2zEux6vNesee5Oq8Gxj1IanYmblPZ218sfKSeyyerF1NC7GD7BrruoC8L4/cU0zII4y+d6zw==} + hasBin: true + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + prettier: ^2 || ^3 + vite-plus: ^0.1.15 || ^0.2.0 + peerDependenciesMeta: + '@types/react': + optional: true + prettier: + optional: true + vite-plus: + optional: true + string-width@7.2.0: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} @@ -4324,10 +4871,22 @@ packages: resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} engines: {node: '>=18'} + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + strip-indent@4.1.1: + resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==} + engines: {node: '>=12'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + systeminformation@5.33.6: resolution: {integrity: sha512-hMOQG/eRUzuopuYGGdl8ntkau0nEC7fOaRoTUg1RSr2GTQIk2VNa76DA0+ApajkGfzmcgAupgIP/vt+jtoe5EA==} engines: {node: '>=10.0.0'} @@ -4359,10 +4918,18 @@ packages: resolution: {integrity: sha512-9YodfrxS9g9IbFr/KOjE5bAeJ0p61n3bW6mqvy0jtoeKd1kTW1Cxm0oulm6KX2lyM9Gl6WIe8nEbY7LWv5ZJww==} engines: {node: ^20.0.0 || >=22.0.0} + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + tinyrainbow@3.1.1: resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} engines: {node: '>=14.0.0'} + tinyspy@4.0.6: + resolution: {integrity: sha512-u8KszXvGfU68hVcZpRHKG28T0krMuv2G5nDhiHaMLen/gIuFEgIJhaJuO69qjnXg5paSrbPMFfx3brNuN8eVSg==} + engines: {node: '>=14.0.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -4381,6 +4948,10 @@ packages: peerDependencies: typescript: '>=4.8.4' + ts-dedent@2.3.0: + resolution: {integrity: sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==} + engines: {node: '>=6.10'} + ts-morph@26.0.0: resolution: {integrity: sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==} @@ -4679,6 +5250,10 @@ packages: utf-8-validate: optional: true + wsl-utils@0.1.0: + resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} + engines: {node: '>=18'} + wsl-utils@1.0.0: resolution: {integrity: sha512-Hl0ZOAs672vg+06kfujwRhoS6/jehvULrlFkuF2dRu6pHgA8U06h3xqNIqNNU1LTXPcedxByAR4GS6pwQK0mgA==} engines: {node: '>=20'} @@ -4720,6 +5295,8 @@ packages: snapshots: + '@adobe/css-tools@4.5.0': {} + '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.29.7 @@ -4968,17 +5545,49 @@ snapshots: '@drizzle-team/brocli@0.10.2': {} + '@emnapi/core@1.11.0': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + '@emnapi/core@1.11.3': dependencies: '@emnapi/wasi-threads': 1.2.3 tslib: 2.8.1 optional: true + '@emnapi/core@1.9.2': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.0': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.11.3': dependencies: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.9.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/wasi-threads@1.2.3': dependencies: tslib: 2.8.1 @@ -5243,9 +5852,17 @@ snapshots: dependencies: hono: 4.13.5 + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(typescript@6.0.3)': + dependencies: + glob: 13.0.6 + react-docgen-typescript: 2.4.0(typescript@6.0.3) + vite: '@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)' + optionalDependencies: + typescript: 6.0.3 + '@jridgewell/gen-mapping@0.3.13': dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.6.0 '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/remapping@2.3.5': @@ -5255,12 +5872,12 @@ snapshots: '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/sourcemap-codec@1.6.0': {} '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.6.0 '@modelcontextprotocol/sdk@1.30.0(supports-color@7.2.0)(zod@3.25.76)': dependencies: @@ -5284,6 +5901,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@napi-rs/wasm-runtime@1.2.3(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0)': + dependencies: + '@emnapi/core': 1.11.0 + '@emnapi/runtime': 1.11.0 + '@tybys/wasm-util': 0.10.3 + optional: true + '@napi-rs/wasm-runtime@1.2.3(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': dependencies: '@emnapi/core': 1.11.3 @@ -5291,6 +5915,13 @@ snapshots: '@tybys/wasm-util': 0.10.3 optional: true + '@napi-rs/wasm-runtime@1.2.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@tybys/wasm-util': 0.10.3 + optional: true + '@neodrag/core@3.0.0-next.11': {} '@neodrag/solid@3.0.0-next.11(@neodrag/core@3.0.0-next.11)(solid-js@1.9.14)': @@ -5330,96 +5961,144 @@ snapshots: '@oxc-parser/binding-android-arm-eabi@0.120.0': optional: true + '@oxc-parser/binding-android-arm-eabi@0.127.0': + optional: true + '@oxc-parser/binding-android-arm-eabi@0.148.0': optional: true '@oxc-parser/binding-android-arm64@0.120.0': optional: true + '@oxc-parser/binding-android-arm64@0.127.0': + optional: true + '@oxc-parser/binding-android-arm64@0.148.0': optional: true '@oxc-parser/binding-darwin-arm64@0.120.0': optional: true + '@oxc-parser/binding-darwin-arm64@0.127.0': + optional: true + '@oxc-parser/binding-darwin-arm64@0.148.0': optional: true '@oxc-parser/binding-darwin-x64@0.120.0': optional: true + '@oxc-parser/binding-darwin-x64@0.127.0': + optional: true + '@oxc-parser/binding-darwin-x64@0.148.0': optional: true '@oxc-parser/binding-freebsd-x64@0.120.0': optional: true + '@oxc-parser/binding-freebsd-x64@0.127.0': + optional: true + '@oxc-parser/binding-freebsd-x64@0.148.0': optional: true '@oxc-parser/binding-linux-arm-gnueabihf@0.120.0': optional: true + '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0': + optional: true + '@oxc-parser/binding-linux-arm-gnueabihf@0.148.0': optional: true '@oxc-parser/binding-linux-arm-musleabihf@0.120.0': optional: true + '@oxc-parser/binding-linux-arm-musleabihf@0.127.0': + optional: true + '@oxc-parser/binding-linux-arm-musleabihf@0.148.0': optional: true '@oxc-parser/binding-linux-arm64-gnu@0.120.0': optional: true + '@oxc-parser/binding-linux-arm64-gnu@0.127.0': + optional: true + '@oxc-parser/binding-linux-arm64-gnu@0.148.0': optional: true '@oxc-parser/binding-linux-arm64-musl@0.120.0': optional: true + '@oxc-parser/binding-linux-arm64-musl@0.127.0': + optional: true + '@oxc-parser/binding-linux-arm64-musl@0.148.0': optional: true '@oxc-parser/binding-linux-ppc64-gnu@0.120.0': optional: true + '@oxc-parser/binding-linux-ppc64-gnu@0.127.0': + optional: true + '@oxc-parser/binding-linux-ppc64-gnu@0.148.0': optional: true '@oxc-parser/binding-linux-riscv64-gnu@0.120.0': optional: true + '@oxc-parser/binding-linux-riscv64-gnu@0.127.0': + optional: true + '@oxc-parser/binding-linux-riscv64-gnu@0.148.0': optional: true '@oxc-parser/binding-linux-riscv64-musl@0.120.0': optional: true + '@oxc-parser/binding-linux-riscv64-musl@0.127.0': + optional: true + '@oxc-parser/binding-linux-riscv64-musl@0.148.0': optional: true '@oxc-parser/binding-linux-s390x-gnu@0.120.0': optional: true + '@oxc-parser/binding-linux-s390x-gnu@0.127.0': + optional: true + '@oxc-parser/binding-linux-s390x-gnu@0.148.0': optional: true '@oxc-parser/binding-linux-x64-gnu@0.120.0': optional: true + '@oxc-parser/binding-linux-x64-gnu@0.127.0': + optional: true + '@oxc-parser/binding-linux-x64-gnu@0.148.0': optional: true '@oxc-parser/binding-linux-x64-musl@0.120.0': optional: true + '@oxc-parser/binding-linux-x64-musl@0.127.0': + optional: true + '@oxc-parser/binding-linux-x64-musl@0.148.0': optional: true '@oxc-parser/binding-openharmony-arm64@0.120.0': optional: true + '@oxc-parser/binding-openharmony-arm64@0.127.0': + optional: true + '@oxc-parser/binding-openharmony-arm64@0.148.0': optional: true @@ -5431,21 +6110,37 @@ snapshots: - '@emnapi/runtime' optional: true + '@oxc-parser/binding-wasm32-wasi@0.127.0': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@napi-rs/wasm-runtime': 1.2.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + optional: true + '@oxc-parser/binding-win32-arm64-msvc@0.120.0': optional: true + '@oxc-parser/binding-win32-arm64-msvc@0.127.0': + optional: true + '@oxc-parser/binding-win32-arm64-msvc@0.148.0': optional: true '@oxc-parser/binding-win32-ia32-msvc@0.120.0': optional: true + '@oxc-parser/binding-win32-ia32-msvc@0.127.0': + optional: true + '@oxc-parser/binding-win32-ia32-msvc@0.148.0': optional: true '@oxc-parser/binding-win32-x64-msvc@0.120.0': optional: true + '@oxc-parser/binding-win32-x64-msvc@0.127.0': + optional: true + '@oxc-parser/binding-win32-x64-msvc@0.148.0': optional: true @@ -5453,6 +6148,8 @@ snapshots: '@oxc-project/types@0.120.0': {} + '@oxc-project/types@0.127.0': {} + '@oxc-project/types@0.147.0': {} '@oxc-project/types@0.148.0': @@ -5460,6 +6157,67 @@ snapshots: '@oxc-project/types@0.149.0': {} + '@oxc-resolver/binding-android-arm-eabi@11.21.2': + optional: true + + '@oxc-resolver/binding-android-arm64@11.21.2': + optional: true + + '@oxc-resolver/binding-darwin-arm64@11.21.2': + optional: true + + '@oxc-resolver/binding-darwin-x64@11.21.2': + optional: true + + '@oxc-resolver/binding-freebsd-x64@11.21.2': + optional: true + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.21.2': + optional: true + + '@oxc-resolver/binding-linux-arm-musleabihf@11.21.2': + optional: true + + '@oxc-resolver/binding-linux-arm64-gnu@11.21.2': + optional: true + + '@oxc-resolver/binding-linux-arm64-musl@11.21.2': + optional: true + + '@oxc-resolver/binding-linux-ppc64-gnu@11.21.2': + optional: true + + '@oxc-resolver/binding-linux-riscv64-gnu@11.21.2': + optional: true + + '@oxc-resolver/binding-linux-riscv64-musl@11.21.2': + optional: true + + '@oxc-resolver/binding-linux-s390x-gnu@11.21.2': + optional: true + + '@oxc-resolver/binding-linux-x64-gnu@11.21.2': + optional: true + + '@oxc-resolver/binding-linux-x64-musl@11.21.2': + optional: true + + '@oxc-resolver/binding-openharmony-arm64@11.21.2': + optional: true + + '@oxc-resolver/binding-wasm32-wasi@11.21.2': + dependencies: + '@emnapi/core': 1.11.0 + '@emnapi/runtime': 1.11.0 + '@napi-rs/wasm-runtime': 1.2.3(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0) + optional: true + + '@oxc-resolver/binding-win32-arm64-msvc@11.21.2': + optional: true + + '@oxc-resolver/binding-win32-x64-msvc@11.21.2': + optional: true + '@oxc-transform-react/binding-android-arm-eabi@0.147.0': optional: true @@ -5710,6 +6468,12 @@ snapshots: '@rolldown/pluginutils@1.0.1': {} + '@rollup/pluginutils@5.4.0': + dependencies: + '@types/estree': 1.0.9 + estree-walker: 2.0.2 + picomatch: 4.0.7 + '@sec-ant/readable-stream@0.4.1': {} '@shadcn/lint@0.1.0(supports-color@7.2.0)(typescript@6.0.3)': @@ -5761,6 +6525,111 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@storybook/addon-a11y@10.6.0(storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)))': + dependencies: + '@storybook/global': 5.0.0 + axe-core: 4.13.0 + storybook: 10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) + + '@storybook/addon-themes@10.6.0(storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)))': + dependencies: + storybook: 10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) + ts-dedent: 2.3.0 + + '@storybook/addon-vitest@10.6.0(@vitest/browser-playwright@4.1.11)(@vitest/browser@4.1.11)(@vitest/runner@4.1.11)(react@19.3.0)(storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)))(vitest@4.1.11)': + dependencies: + '@storybook/global': 5.0.0 + '@storybook/icons': 2.1.0(react@19.3.0) + storybook: 10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) + optionalDependencies: + '@vitest/browser': 4.1.11(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(vitest@4.1.11) + '@vitest/browser-playwright': 4.1.11(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(playwright@1.63.0)(vitest@4.1.11) + '@vitest/runner': 4.1.11 + vitest: 4.1.11(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/browser-preview@4.1.11(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(vitest@4.1.11))(@vitest/coverage-v8@4.1.11)(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) + transitivePeerDependencies: + - react + + '@storybook/builder-vite@10.6.0(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)))': + dependencies: + storybook: 10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) + ts-dedent: 2.3.0 + vite: '@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)' + + '@storybook/global@5.0.0': {} + + '@storybook/icons@2.1.0(react@19.3.0)': + dependencies: + react: 19.3.0 + + '@storybook/react-dom-shim@10.6.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)))': + dependencies: + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + storybook: 10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@storybook/react-vite@10.6.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)))(supports-color@7.2.0)(typescript@6.0.3)': + dependencies: + '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(typescript@6.0.3) + '@rollup/pluginutils': 5.4.0 + '@storybook/builder-vite': 10.6.0(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))) + '@storybook/react': 10.6.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)))(supports-color@7.2.0)(typescript@6.0.3) + empathic: 2.0.1 + magic-string: 1.4.1 + react: 19.3.0 + react-docgen: 8.0.3(supports-color@7.2.0) + react-dom: 19.3.0(react@19.3.0) + resolve: 1.22.12 + storybook: 10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) + tsconfig-paths: 4.2.0 + vite: '@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)' + optionalDependencies: + typescript: 6.0.3 + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + - rollup + - supports-color + + '@storybook/react@10.6.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)))(supports-color@7.2.0)(typescript@6.0.3)': + dependencies: + '@storybook/global': 5.0.0 + '@storybook/react-dom-shim': 10.6.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))) + react: 19.3.0 + react-docgen: 8.0.3(supports-color@7.2.0) + react-docgen-typescript: 2.4.0(typescript@6.0.3) + react-dom: 19.3.0(react@19.3.0) + storybook: 10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@storybook/tanstack-react@10.6.0(@tanstack/react-router@1.170.32(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@tanstack/react-start@1.168.49(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(crossws@0.4.12(srvx@0.11.22))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.6)(supports-color@7.2.0))(@tanstack/router-core@1.171.27)(@tanstack/start-client-core@1.170.27)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)))(supports-color@7.2.0)(typescript@6.0.3)': + dependencies: + '@storybook/builder-vite': 10.6.0(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))) + '@storybook/react': 10.6.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)))(supports-color@7.2.0)(typescript@6.0.3) + '@storybook/react-vite': 10.6.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)))(supports-color@7.2.0)(typescript@6.0.3) + '@tanstack/react-router': 1.170.32(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/router-core': 1.171.27 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + storybook: 10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) + vite: '@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)' + optionalDependencies: + '@tanstack/react-start': 1.168.49(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(crossws@0.4.12(srvx@0.11.22))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.6)(supports-color@7.2.0) + '@tanstack/start-client-core': 1.170.27 + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + - rollup + - supports-color + - typescript + '@tailwindcss/node@4.3.3': dependencies: '@jridgewell/remapping': 2.3.5 @@ -6293,6 +7162,15 @@ snapshots: picocolors: 1.1.1 pretty-format: 27.5.1 + '@testing-library/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.5.0 + aria-query: 5.3.0 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + '@testing-library/react@16.3.3(@testing-library/dom@10.4.1)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@babel/runtime': 7.29.7 @@ -6320,6 +7198,27 @@ snapshots: '@types/aria-query@5.0.4': {} + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.8 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.8 + '@types/better-sqlite3@9.6.0': dependencies: '@types/node': 24.13.3 @@ -6331,6 +7230,8 @@ snapshots: '@types/deep-eql@4.0.2': {} + '@types/doctrine@0.0.9': {} + '@types/estree@1.0.9': {} '@types/json-schema@7.0.15': {} @@ -6347,6 +7248,8 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/resolve@1.20.6': {} + '@types/validate-npm-package-name@4.0.2': {} '@typescript-eslint/parser@8.70.0(supports-color@7.2.0)(typescript@6.0.3)': @@ -6415,7 +7318,7 @@ snapshots: '@vitest/mocker': 4.1.11(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) playwright: 1.63.0 tinyrainbow: 3.1.1 - vitest: 4.1.11(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/browser-preview@4.1.11)(@vitest/coverage-v8@4.1.11)(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) + vitest: 4.1.11(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/browser-preview@4.1.11(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(vitest@4.1.11))(@vitest/coverage-v8@4.1.11)(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) transitivePeerDependencies: - bufferutil - msw @@ -6427,7 +7330,7 @@ snapshots: '@testing-library/dom': 10.4.1 '@testing-library/user-event': 14.6.4(@testing-library/dom@10.4.1) '@vitest/browser': 4.1.11(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(vitest@4.1.11) - vitest: 4.1.11(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/browser-preview@4.1.11)(@vitest/coverage-v8@4.1.11)(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) + vitest: 4.1.11(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/browser-preview@4.1.11(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(vitest@4.1.11))(@vitest/coverage-v8@4.1.11)(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) transitivePeerDependencies: - bufferutil - msw @@ -6443,7 +7346,7 @@ snapshots: pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.1.1 - vitest: 4.1.11(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/browser-preview@4.1.11)(@vitest/coverage-v8@4.1.11)(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) + vitest: 4.1.11(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/browser-preview@4.1.11(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(vitest@4.1.11))(@vitest/coverage-v8@4.1.11)(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) ws: 8.21.3 transitivePeerDependencies: - bufferutil @@ -6463,10 +7366,18 @@ snapshots: obug: 2.1.4 std-env: 4.2.0 tinyrainbow: 3.1.1 - vitest: 4.1.11(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/browser-preview@4.1.11)(@vitest/coverage-v8@4.1.11)(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) + vitest: 4.1.11(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/browser-preview@4.1.11(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(vitest@4.1.11))(@vitest/coverage-v8@4.1.11)(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) optionalDependencies: '@vitest/browser': 4.1.11(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(vitest@4.1.11) + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + tinyrainbow: 2.0.0 + '@vitest/expect@4.1.11': dependencies: '@standard-schema/spec': 1.1.0 @@ -6484,6 +7395,10 @@ snapshots: optionalDependencies: vite: '@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)' + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 + '@vitest/pretty-format@4.1.11': dependencies: tinyrainbow: 3.1.1 @@ -6500,8 +7415,18 @@ snapshots: magic-string: 0.30.21 pathe: 2.0.3 + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.6 + '@vitest/spy@4.1.11': {} + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + '@vitest/utils@4.1.11': dependencies: '@vitest/pretty-format': 4.1.11 @@ -6556,6 +7481,8 @@ snapshots: '@voidzero-dev/vite-plus-win32-x64-msvc@0.3.2': optional: true + '@webcontainer/env@1.1.1': {} + '@yuku-codegen/binding-android-arm64@0.9.5': optional: true @@ -6756,10 +7683,20 @@ snapshots: caniuse-lite@1.0.30001810: {} + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + chai@6.2.2: {} chalk@5.6.2: {} + check-error@2.1.3: {} + chokidar@5.0.0: dependencies: readdirp: 5.1.1 @@ -6837,6 +7774,8 @@ snapshots: optionalDependencies: srvx: 0.11.22 + css.escape@1.5.1: {} + cssesc@3.0.0: {} csstype@3.2.3: {} @@ -6862,6 +7801,8 @@ snapshots: dedent@1.7.2: {} + deep-eql@5.0.2: {} + deepmerge@4.3.1: {} default-browser-id@5.0.1: {} @@ -6883,8 +7824,14 @@ snapshots: diff@8.0.4: {} + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + dom-accessibility-api@0.5.16: {} + dom-accessibility-api@0.6.3: {} + dot-prop@6.0.1: dependencies: is-obj: 2.0.0 @@ -6915,6 +7862,8 @@ snapshots: emoji-regex@10.6.0: {} + empathic@2.0.1: {} + encodeurl@2.0.0: {} enhanced-resolve@5.24.5: @@ -7041,10 +7990,14 @@ snapshots: esprima@4.0.1: {} + estree-walker@2.0.2: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.9 + esutils@2.0.3: {} + etag@1.8.1: {} eventsource-parser@3.1.1: {} @@ -7226,6 +8179,12 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@13.0.6: + dependencies: + minimatch: 10.2.6 + minipass: 7.1.3 + path-scurry: 2.0.2 + goober@2.1.19(csstype@3.2.3): dependencies: csstype: 3.2.3 @@ -7287,6 +8246,8 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 + indent-string@4.0.0: {} + inherits@2.0.4: {} ip-address@10.7.0: {} @@ -7295,6 +8256,10 @@ snapshots: is-arrayish@0.2.1: {} + is-core-module@2.16.2: + dependencies: + hasown: 2.0.4 + is-docker@2.2.1: {} is-docker@3.0.0: {} @@ -7384,6 +8349,8 @@ snapshots: json5@2.2.3: {} + jsonc-parser@3.3.1: {} + jsonfile@6.2.1: dependencies: universalify: 2.0.1 @@ -7509,6 +8476,10 @@ snapshots: chalk: 5.6.2 is-unicode-supported: 1.3.0 + loupe@3.2.1: {} + + lru-cache@11.5.2: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -7521,7 +8492,11 @@ snapshots: magic-string@0.30.21: dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.6.0 + + magic-string@1.4.1: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 magicast@0.5.4: dependencies: @@ -7560,12 +8535,16 @@ snapshots: mimic-function@5.0.1: {} + min-indent@1.0.1: {} + minimatch@10.2.6: dependencies: brace-expansion: 5.0.9 minimist@1.2.8: {} + minipass@7.1.3: {} + mrmime@2.0.1: {} ms@2.1.3: {} @@ -7578,7 +8557,7 @@ snapshots: nf3@0.3.24: {} - nitro@3.0.260610-beta(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(better-sqlite3@13.0.3)(chokidar@5.0.0)(dotenv@17.4.2)(drizzle-orm@0.45.2(@types/better-sqlite3@9.6.0)(better-sqlite3@13.0.3))(jiti@2.7.0): + nitro@3.0.260610-beta(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(better-sqlite3@13.0.3)(chokidar@5.0.0)(dotenv@17.4.2)(drizzle-orm@0.45.2(@types/better-sqlite3@9.6.0)(better-sqlite3@13.0.3))(jiti@2.7.0)(lru-cache@11.5.2): dependencies: consola: 3.4.2 crossws: 0.4.12(srvx@0.11.22) @@ -7593,7 +8572,7 @@ snapshots: rolldown: 1.2.6 srvx: 0.11.22 unenv: 2.0.0-rc.24 - unstorage: 2.0.0-alpha.7(chokidar@5.0.0)(db0@0.3.4(better-sqlite3@13.0.3)(drizzle-orm@0.45.2(@types/better-sqlite3@9.6.0)(better-sqlite3@13.0.3)))(ofetch@2.0.0-alpha.3) + unstorage: 2.0.0-alpha.7(chokidar@5.0.0)(db0@0.3.4(better-sqlite3@13.0.3)(drizzle-orm@0.45.2(@types/better-sqlite3@9.6.0)(better-sqlite3@13.0.3)))(lru-cache@11.5.2)(ofetch@2.0.0-alpha.3) optionalDependencies: dotenv: 17.4.2 jiti: 2.7.0 @@ -7675,6 +8654,13 @@ snapshots: dependencies: mimic-function: 5.0.1 + open@10.2.0: + dependencies: + default-browser: 5.5.1 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + wsl-utils: 0.1.0 + open@11.0.2: dependencies: default-browser: 5.5.1 @@ -7730,6 +8716,31 @@ snapshots: - '@emnapi/core' - '@emnapi/runtime' + oxc-parser@0.127.0: + dependencies: + '@oxc-project/types': 0.127.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.127.0 + '@oxc-parser/binding-android-arm64': 0.127.0 + '@oxc-parser/binding-darwin-arm64': 0.127.0 + '@oxc-parser/binding-darwin-x64': 0.127.0 + '@oxc-parser/binding-freebsd-x64': 0.127.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.127.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.127.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.127.0 + '@oxc-parser/binding-linux-arm64-musl': 0.127.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.127.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.127.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.127.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.127.0 + '@oxc-parser/binding-linux-x64-gnu': 0.127.0 + '@oxc-parser/binding-linux-x64-musl': 0.127.0 + '@oxc-parser/binding-openharmony-arm64': 0.127.0 + '@oxc-parser/binding-wasm32-wasi': 0.127.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.127.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.127.0 + '@oxc-parser/binding-win32-x64-msvc': 0.127.0 + oxc-parser@0.148.0: dependencies: '@oxc-project/types': 0.148.0 @@ -7755,6 +8766,28 @@ snapshots: '@oxc-parser/binding-win32-x64-msvc': 0.148.0 optional: true + oxc-resolver@11.21.2: + optionalDependencies: + '@oxc-resolver/binding-android-arm-eabi': 11.21.2 + '@oxc-resolver/binding-android-arm64': 11.21.2 + '@oxc-resolver/binding-darwin-arm64': 11.21.2 + '@oxc-resolver/binding-darwin-x64': 11.21.2 + '@oxc-resolver/binding-freebsd-x64': 11.21.2 + '@oxc-resolver/binding-linux-arm-gnueabihf': 11.21.2 + '@oxc-resolver/binding-linux-arm-musleabihf': 11.21.2 + '@oxc-resolver/binding-linux-arm64-gnu': 11.21.2 + '@oxc-resolver/binding-linux-arm64-musl': 11.21.2 + '@oxc-resolver/binding-linux-ppc64-gnu': 11.21.2 + '@oxc-resolver/binding-linux-riscv64-gnu': 11.21.2 + '@oxc-resolver/binding-linux-riscv64-musl': 11.21.2 + '@oxc-resolver/binding-linux-s390x-gnu': 11.21.2 + '@oxc-resolver/binding-linux-x64-gnu': 11.21.2 + '@oxc-resolver/binding-linux-x64-musl': 11.21.2 + '@oxc-resolver/binding-openharmony-arm64': 11.21.2 + '@oxc-resolver/binding-wasm32-wasi': 11.21.2 + '@oxc-resolver/binding-win32-arm64-msvc': 11.21.2 + '@oxc-resolver/binding-win32-x64-msvc': 11.21.2 + oxc-transform-react@0.147.0: optionalDependencies: '@oxc-transform-react/binding-android-arm-eabi': 0.147.0 @@ -7868,10 +8901,19 @@ snapshots: path-key@4.0.0: {} + path-parse@1.0.7: {} + + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.2 + minipass: 7.1.3 + path-to-regexp@8.4.2: {} pathe@2.0.3: {} + pathval@2.0.1: {} + picocolors@1.1.1: {} picomatch@2.3.2: {} @@ -7953,6 +8995,25 @@ snapshots: optionalDependencies: '@types/react': 19.3.0 + react-docgen-typescript@2.4.0(typescript@6.0.3): + dependencies: + typescript: 6.0.3 + + react-docgen@8.0.3(supports-color@7.2.0): + dependencies: + '@babel/core': 7.29.7(supports-color@7.2.0) + '@babel/traverse': 7.29.8(supports-color@7.2.0) + '@babel/types': 7.29.8 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.28.0 + '@types/doctrine': 0.0.9 + '@types/resolve': 1.20.6 + doctrine: 3.0.0 + resolve: 1.22.12 + strip-indent: 4.1.1 + transitivePeerDependencies: + - supports-color + react-dom@19.3.0(react@19.3.0): dependencies: react: 19.3.0 @@ -7972,6 +9033,11 @@ snapshots: tiny-invariant: 1.3.3 tslib: 2.8.1 + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + require-from-string@2.0.2: {} reselect@5.3.0: {} @@ -7980,6 +9046,13 @@ snapshots: resolve-pkg-maps@1.0.0: {} + resolve@1.22.12: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + restore-cursor@5.1.0: dependencies: onetime: 7.0.0 @@ -8198,6 +9271,34 @@ snapshots: stdin-discarder@0.2.2: {} + storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)): + dependencies: + '@storybook/global': 5.0.0 + '@storybook/icons': 2.1.0(react@19.3.0) + '@testing-library/dom': 10.4.1 + '@testing-library/jest-dom': 6.9.1 + '@testing-library/user-event': 14.6.4(@testing-library/dom@10.4.1) + '@vitest/expect': 3.2.4 + '@vitest/spy': 3.2.4 + '@webcontainer/env': 1.1.1 + esbuild: 0.28.2 + jsonc-parser: 3.3.1 + open: 10.2.0 + oxc-parser: 0.127.0 + oxc-resolver: 11.21.2 + recast: 0.23.21 + semver: 7.8.5 + use-sync-external-store: 1.6.0(react@19.3.0) + ws: 8.21.3 + optionalDependencies: + '@types/react': 19.3.0 + prettier: 3.9.6 + vite-plus: 0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3) + transitivePeerDependencies: + - bufferutil + - react + - utf-8-validate + string-width@7.2.0: dependencies: emoji-regex: 10.6.0 @@ -8224,10 +9325,18 @@ snapshots: strip-final-newline@4.0.0: {} + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-indent@4.1.1: {} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 + supports-preserve-symlinks-flag@1.0.0: {} + systeminformation@5.33.6: {} tailwindcss@4.3.3: {} @@ -8247,8 +9356,12 @@ snapshots: tinypool@2.1.2: {} + tinyrainbow@2.0.0: {} + tinyrainbow@3.1.1: {} + tinyspy@4.0.6: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -8261,6 +9374,8 @@ snapshots: dependencies: typescript: 6.0.3 + ts-dedent@2.3.0: {} + ts-morph@26.0.0: dependencies: '@ts-morph/common': 0.27.0 @@ -8316,10 +9431,11 @@ snapshots: rolldown: 1.2.6 vite: '@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)' - unstorage@2.0.0-alpha.7(chokidar@5.0.0)(db0@0.3.4(better-sqlite3@13.0.3)(drizzle-orm@0.45.2(@types/better-sqlite3@9.6.0)(better-sqlite3@13.0.3)))(ofetch@2.0.0-alpha.3): + unstorage@2.0.0-alpha.7(chokidar@5.0.0)(db0@0.3.4(better-sqlite3@13.0.3)(drizzle-orm@0.45.2(@types/better-sqlite3@9.6.0)(better-sqlite3@13.0.3)))(lru-cache@11.5.2)(ofetch@2.0.0-alpha.3): optionalDependencies: chokidar: 5.0.0 db0: 0.3.4(better-sqlite3@13.0.3)(drizzle-orm@0.45.2(@types/better-sqlite3@9.6.0)(better-sqlite3@13.0.3)) + lru-cache: 11.5.2 ofetch: 2.0.0-alpha.3 update-browserslist-db@1.3.2(browserslist@4.28.8): @@ -8359,7 +9475,7 @@ snapshots: oxlint: 1.82.0(oxlint-tsgolint@7.0.2001)(vite-plus@0.3.2(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) oxlint-tsgolint: 7.0.2001 vite: '@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)' - vitest: 4.1.11(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/browser-preview@4.1.11)(@vitest/coverage-v8@4.1.11)(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) + vitest: 4.1.11(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/browser-preview@4.1.11(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(vitest@4.1.11))(@vitest/coverage-v8@4.1.11)(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) optionalDependencies: '@vitest/browser-playwright': 4.1.11(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(playwright@1.63.0)(vitest@4.1.11) '@voidzero-dev/vite-plus-darwin-arm64': 0.3.2 @@ -8408,12 +9524,12 @@ snapshots: dependencies: react: 19.3.0 react-dom: 19.3.0(react@19.3.0) - vitest: 4.1.11(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/browser-preview@4.1.11)(@vitest/coverage-v8@4.1.11)(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) + vitest: 4.1.11(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/browser-preview@4.1.11(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(vitest@4.1.11))(@vitest/coverage-v8@4.1.11)(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - vitest@4.1.11(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/browser-preview@4.1.11)(@vitest/coverage-v8@4.1.11)(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)): + vitest@4.1.11(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/browser-preview@4.1.11(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3))(vitest@4.1.11))(@vitest/coverage-v8@4.1.11)(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)): dependencies: '@vitest/expect': 4.1.11 '@vitest/mocker': 4.1.11(@voidzero-dev/vite-plus-core@0.3.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(typescript@6.0.3)) @@ -8462,6 +9578,10 @@ snapshots: ws@8.21.3: {} + wsl-utils@0.1.0: + dependencies: + is-wsl: 3.1.1 + wsl-utils@1.0.0: dependencies: is-wsl: 3.1.1 diff --git a/scripts/checks/integrity/lint-config.test.ts b/scripts/checks/integrity/lint-config.test.ts index e0b18a7b..466a9344 100644 --- a/scripts/checks/integrity/lint-config.test.ts +++ b/scripts/checks/integrity/lint-config.test.ts @@ -4,6 +4,7 @@ import { resolve } from "node:path"; import { beforeAll, describe, expect, it } from "vite-plus/test"; import viteConfig from "../../../vite.config"; +import { companionGlobs } from "../../lib/companion-files"; import { REPO_ROOT } from "../../lib/repo-root"; /** @@ -36,8 +37,8 @@ const EXPECTED_PLUGINS = [ */ const EXPECTED_OVERRIDES = [ { - // 緩和の範囲とルール。範囲を広げると本体コードでも no-unsafe-* が無効になり、ルールを増やすと - // テストコードの型検査がその分だけ緩む (ADR-0004「テストファイルの緩和」) + // 緩和の適用先とルール。適用先を広げると本体コードでも no-unsafe-* が無効になり、ルールを + // 増やすとテストコードの型検査がその分だけ緩む (ADR-0004「テストファイルの緩和」) files: ["**/*.test.ts", "**/*.test.tsx", "src/test/**"], excludeFiles: undefined, rules: { @@ -50,30 +51,28 @@ const EXPECTED_OVERRIDES = [ }, { // 層の境界に載せる規則と、その適用外にする層 (ADR-0020 / ADR-0021)。design system の著作側 - // (ui/ action/ parts/) だけを外し、消費側には規則を効かせる。excludeFiles を広げると、広げた - // 先の層で className の上書きと動的な className が無診断で通る - files: ["src/**"], + // (ui/ action/ parts/) だけを外し、消費側には規則を効かせる。広げると、広げた先の層で + // className の上書きと動的な className が無診断で通る。.storybook/ も消費側として扱う + // (decorator が design system component を包む置き場になる) + files: ["src/**", ".storybook/**"], excludeFiles: ["src/components/ui/**", "src/components/action/**", "src/components/parts/**"], rules: { "shadcn/no-restyle": "deny", "shadcn/require-static-classes": "deny" }, }, { - // テスト専用のコードの import 禁止を当てる範囲。緩和ではなく範囲を絞った有効化なので、 - // テスト側は off ではなく excludeFiles で外す (ADR-0004「基準から外れる名指し」)。 - // excludeFiles を狭めるとテストや helper が自分の helper を import できなくなる + // テスト専用のコードの import 禁止。緩和ではなく適用先を絞った有効化なので、テスト側は + // off ではなく excludeFiles で外す (ADR-0004「基準から外れる名指し」)。付随ファイルぶんは + // 下で差し引くので、ここに残るのは src/test/** だけになる files: ["src/**", "scripts/**"], - excludeFiles: [ - "**/*.test.ts", - "**/*.test.tsx", - "**/*.test-helpers.ts", - "**/*.test-helpers.tsx", - "src/test/**", - ], + excludeFiles: ["src/test/**"], rules: { "no-restricted-imports": "deny" }, }, ]; +/** 期待値から差し引く付随ファイルの glob。唯一の定義は scripts/lib/companion-files.ts */ +const COMPANION_GLOBS = new Set(companionGlobs("**/")); + /** lint が見に行くべきソースの所在 */ -const SOURCE_ROOTS = ["src", "scripts"]; +const SOURCE_ROOTS = ["src", "scripts", ".storybook"]; /** 追跡されているのに lint されなくてよい唯一のソース。生成物 (ADR 対象外) */ const ALLOWED_INVISIBLE = ["src/routeTree.gen.ts"]; @@ -162,14 +161,20 @@ describe("書いた設定が解決後も残っている", () => { expect(printedConfig.options).toEqual({ typeAware: true, typeCheck: true }); }); - it("override の範囲とルールを全件固定している", () => { + it("override の適用先とルールを全件固定している", () => { // 落ちたら: printedConfig.overrides を見て、増減した override を EXPECTED_OVERRIDES へ // 反映するか、意図しない変更なら vite.config.ts を直す。severity まで見るのは、ルールを - // 残したまま "off" へ差し替える壊し方をキー集合だけでは拾えないため (2026-09-19 に実測) + // 残したまま "off" へ差し替える壊し方をキー集合だけでは拾えないため (2026-09-19 に実測)。 + // + // excludeFiles からは companionGlobs の分を差し引いてから比べる。付随ファイルの種別は + // scripts/lib/companion-files.ts が唯一の定義で、そこに単体テストがある。ここへ写すと + // 種別を足すたびに同じ変更を 2 度書くだけの手順が増える。差し引いた残り (手書きの層と + // src/test/**) は写す。差し引きはどの override にも効くので、付随ファイルの除外が別の + // override へ付く壊し方は下の検査が受け持つ expect( printedConfig.overrides.map(({ files, excludeFiles, rules }) => ({ files, - excludeFiles, + excludeFiles: excludeFiles?.filter((glob) => !COMPANION_GLOBS.has(glob)), rules: Object.fromEntries( Object.entries(rules).map(([rule, value]) => [ rule, @@ -177,10 +182,29 @@ describe("書いた設定が解決後も残っている", () => { ]), ), })), - "override の範囲かルールか severity が変わった。範囲を広げるとその層で規則が無診断になり、" + + "override の適用先かルールか severity が変わった。適用先を広げるとその層で規則が無診断になり、" + "ルールを消すか off にすると規則が無言で外れる (ADR-0004 / ADR-0020 / ADR-0021)", ).toEqual(EXPECTED_OVERRIDES); }); + + it("付随ファイルの除外は import 禁止の override が全種類ぶん持つ", () => { + // 上の検査は excludeFiles から付随ファイルの分を無条件に差し引く。差し引きは「付いている + // か」を見ないので、別の override へ付ける壊し方も、1 本消す壊し方も差分が出ない。 + // どちらもここで受け持つ + const holders = printedConfig.overrides.filter(({ excludeFiles }) => + excludeFiles?.some((glob) => COMPANION_GLOBS.has(glob)), + ); + expect( + holders.map(({ files }) => files), + "付随ファイルの除外が想定外の override に付いた。その override の規則が" + + "テストと story で無診断になる (ADR-0004 / ADR-0020 / ADR-0021)", + ).toEqual([["src/**", "scripts/**"]]); + expect( + holders[0]?.excludeFiles?.filter((glob) => COMPANION_GLOBS.has(glob)), + "付随ファイルの除外が欠けた。その種別のファイルが自分の helper を import できなくなる " + + "(ADR-0004「基準から外れる名指し」)", + ).toEqual(companionGlobs("**/")); + }); }); /** diff --git a/scripts/checks/integrity/registry-baseline.test.ts b/scripts/checks/integrity/registry-baseline.test.ts index 9e432dba..4153968e 100644 --- a/scripts/checks/integrity/registry-baseline.test.ts +++ b/scripts/checks/integrity/registry-baseline.test.ts @@ -3,6 +3,7 @@ import { join } from "node:path"; import { describe, expect, it } from "vite-plus/test"; +import { isCompanionFile } from "../../lib/companion-files"; import { REPO_ROOT } from "../../lib/repo-root"; /** @@ -26,8 +27,13 @@ const EXTERNAL_REGISTRY_FILES: Record = { /** ui 直下に置かれる、コンポーネントでないディレクトリ */ const UI_NON_COMPONENT_DIRS = new Set(["__screenshots__"]); +/** + * registry 由来でない付随ファイル (`scripts/lib/companion-files.ts`) は baseline を持たない。 + * `shadcn add` の出力に含まれないので、baseline と突き合わせる対象から外す + */ function isComponentFile(name: string): boolean { - return /\.tsx?$/.test(name) && !/\.test\.tsx?$/.test(name) && !name.endsWith(".d.ts"); + if (!/\.tsx?$/.test(name) || name.endsWith(".d.ts")) return false; + return !isCompanionFile(name); } function uiComponentFiles(): string[] { diff --git a/scripts/dev-env/derive-dev-port.sh b/scripts/dev-env/derive-dev-port.sh index 51cca463..64cfdd3b 100755 --- a/scripts/dev-env/derive-dev-port.sh +++ b/scripts/dev-env/derive-dev-port.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash # dev server (`vp dev --port`) の port を worktree ごとに導出する。 # -# main checkout → (従来どおり 3000 固定) -# linked worktree → 3001-3999 の決定的な値(worktree 名のハッシュから算出) +# main checkout → +# linked worktree → +1 から +999 の決定的な値(worktree 名のハッシュから算出) # # 複数 worktree で dev server を同時起動すると port 3000 が衝突するため、 # git-dir と git-common-dir の一致判定で main / linked worktree を判定する。 @@ -12,8 +12,8 @@ # git 情報が取れない場合は base にフォールバックする — port 分離が # 効かないだけでアプリは従来どおり動く(fail-safe)。ただし観測可能にするため # stderr に警告を残す。 -# 注意: base は worktree 導出範囲 (3001-3999) の外に置くこと。範囲内の base を -# 渡すと main と worktree の port が衝突し得る(現行 base は 3000 で範囲外)。 +# 注意: 複数の base を使うときは 1000 以上離すこと。近いと範囲が重なる +# (現行は dev server 3000 と Storybook 6006)。 set -u base="${1:-3000}" @@ -45,6 +45,8 @@ if [ -z "${name}" ]; then fi hash=$(printf '%s' "${name}" | cksum | cut -d' ' -f1) -port=$((hash % 999 + 3001)) +# base 相対にする。base ごとに範囲が分かれるので、同じ worktree で dev server と +# Storybook を同時に起動しても衝突しない +port=$((hash % 999 + base + 1)) echo "${port}" diff --git a/scripts/dev-env/derive-dev-port.test.ts b/scripts/dev-env/derive-dev-port.test.ts index fe327e5f..23db4011 100644 --- a/scripts/dev-env/derive-dev-port.test.ts +++ b/scripts/dev-env/derive-dev-port.test.ts @@ -63,6 +63,21 @@ describe("derive-dev-port.sh", () => { expect(port).toBeLessThanOrEqual(MAX_PORT); }); + // base ごとに範囲が分かれないと、同じ worktree で dev server と Storybook が衝突する + it("linked worktree の port は base 相対で、base が違えば別の値になる", () => { + const repo = initTempRepo("ddp-repo-base-"); + const worktree = addWorktree(repo, "feat-base"); + + const forDev = Number(runScript(worktree, [BASE]).stdout); + const forStorybook = Number(runScript(worktree, ["6006"]).stdout); + + expect(forDev).toBeGreaterThanOrEqual(MIN_PORT); + expect(forDev).toBeLessThanOrEqual(MAX_PORT); + expect(forStorybook).toBeGreaterThanOrEqual(6007); + expect(forStorybook).toBeLessThanOrEqual(7005); + expect(forStorybook).not.toBe(forDev); + }); + it("同名 worktree は同じ port を返す (決定的)", () => { const repoA = initTempRepo("ddp-repo-a-"); const worktreeA = addWorktree(repoA, "feat-same"); diff --git a/scripts/lib/companion-files.test.ts b/scripts/lib/companion-files.test.ts new file mode 100644 index 00000000..b294fc2f --- /dev/null +++ b/scripts/lib/companion-files.test.ts @@ -0,0 +1,80 @@ +import { describe, expect, it } from "vite-plus/test"; + +import { + COMPANION_KINDS, + companionFilePattern, + companionGlobs, + isCompanionFile, +} from "./companion-files"; + +describe("COMPANION_KINDS", () => { + it("directory-structure.md が定める 4 種別を持つ", () => { + expect([...COMPANION_KINDS]).toEqual(["test", "test-helpers", "story-helpers", "stories"]); + }); +}); + +describe("companionGlobs", () => { + it("種別ごとに ts と tsx の 2 本を、宣言順で並べる", () => { + expect(companionGlobs("**/")).toEqual([ + "**/*.test.ts", + "**/*.test.tsx", + "**/*.test-helpers.ts", + "**/*.test-helpers.tsx", + "**/*.story-helpers.ts", + "**/*.story-helpers.tsx", + "**/*.stories.ts", + "**/*.stories.tsx", + ]); + }); + + it("prefix を差し替えられる (coverage は src 配下だけを見る)", () => { + expect(companionGlobs("src/**/")).toContain("src/**/*.story-helpers.ts"); + expect(companionGlobs("src/**/")).toHaveLength(COMPANION_KINDS.length * 2); + }); +}); + +describe("companionFilePattern", () => { + it("4 種別 × ts / tsx に当たり、アプリのコードには当たらない", () => { + const pattern = new RegExp(companionFilePattern()); + + for (const kind of COMPANION_KINDS) { + expect(pattern.test(`button.${kind}.ts`), `button.${kind}.ts`).toBe(true); + expect(pattern.test(`button.${kind}.tsx`), `button.${kind}.tsx`).toBe(true); + } + expect(pattern.test("button.tsx")).toBe(false); + expect(pattern.test("handlers.server.ts")).toBe(false); + expect(pattern.test("routeTree.gen.ts")).toBe(false); + }); +}); + +describe("isCompanionFile", () => { + it("4 種別 × ts / tsx を付随ファイルと判定する", () => { + for (const kind of COMPANION_KINDS) { + expect(isCompanionFile(`button.${kind}.ts`), `button.${kind}.ts`).toBe(true); + expect(isCompanionFile(`button.${kind}.tsx`), `button.${kind}.tsx`).toBe(true); + } + }); + + it("アプリのコードは判定しない", () => { + // basename に `.` を持つアプリのコード。字面が似ているので取り違えると本体が検査から漏れる + expect(isCompanionFile("button.tsx")).toBe(false); + expect(isCompanionFile("handlers.server.ts")).toBe(false); + expect(isCompanionFile("routeTree.gen.ts")).toBe(false); + expect(isCompanionFile("env.d.ts")).toBe(false); + }); + + it("拡張子の前に種別名が無いファイルは対象外", () => { + expect(isCompanionFile("test.ts")).toBe(false); + expect(isCompanionFile("stories.tsx")).toBe(false); + }); + + it("種別名の部分一致では判定しない", () => { + expect(isCompanionFile("button.tests.ts")).toBe(false); + expect(isCompanionFile("button.story.ts")).toBe(false); + }); + + it("ts / tsx 以外の拡張子は対象外", () => { + expect(isCompanionFile("button.test.js")).toBe(false); + expect(isCompanionFile("button.stories.mdx")).toBe(false); + }); +}); diff --git a/scripts/lib/companion-files.ts b/scripts/lib/companion-files.ts new file mode 100644 index 00000000..86914d2c --- /dev/null +++ b/scripts/lib/companion-files.ts @@ -0,0 +1,43 @@ +/** + * アプリのコードと同じディレクトリに置くが、アプリのコードではないファイルの種別。 + * `<名前>.<種別>.ts` / `.tsx` の形で置く (directory-structure.md「テストとスクリプトの配置」)。 + * + * この一覧が唯一の定義で、lint の適用外 (`vite.config.ts`)、coverage の除外 + * (`vitest.config.ts`)、registry baseline の突き合わせ (`registry-baseline.test.ts`) が + * ここから導出される。種別を足すときにどれか 1 つを書き忘れる事故が起きないようにするのが + * 目的なので、導出先で改めて字面を並べ直さない。 + * + * `lint-config.test.ts` は lint の解決結果と突き合わせる期待値を手書きで持つ。そちらを + * ここから導出すると入力どうしの比較になり検査が常に通るので、付随ファイルの分を差し引く + * 用途にだけこの定義を使う。 + */ +export const COMPANION_KINDS = ["test", "test-helpers", "story-helpers", "stories"] as const; + +const EXTENSIONS = ["ts", "tsx"] as const; + +/** + * 付随ファイルに当たる glob。`prefix` は呼ぶ側の基準ディレクトリで決まり、`/` で終える + * (lint は `**\/`、coverage は `src/**\/`)。終えないと `src*.test.ts` のような glob になる。 + */ +export function companionGlobs(prefix: string): string[] { + return COMPANION_KINDS.flatMap((kind) => + EXTENSIONS.map((extension) => `${prefix}*.${kind}.${extension}`), + ); +} + +/** + * ファイル名が付随ファイルかを判定する。判定する集合は `companionGlobs` と同じ。 + */ +export function isCompanionFile(fileName: string): boolean { + return COMPANION_KINDS.some((kind) => + EXTENSIONS.some((extension) => fileName.endsWith(`.${kind}.${extension}`)), + ); +} + +/** + * 付随ファイルに当たるファイル名の正規表現。TanStack Router の `routeFileIgnorePattern` の + * ように、glob ではなくパターン文字列を要求する消費者へ渡す。 + */ +export function companionFilePattern(): string { + return `\\.(${COMPANION_KINDS.join("|")})\\.tsx?$`; +} diff --git a/src/components/css-rules.story-helpers.test.tsx b/src/components/css-rules.story-helpers.test.tsx new file mode 100644 index 00000000..8474f08e --- /dev/null +++ b/src/components/css-rules.story-helpers.test.tsx @@ -0,0 +1,171 @@ +import { afterEach, describe, expect, it, vi } from "vite-plus/test"; + +import { collectRootCustomProperties } from "./css-rules.story-helpers"; + +function sheet(css: string): CSSStyleSheet { + const created = new CSSStyleSheet(); + created.replaceSync(css); + return created; +} + +const root = document.documentElement; + +// spy はファイル内の全 describe で張る。describe の中に置くと、その describe の +// テストにしか効かず、兄弟の describe が張った spy が後続へ残る +afterEach(() => vi.restoreAllMocks()); + +describe("collectRootCustomProperties", () => { + it(":root の宣言を prefix で絞って名前順に返す", () => { + const css = ":root { --b: 2; --a: 1; --other: 3; }"; + + expect(collectRootCustomProperties([sheet(css)], "--a", root)).toEqual(["--a"]); + expect(collectRootCustomProperties([sheet(css)], "--", root)).toEqual([ + "--a", + "--b", + "--other", + ]); + }); + + // Tailwind は @theme の中身を @layer theme へ出す。辿らないと 1 件も取れない + it("@layer の中の宣言も拾う", () => { + const css = "@layer theme { :root, :host { --inside: 1; } }"; + + expect(collectRootCustomProperties([sheet(css)], "--", root)).toEqual(["--inside"]); + }); + + // @import が参照する stylesheet は CSSGroupingRule ではないので、別に辿らないと落ちる + it("@import が指す stylesheet の宣言も拾う", () => { + const imported = sheet(":root { --imported: 1; }"); + const importing = { + href: null, + cssRules: [ + // CSSImportRule の href / styleSheet は getter のみなので defineProperty で作る + Object.create(CSSImportRule.prototype, { + href: { value: "imported.css" }, + styleSheet: { value: imported }, + }), + ], + }; + + expect(collectRootCustomProperties([importing], "--", root)).toEqual(["--imported"]); + }); + + // 深い位置の失敗で、その stylesheet の残りの rule が落ちてはいけない + // cross-origin では cssRules が SecurityError を投げる (CSSOM)。styleSheet の getter が + // 投げるという記述は仕様にも MDN にも無いので、投げるのは cssRules の側で模擬する + it("読めない @import があっても同じ stylesheet の他の宣言は残る", () => { + const unreadable = Object.create(CSSImportRule.prototype, { + href: { value: "cross-origin.css" }, + styleSheet: { + value: { + get cssRules(): CSSRuleList { + throw new DOMException("cross-origin", "SecurityError"); + }, + }, + }, + }); + const importing = { + href: null, + cssRules: [unreadable, ...sheet(":root { --kept: 1; }").cssRules], + }; + const warn = vi.spyOn(console, "warn").mockImplementation(() => {}); + + expect(collectRootCustomProperties([importing], "--", root)).toEqual(["--kept"]); + expect(warn).toHaveBeenCalledOnce(); + }); + + it("styleSheet が null の @import は warn して飛ばす", () => { + const pending = Object.create(CSSImportRule.prototype, { + href: { value: "not-loaded.css" }, + styleSheet: { value: null }, + supportsText: { value: null }, + }); + const importing = { + href: null, + cssRules: [pending, ...sheet(":root { --kept: 1; }").cssRules], + }; + const warn = vi.spyOn(console, "warn").mockImplementation(() => {}); + + expect(collectRootCustomProperties([importing], "--", root)).toEqual(["--kept"]); + expect(warn).toHaveBeenCalledOnce(); + }); + + it("入れ子のグループ規則も辿る", () => { + const css = "@layer a { @media screen { :root { --deep: 1; } } }"; + + expect(collectRootCustomProperties([sheet(css)], "--", root)).toEqual(["--deep"]); + }); + + // selectorText を "," で分けて ":root" と比べる形だと取りこぼす + it(", を内側に持つ selector も取りこぼさない", () => { + const css = ":is(:root, .theme-x) { --inside-is: 1; }"; + + expect(collectRootCustomProperties([sheet(css)], "--", root)).toEqual(["--inside-is"]); + }); + + it("root に当たらない rule は拾わない", () => { + const css = ".card { --not-root: 1; }"; + + expect(collectRootCustomProperties([sheet(css)], "--", root)).toEqual([]); + }); + + it("複数の stylesheet をまたいで重複を潰す", () => { + const sheets = [sheet(":root { --dup: 1; }"), sheet(":root { --dup: 2; --extra: 3; }")]; + + expect(collectRootCustomProperties(sheets, "--", root)).toEqual(["--dup", "--extra"]); + }); + + it("cssRules を読めない stylesheet は warn して飛ばす", () => { + const warn = vi.spyOn(console, "warn").mockImplementation(() => {}); + const blocked = { + href: "https://example.test/x.css", + get cssRules(): CSSRuleList { + throw new Error("SecurityError"); + }, + }; + + expect(collectRootCustomProperties([blocked, sheet(":root { --ok: 1; }")], "--", root)).toEqual( + ["--ok"], + ); + expect(warn).toHaveBeenCalledWith("[css-rules] cssRules を読めない stylesheet", { + href: "https://example.test/x.css", + error: expect.any(Error), + }); + }); +}); + +describe("root 固有でない rule", () => { + // Tailwind の preflight は root にも当たり、内部用の変数を持ち込む + it("任意の要素にも当たる rule は拾わない", () => { + const css = "*, ::before, ::after { --tw-internal: 1; } :root { --token: 2; }"; + + expect(collectRootCustomProperties([sheet(css)], "--", root)).toEqual(["--token"]); + }); + + it("html セレクタは root 固有として拾う", () => { + expect(collectRootCustomProperties([sheet("html { --on-html: 1; }")], "--", root)).toEqual([ + "--on-html", + ]); + }); +}); + +describe("解釈できない selector", () => { + // CSSOM に入りうる selector をすべて matches() が解釈できるとは限らない。 + // 走査ごと止めずに、その rule だけ飛ばして残りを返す + it("matches() が投げる rule は warn して飛ばす", () => { + const warn = vi.spyOn(console, "warn").mockImplementation(() => {}); + const throwingRoot = { + matches: () => { + throw new Error("SyntaxError"); + }, + ownerDocument: document, + }; + + expect(collectRootCustomProperties([sheet(":root { --x: 1; }")], "--", throwingRoot)).toEqual( + [], + ); + expect(warn).toHaveBeenCalledWith("[css-rules] 解釈できない selector", { + selectorText: ":root", + }); + }); +}); diff --git a/src/components/css-rules.story-helpers.ts b/src/components/css-rules.story-helpers.ts new file mode 100644 index 00000000..f48944c4 --- /dev/null +++ b/src/components/css-rules.story-helpers.ts @@ -0,0 +1,107 @@ +/** この関数が root に求めるもの。`Element` はこれを満たす */ +export interface RootElement { + matches(selectors: string): boolean; + readonly ownerDocument: Document; +} + +/** この関数が stylesheet に求めるもの。`CSSStyleSheet` はこれを満たす */ +export interface ReadableStyleSheet { + readonly href: string | null; + readonly cssRules: Iterable; +} + +/** + * `` に当たる rule が宣言しているカスタムプロパティを名前順で集める。 + * + * Tailwind v4 は `@theme` の内容を `@layer theme { :root, :host { ... } }` へ出すため、 + * トップレベルの `CSSStyleRule` だけでなく `@layer` / `@media` 等のグループ規則 + * (`CSSGroupingRule` を継承する rule 全般) の中も再帰的に辿る。辿らないと `@layer` の中の + * 宣言を見落とす (ADR-0022)。 + * + * 対象の判定は `Element.matches()` に委ねる。`selectorText` を `,` で分けて `:root` と + * 比べる形にすると、`:is(:root, .x)` のように `,` を内側に持つ selector を取りこぼす。 + * + * ただし root に当たるだけでは足りない。Tailwind の `*, ::before, ::after, ::backdrop` は + * root にも当たり、内部用の変数を持ち込む。任意の要素にも当たる rule は root 固有では + * ないので除く。混入の有無は Storybook で `Tokens/Colors` を開き、`--tw` で始まる名前が + * 並んでいないかで見る。 + */ +export function collectRootCustomProperties( + sheets: Iterable, + prefix: string, + root: RootElement, +): string[] { + const names = new Set(); + // 「任意の要素にも当たるか」を測る相手。document へ挿さないので他の rule の影響を受けない + const anyElement = root.ownerDocument.createElement("div"); + + /** + * 別オリジンの stylesheet は `cssRules` を読めない。読めなかったことを残して先へ進む。 + * catch の範囲を走査全体にすると、深い位置の失敗でその stylesheet の残りが丸ごと落ちる。 + */ + const readRules = ( + read: () => Iterable, + href: string | null, + ): Iterable | null => { + try { + return read(); + } catch (error) { + console.warn("[css-rules] cssRules を読めない stylesheet", { href, error }); + return null; + } + }; + + const visit = (rules: Iterable) => { + for (const rule of rules) { + if (rule instanceof CSSStyleRule && isRootScoped(root, anyElement, rule.selectorText)) { + for (const name of rule.style) { + if (name.startsWith(prefix)) names.add(name); + } + } + // @import が参照する stylesheet は CSSGroupingRule を継承しないので、別に辿る。 + // 辿らないと、その中の宣言が「トークンが無い」のと区別が付かない形で落ちる + if (rule instanceof CSSImportRule) { + // cross-origin の stylesheet では cssRules が SecurityError を投げる + // (CSSOM「If the origin-clean flag is unset, throw a SecurityError exception.」)。 + // 読み取りごと readRules に入れて、走査全体が止まらないようにする + const inner = readRules(() => { + const imported = rule.styleSheet; + if (imported === null) { + // 読み込み前か失敗。chromium 153 では取得に失敗した @import が null になる + // (2026-09-20 実測)。黙って飛ばすと「その stylesheet にトークンが無い」のと + // 区別が付かなくなる。supports() の条件が偽のときに null を返す仕様も議論 + // されているが (w3c/csswg-drafts#8608)、chromium 153 は条件が偽でも取得して + // 非 null を返す。条件で warn を止める分岐は置かず、supportsText を payload へ + // 載せて読み手が切り分けられるようにする + console.warn("[css-rules] styleSheet を読めない @import", { + href: rule.href, + supportsText: rule.supportsText, + }); + return []; + } + return imported.cssRules; + }, rule.href); + if (inner !== null) visit(inner); + continue; + } + if (rule instanceof CSSGroupingRule) visit(rule.cssRules); + } + }; + + for (const sheet of sheets) { + const rules = readRules(() => sheet.cssRules, sheet.href); + if (rules !== null) visit(rules); + } + + return [...names].sort(); +} + +/** `matches()` は不正な selector で例外を投げる。走査を止めずにその rule だけ飛ばす */ +function isRootScoped(root: RootElement, anyElement: Element, selectorText: string): boolean { + try { + return root.matches(selectorText) && !anyElement.matches(selectorText); + } catch { + console.warn("[css-rules] 解釈できない selector", { selectorText }); + return false; + } +} diff --git a/src/components/theme-snapshot.story-helpers.test.tsx b/src/components/theme-snapshot.story-helpers.test.tsx new file mode 100644 index 00000000..51dbdcd1 --- /dev/null +++ b/src/components/theme-snapshot.story-helpers.test.tsx @@ -0,0 +1,169 @@ +import { describe, expect, it, vi } from "vite-plus/test"; + +import { createThemeSnapshotStore } from "./theme-snapshot.story-helpers"; + +function element(className: string): Element { + const created = document.createElement("div"); + created.className = className; + return created; +} + +describe("createThemeSnapshotStore", () => { + it("同じテーマの間は同じ参照を返す", () => { + const node = element("light"); + const store = createThemeSnapshotStore( + () => ({ read: true }), + { read: false }, + () => node, + ); + + expect(store.getSnapshot()).toBe(store.getSnapshot()); + }); + + it("テーマが変わると読み直す", () => { + const node = element("light"); + const read = vi.fn(() => node.className); + const store = createThemeSnapshotStore(read, "", () => node); + + expect(store.getSnapshot()).toBe("light"); + node.className = "dark"; + expect(store.getSnapshot()).toBe("dark"); + expect(read).toHaveBeenCalledTimes(2); + }); + + it("テーマが戻ったら読み直す (キャッシュは 1 つだけ持つ)", () => { + const node = element("light"); + const read = vi.fn(() => node.className); + const store = createThemeSnapshotStore(read, "", () => node); + + store.getSnapshot(); + node.className = "dark"; + store.getSnapshot(); + node.className = "light"; + + expect(store.getSnapshot()).toBe("light"); + expect(read).toHaveBeenCalledTimes(3); + }); + + it("class の変化を購読し、解除できる", async () => { + const node = element(""); + const store = createThemeSnapshotStore( + () => 1, + 0, + () => node, + ); + const onChange = vi.fn(); + + const unsubscribe = store.subscribe(onChange); + node.className = "dark"; + // MutationObserver の通知は microtask で届く + await Promise.resolve(); + expect(onChange).toHaveBeenCalled(); + + unsubscribe(); + node.className = "light"; + await Promise.resolve(); + expect(onChange).toHaveBeenCalledTimes(1); + }); + + it("CSS の HMR を受けたら、テーマが同じでも読み直す", () => { + const node = element("light"); + const read = vi.fn(() => ({ read: true })); + const listeners = new Set<() => void>(); + const hot = { + on: (_event: "vite:afterUpdate", callback: () => void) => void listeners.add(callback), + off: (_event: "vite:afterUpdate", callback: () => void) => void listeners.delete(callback), + }; + const store = createThemeSnapshotStore(read, { read: false }, () => node, hot); + + store.subscribe(vi.fn()); + const before = store.getSnapshot(); + // stylesheet だけが差し替わる。class は動かない + for (const callback of listeners) callback(); + + expect(store.getSnapshot()).not.toBe(before); + expect(read).toHaveBeenCalledTimes(2); + }); + + it("購読者が複数でも、1 回の変更につき読み直しは 1 回", () => { + const node = element("light"); + const read = vi.fn(() => ({ read: true })); + const listeners = new Set<() => void>(); + const hot = { + on: (_event: "vite:afterUpdate", callback: () => void) => void listeners.add(callback), + off: (_event: "vite:afterUpdate", callback: () => void) => void listeners.delete(callback), + }; + const store = createThemeSnapshotStore(read, { read: false }, () => node, hot); + + // React は購読者ごとに別の onChange を渡し、そのあと getSnapshot を呼ぶ。 + // 同じ関数を 2 回渡すと Set が重複を落とし、購読者が 1 件しか作られない + store.subscribe(() => void store.getSnapshot()); + store.subscribe(() => void store.getSnapshot()); + store.getSnapshot(); + expect(read).toHaveBeenCalledTimes(1); + + for (const callback of listeners) callback(); + + expect(read).toHaveBeenCalledTimes(2); + }); + + it("購読を解除すると HMR の listener も外れる", () => { + const node = element("light"); + const listeners = new Set<() => void>(); + const hot = { + on: (_event: "vite:afterUpdate", callback: () => void) => void listeners.add(callback), + off: (_event: "vite:afterUpdate", callback: () => void) => void listeners.delete(callback), + }; + const store = createThemeSnapshotStore( + () => 1, + 0, + () => node, + hot, + ); + + const unsubscribe = store.subscribe(vi.fn()); + expect(listeners.size).toBe(1); + unsubscribe(); + expect(listeners.size).toBe(0); + }); + + it("同じ関数を 2 回購読しても観測は 1 つだけ", () => { + const node = element("light"); + // Set だと二重登録が見えないので配列で受ける + const registered: Array<() => void> = []; + const hot = { + on: (_event: "vite:afterUpdate", callback: () => void) => void registered.push(callback), + off: (_event: "vite:afterUpdate", callback: () => void) => { + const at = registered.indexOf(callback); + if (at >= 0) registered.splice(at, 1); + }, + }; + const store = createThemeSnapshotStore( + () => 1, + 0, + () => node, + hot, + ); + const onChange = vi.fn(); + + const first = store.subscribe(onChange); + const second = store.subscribe(onChange); + + expect(registered).toHaveLength(1); + + first(); + second(); + + expect(registered).toHaveLength(0); + }); + + it("描画前は fallback を返す", () => { + const store = createThemeSnapshotStore( + () => ["read"], + [], + () => element(""), + ); + + expect(store.getServerSnapshot()).toEqual([]); + }); +}); diff --git a/src/components/theme-snapshot.story-helpers.ts b/src/components/theme-snapshot.story-helpers.ts new file mode 100644 index 00000000..b479082b --- /dev/null +++ b/src/components/theme-snapshot.story-helpers.ts @@ -0,0 +1,90 @@ +/** + * `` の class (light/dark) をキーに、テーマごとの読み取り結果を配る外部ストア。 + * + * CSSOM や `getComputedStyle` から読んだ値は React の依存に現れない。再 render だけでは + * React Compiler がメモ化した結果を返して値が止まるため、購読の仕組みが要る (ADR-0022)。 + * + * story を `key` で remount する形は採らない。`withThemeByClassName` は `STORY_RENDERED` + * 後の `useEffect` で class を当てるので、class の変化は常に play function より後に来る。 + * そこで remount すると play が作った状態を捨てることになる (2026-09-20 実測)。 + */ +/** + * `import.meta.hot` のうち、この store が使う部分だけ。Vitest は `server.hmr: false` で + * 実イベントを配らないため、差し替えられないと HMR の経路をテストで固定できない + */ +export interface UpdateNotifier { + on: (event: "vite:afterUpdate", callback: () => void) => void; + off: (event: "vite:afterUpdate", callback: () => void) => void; +} + +export interface ThemeSnapshotStore { + subscribe: (onChange: () => void) => () => void; + getSnapshot: () => T; + getServerSnapshot: () => T; +} + +/** + * `read` の結果をテーマごとに 1 回だけ計算して配る。`getSnapshot` は同じテーマの間は + * 同じ参照を返す必要があるため (返さないと React が無限に再 render する)、class をキーに + * 覚えておく。 + * + * キーには世代も混ぜる。class は「読み取り結果が古くなったこと」の代理だが、CSS の HMR は + * stylesheet の中身だけを差し替えて class を動かさないため、代理が破れる。React は + * `Object.is` で snapshot を比べて再 render の要否を決めるので、class だけをキーにすると + * 購読していても同じ参照が返り、再 render に至らない (React 公式「How you determine whether + * mutable data has changed depends on your mutable store」)。 + */ +export function createThemeSnapshotStore( + read: () => T, + fallback: T, + root: () => Element = () => document.documentElement, + hot: UpdateNotifier | undefined = import.meta.hot, +): ThemeSnapshotStore { + let cache: { key: string; value: T } | null = null; + let generation = 0; + const listeners = new Set<() => void>(); + let observer: MutationObserver | null = null; + + // 変更の契機は store に 1 つだけ置き、そこから購読者へ配る。購読者ごとに観測すると、 + // 1 回の変更で購読者の数だけ世代が進み、その数だけ read が走る + const notify = (): void => { + // 世代を進めてから配る。進めないと getSnapshot がキャッシュを返し、React は + // 変化なしと見て再 render を見送る + generation += 1; + for (const listener of listeners) listener(); + }; + + return { + subscribe: (onChange) => { + listeners.add(onChange); + // 観測の有無は observer で判定する。listeners.size で数えると、同じ関数を 2 回 + // 購読したときに Set が重複を落として size が 1 のままになり、観測が二重に張られる + if (observer === null) { + observer = new MutationObserver(notify); + observer.observe(root(), { attributes: true, attributeFilter: ["class"] }); + // CSS の HMR は stylesheet の中身だけを差し替えるので、class の MutationObserver + // では拾えない。値は stylesheet から読んでいるため、読み直さないとカタログが古い + // まま残る。vite:afterUpdate は CSS の load 完了後に発火するので、この時点の CSSOM + // は新しい (vitejs/vite#9810)。本番では import.meta.hot が無く、購読ごと落ちる + hot?.on("vite:afterUpdate", notify); + } + return () => { + listeners.delete(onChange); + if (listeners.size === 0 && observer !== null) { + observer.disconnect(); + observer = null; + hot?.off("vite:afterUpdate", notify); + } + }; + }, + getSnapshot: () => { + const key = `${generation} ${root().className}`; + if (cache === null || cache.key !== key) { + cache = { key, value: read() }; + } + return cache.value; + }, + // 描画前は読む対象が無い。空を返して mount 後の最初の snapshot に委ねる + getServerSnapshot: () => fallback, + }; +} diff --git a/src/components/theme-tokens.story-helpers.test.ts b/src/components/theme-tokens.story-helpers.test.ts new file mode 100644 index 00000000..3712472a --- /dev/null +++ b/src/components/theme-tokens.story-helpers.test.ts @@ -0,0 +1,59 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test"; + +import { dropRedundantColorAliases } from "./theme-tokens.story-helpers"; + +describe("dropRedundantColorAliases", () => { + let warnSpy: ReturnType; + + beforeEach(() => { + warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("生トークンが同じ値で在る --color-X を落とす", () => { + const tokens = [ + { name: "--color-primary", value: "oklch(0.5 0.2 260)" }, + { name: "--primary", value: "oklch(0.5 0.2 260)" }, + ]; + + expect(dropRedundantColorAliases(tokens)).toEqual([ + { name: "--primary", value: "oklch(0.5 0.2 260)" }, + ]); + }); + + it("対応する生トークンが無い --color-X は残す", () => { + const tokens = [{ name: "--color-black", value: "#000" }]; + + expect(dropRedundantColorAliases(tokens)).toEqual(tokens); + }); + + it("--color- で始まらないトークンは残す", () => { + const tokens = [{ name: "--primary", value: "oklch(0.5 0.2 260)" }]; + + expect(dropRedundantColorAliases(tokens)).toEqual(tokens); + }); + + it("値が食い違う別名は落とさず warn する", () => { + const tokens = [ + { name: "--color-primary", value: "oklch(0.9 0 0)" }, + { name: "--primary", value: "oklch(0.5 0.2 260)" }, + ]; + + expect(dropRedundantColorAliases(tokens)).toEqual(tokens); + expect(warnSpy).toHaveBeenCalledWith("[theme-tokens] 別名と生トークンの値が食い違う", { + alias: "--color-primary", + aliasValue: "oklch(0.9 0 0)", + raw: "--primary", + rawValue: "oklch(0.5 0.2 260)", + }); + }); + + it("--color- だけの名前は生トークン名が空になるので残す", () => { + const tokens = [{ name: "--color-", value: "#000" }]; + + expect(dropRedundantColorAliases(tokens)).toEqual(tokens); + }); +}); diff --git a/src/components/theme-tokens.story-helpers.ts b/src/components/theme-tokens.story-helpers.ts new file mode 100644 index 00000000..7e7257a2 --- /dev/null +++ b/src/components/theme-tokens.story-helpers.ts @@ -0,0 +1,38 @@ +/** 解決後の値を伴う CSS カスタムプロパティ。名前は `--` で始まる */ +export interface ThemeToken { + name: string; + value: string; +} + +const COLOR_ALIAS_PREFIX = "--color-"; + +/** + * Tailwind の `@theme inline` が生成する `--color-X` のうち、生トークン `--X` と値まで一致する + * ものを落とす。`--color-primary: var(--primary)` のような別名は一覧に並べても同じ値と + * コントラスト比が二重に出るだけで、読む側は 2 行のどちらを見ればよいか判断できない。 + * + * 値が食い違う別名は落とさず warn する。落としてよい根拠は「`var()` 経由の別名だから値が + * 同じ」であって名前の綴りではないため、前提が崩れた別名を黙って消すと、一覧が本来見せる + * ための drift をその一覧自身が隠すことになる。 + * + * `--color-black` のように対応する生トークンを持たない宣言は残る (styles.css が `#000` を + * 直接宣言している)。 + */ +export function dropRedundantColorAliases(tokens: readonly ThemeToken[]): ThemeToken[] { + const byName = new Map(tokens.map((token) => [token.name, token.value])); + + return tokens.filter(({ name, value }) => { + if (!name.startsWith(COLOR_ALIAS_PREFIX)) return true; + const raw = `--${name.slice(COLOR_ALIAS_PREFIX.length)}`; + const rawValue = byName.get(raw); + if (rawValue === undefined) return true; + if (rawValue === value) return false; + console.warn("[theme-tokens] 別名と生トークンの値が食い違う", { + alias: name, + aliasValue: value, + raw, + rawValue, + }); + return true; + }); +} diff --git a/src/components/tokens.stories.tsx b/src/components/tokens.stories.tsx new file mode 100644 index 00000000..a2099eb1 --- /dev/null +++ b/src/components/tokens.stories.tsx @@ -0,0 +1,133 @@ +import type { Meta, StoryObj } from "@storybook/tanstack-react"; +import { useSyncExternalStore } from "react"; +import { expect, within } from "storybook/test"; + +import { collectRootCustomProperties } from "./css-rules.story-helpers"; +import { createThemeSnapshotStore } from "./theme-snapshot.story-helpers"; +import { dropRedundantColorAliases, type ThemeToken } from "./theme-tokens.story-helpers"; + +/** + * 解決後の値を伴う全トークンを名前順で集める。`styles.css` が SSOT なので値は写さない + * (ADR-0022)。`getComputedStyle` は呼ぶたびに root のスタイル解決を起こすので 1 つを使い回す + */ +function readAllTokens(): ThemeToken[] { + const style = getComputedStyle(document.documentElement); + const names = collectRootCustomProperties(document.styleSheets, "--", document.documentElement); + return names.map((name) => ({ name, value: style.getPropertyValue(name).trim() })); +} + +interface TokenLists { + colors: ThemeToken[]; + radius: ThemeToken[]; + fonts: ThemeToken[]; +} + +function byPrefix(tokens: ThemeToken[], prefix: string): ThemeToken[] { + return tokens.filter(({ name }) => name.startsWith(prefix)); +} + +/** + * テーマごとに 1 回だけ読む。値は CSSOM と getComputedStyle から取るので React の依存に + * 現れず、購読しないと切り替えても止まる (ADR-0022)。 + * + * 種別ごとの絞り込みもここで済ませる。render 側で絞ると、CSSOM の走査こそ 1 回でも、 + * `dropRedundantColorAliases` の warn が再 render のたびに出る。走査と警告はテーマが + * 変わったときだけ起こしたい。 + * + * 色かどうかはブラウザ自身のパーサに聞く。`CSS.supports` は property と value の組で + * 解析できるかを返すので、`oklch()` も `color-mix()` も relative color syntax も通る + */ +const tokenStore = createThemeSnapshotStore( + () => { + const all = readAllTokens(); + if (all.length === 0) { + console.warn("[tokens] root のカスタムプロパティが 1 件も取れない"); + } + return { + colors: dropRedundantColorAliases(all.filter(({ value }) => CSS.supports("color", value))), + radius: byPrefix(all, "--radius"), + fonts: byPrefix(all, "--font"), + }; + }, + { colors: [], radius: [], fonts: [] }, +); + +function useTokenLists(): TokenLists { + return useSyncExternalStore( + tokenStore.subscribe, + tokenStore.getSnapshot, + tokenStore.getServerSnapshot, + ); +} + +function ColorSwatches() { + const { colors: tokens } = useTokenLists(); + + return ( +
    + {tokens.map(({ name, value }) => ( +
  • + + {name} + {value} +
  • + ))} +
+ ); +} + +function RadiusTokens() { + const { radius: tokens } = useTokenLists(); + return ( +
+ {tokens.map(({ name, value }) => ( +
+
+ {name} + {value} +
+ ))} +
+ ); +} + +function TypographyTokens() { + const { fonts: tokens } = useTokenLists(); + return ( +
+ {tokens.map(({ name, value }) => ( +

+ {name}: {value} +

+ ))} +
+ ); +} + +const meta = {} satisfies Meta; + +export default meta; + +/** + * 一覧が空でないことを見る。中身は `styles.css` と CSSOM の走査で決まるので、story を + * 描くだけでは 0 件でも通ってしまう。`warnIfEmpty` の warn はテストを落とさない + * (2026-09-20 に走査を潰す mutant で実測)。トークン名は `--` で始まる文字列として出る。 + */ +async function expectTokensRendered({ + canvasElement, +}: { + canvasElement: HTMLElement; +}): Promise { + await expect(within(canvasElement).getAllByText(/^--/).length).toBeGreaterThan(0); +} + +export const Colors: StoryObj = { render: () => , play: expectTokensRendered }; +export const Radius: StoryObj = { render: () => , play: expectTokensRendered }; +export const Typography: StoryObj = { + render: () => , + play: expectTokensRendered, +}; diff --git a/src/styles.css b/src/styles.css index be87056e..38de3ea8 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1,4 +1,6 @@ -@import "tailwindcss"; +/* scan の対象をアプリのソースへ絞る。既定はリポジトリ全体で、Markdown の字面まで本番 CSS へ + 出る。除外を並べる形を採らない理由と効果の測り方は ADR-0022 */ +@import "tailwindcss" source("../src"); @import "tw-animate-css"; @import "shadcn/tailwind.css"; @import "@fontsource-variable/geist"; @@ -99,6 +101,9 @@ --sidebar-ring: oklch(0.623 0.214 259.815); } +/* static はここに付けない。付けると本番の CSS が使っていない変数の分まで持つ。 + カタログに要る分は .storybook/preview.css がこのファイルを theme(static) 付きで + 読み直して掛ける (ADR-0022) */ @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); diff --git a/src/test/viewport-sizes.ts b/src/test/viewport-sizes.ts new file mode 100644 index 00000000..b2d87fe2 --- /dev/null +++ b/src/test/viewport-sizes.ts @@ -0,0 +1,23 @@ +/** + * viewport の寸法。寸法はすべてここに置く。`vitest.browser.config.ts` と + * `vitest.storybook.config.ts` が `DEFAULT_VIEWPORT` を `browser.viewport` として読むため、 + * このファイルは `vite-plus/test/browser` を import しない。 + * `src/test/viewport.ts` は browser mode でしか動かず、config から読むと + * 「vitest/browser can be imported only inside the Browser Mode」で落ちる (2026-09-20 実測)。 + */ +export interface Viewport { + width: number; + height: number; +} + +/** ブラウザテストの既定 viewport */ +export const DEFAULT_VIEWPORT: Viewport = { width: 1280, height: 720 }; + +/** タブレット想定のレイアウト検証に使う viewport (既定より縦に長い経路を通す) */ +export const TABLET_VIEWPORT: Viewport = { width: 1280, height: 853 }; + +/** 極端に低い viewport (ブラウザ UI 領域が大きい環境の下限想定) */ +export const SHORT_VIEWPORT: Viewport = { width: 1280, height: 420 }; + +/** 対応する最小画面幅。ここでレイアウトが崩壊しないことを下限として固定する */ +export const NARROW_VIEWPORT: Viewport = { width: 375, height: 667 }; diff --git a/src/test/viewport.ts b/src/test/viewport.ts index 0b30ed72..e3c9e2ac 100644 --- a/src/test/viewport.ts +++ b/src/test/viewport.ts @@ -1,28 +1,22 @@ import { expect } from "vite-plus/test"; import { page } from "vite-plus/test/browser"; +import { DEFAULT_VIEWPORT, type Viewport } from "./viewport-sizes"; + /** - * ブラウザテストのレイアウト検証で使う viewport 定数・操作・アサーション。 + * ブラウザテストのレイアウト検証で使う viewport の操作とアサーション。寸法は + * `viewport-sizes.ts` が持ち、テストが 1 つの import で済むようここから再 export する。 * 変更したテストは afterEach で必ず `restoreDefaultViewport` を通す * (戻さないと後続ファイルのブレークポイント依存テストが巻き添えになる)。 */ -export interface Viewport { - width: number; - height: number; -} - -/** ブラウザテストの既定 viewport (SSOT: vitest.browser.config.ts の browser.viewport と一致させる) */ -export const DEFAULT_VIEWPORT: Viewport = { width: 1280, height: 720 }; - -/** タブレット想定のレイアウト検証に使う viewport (既定より縦に長い経路を通す) */ -export const TABLET_VIEWPORT: Viewport = { width: 1280, height: 853 }; - -/** 極端に低い viewport (ブラウザ UI 領域が大きい環境の下限想定) */ -export const SHORT_VIEWPORT: Viewport = { width: 1280, height: 420 }; - -/** 対応する最小画面幅。ここでレイアウトが崩壊しないことを下限として固定する */ -export const NARROW_VIEWPORT: Viewport = { width: 375, height: 667 }; +export type { Viewport } from "./viewport-sizes"; +export { + DEFAULT_VIEWPORT, + NARROW_VIEWPORT, + SHORT_VIEWPORT, + TABLET_VIEWPORT, +} from "./viewport-sizes"; /** * viewport を切り替える。定数と `page.viewport()` の引数展開を 1 箇所に閉じ、 diff --git a/tsconfig.json b/tsconfig.json index d24efeae..2edd68c6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "include": ["**/*.ts", "**/*.tsx"], + "include": ["**/*.ts", "**/*.tsx", ".storybook/**/*.ts", ".storybook/**/*.tsx"], "exclude": [ "node_modules", "dist", diff --git a/vite.config.ts b/vite.config.ts index 9a11f849..3d1c3be0 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,6 +5,8 @@ import viteReact from "@vitejs/plugin-react"; import { nitro } from "nitro/vite"; import { defineConfig, lazyPlugins } from "vite-plus"; +import { companionFilePattern, companionGlobs } from "./scripts/lib/companion-files"; + const OXLINT_DEFAULT_PLUGINS = ["typescript", "unicorn", "oxc"] as const; /** @@ -296,7 +298,7 @@ export default defineConfig({ // から見えず、routes からの上書きが素通りする。require-static-classes は他の shadcn // ルールの門番で、ここで落ちる className は no-raw-colors / no-unknown-classes も中身を // 読めない (ADR-0021) - files: ["src/**"], + files: ["src/**", ".storybook/**"], excludeFiles: DESIGN_SYSTEM_LAYERS.map((layer) => `src/components/${layer}/**`), rules: { "shadcn/no-restyle": ["error", { allow: ["layout"] }], @@ -304,17 +306,15 @@ export default defineConfig({ }, }, { - // テスト専用のコード (*.test-helpers.ts と src/test/) をアプリのコードから import させない - // (ADR-0004「基準から外れる名指し」)。緩和ではなく範囲を絞った有効化なので、テスト側は - // off にせず excludeFiles で対象から外す (files の否定 glob は oxlint 1.79 では効かない) + // テストと story だけが使うコード (*.test-helpers.ts / *.story-helpers.ts / + // *.stories.tsx / src/test/) をアプリのコードから import させない + // (ADR-0004「基準から外れる名指し」)。story 自身も止める。story を import すると + // それが引く helper と fixture が bundle に入る。緩和ではなく + // 範囲を絞った有効化なので、テスト側は off にせず excludeFiles で対象から外す + // (files の否定 glob は oxlint 1.79 では効かない)。story 自身も出荷される bundle に + // 入らない (アプリのどこからも import されず、.storybook/main.ts の glob だけが拾う) files: ["src/**", "scripts/**"], - excludeFiles: [ - "**/*.test.ts", - "**/*.test.tsx", - "**/*.test-helpers.ts", - "**/*.test-helpers.tsx", - "src/test/**", - ], + excludeFiles: [...companionGlobs("**/"), "src/test/**"], rules: { "no-restricted-imports": [ "error", @@ -322,9 +322,9 @@ export default defineConfig({ patterns: [ { // alias (@/test/) と相対 (./test/ ../test/) の両方の specifier を止める - regex: "\\.test-helpers$|(^@|\\.)/test/", + regex: "\\.(test|story)-helpers$|\\.stories$|(^@|\\.)/test/", message: - "テスト専用のコード。アプリのコードから import しない (directory-structure.md「テストとスクリプトの配置」)", + "テストと story だけが使うコード。アプリのコードから import しない (directory-structure.md「テストとスクリプトの配置」)", }, ], }, @@ -364,7 +364,7 @@ export default defineConfig({ tanstackStart({ // テストとテスト専用ヘルパーを route ファイル扱いから外す。外さないと generator が // 「Route を export していない」と毎ビルド警告する (directory-structure.md「テストとスクリプトの配置」) - router: { routeFileIgnorePattern: "\\.test(-helpers)?\\.tsx?$" }, + router: { routeFileIgnorePattern: companionFilePattern() }, importProtection: { client: { // better-sqlite3 は native binding (node-gyp) を持ち、client bundle に含めると diff --git a/vitest.browser.config.ts b/vitest.browser.config.ts index 63b56df9..d97a0d47 100644 --- a/vitest.browser.config.ts +++ b/vitest.browser.config.ts @@ -3,6 +3,8 @@ import viteReact from "@vitejs/plugin-react"; import { playwright } from "vite-plus/test/browser-playwright"; import { defineProject } from "vite-plus/test/config"; +import { DEFAULT_VIEWPORT } from "./src/test/viewport-sizes"; + export default defineProject({ // vite.config.ts と同じく .env を読まない。vitest.config.ts は vite.config.ts を // 継承せず上書きする (Vitest 公式「all options in your vite.config will be ignored」)。 @@ -58,10 +60,8 @@ export default defineProject({ enabled: true, provider: playwright(), headless: true, - // 既定 viewport の SSOT (src/test/viewport.ts の DEFAULT_VIEWPORT と対にする)。 - // Playwright の browser context 既定と同値だが、暗黙依存にすると - // DEFAULT_VIEWPORT との対応が読めなくなるため明示する - viewport: { width: 1280, height: 720 }, + // 既定 viewport は src/test/viewport.ts が持つ。写すとどちらかが古くなるので import する + viewport: DEFAULT_VIEWPORT, instances: [{ browser: "chromium" }], }, }, diff --git a/vitest.config.ts b/vitest.config.ts index b78d86bb..8090c576 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,6 +1,9 @@ import viteReact from "@vitejs/plugin-react"; import { defineConfig } from "vite-plus"; +import { companionGlobs } from "./scripts/lib/companion-files"; +import { storybookProject } from "./vitest.storybook.config"; + const sharedExclude = [ "**/node_modules/**", "**/dist/**", @@ -49,6 +52,9 @@ export default defineConfig({ }, }, "vitest.browser.config.ts", + // テーマごとに 1 project。a11y 検査を light と dark の両方へ当てる + storybookProject("light"), + storybookProject("dark"), ], coverage: { provider: "v8", @@ -56,10 +62,8 @@ export default defineConfig({ include: ["src/**"], exclude: [ "src/routeTree.gen.ts", - "src/**/*.test.ts", - "src/**/*.test.tsx", - "src/**/*.test-helpers.ts", - "src/**/*.test-helpers.tsx", + // 付随ファイルは出荷されないので分母に入れない (directory-structure.md) + ...companionGlobs("src/**/"), "src/test/**", "src/**/*.d.ts", ], diff --git a/vitest.storybook.config.ts b/vitest.storybook.config.ts new file mode 100644 index 00000000..6e742693 --- /dev/null +++ b/vitest.storybook.config.ts @@ -0,0 +1,75 @@ +import { storybookTest } from "@storybook/addon-vitest/vitest-plugin"; +import tailwindcss from "@tailwindcss/vite"; +import viteReact from "@vitejs/plugin-react"; +import { playwright } from "vite-plus/test/browser-playwright"; +import { defineProject } from "vite-plus/test/config"; + +import { DEFAULT_VIEWPORT } from "./src/test/viewport-sizes"; + +/** + * テーマごとに 1 つの project を作る。`initialGlobals` で toolbar の global を固定すると、 + * 同じ story が両方のテーマで走る (addon-vitest の公式パターン)。light だけで回すと、 + * `parameters.a11y.test: "error"` が dark の contrast を検査していないのに検査しているように + * 見える。`theme` は `@storybook/addon-themes` の global 名である。 + */ +export function storybookProject(theme: "light" | "dark") { + return defineProject({ + // vite.config.ts と同じく .env を読まない (ADR-0002) + envDir: false, + plugins: [ + viteReact(), + tailwindcss(), + storybookTest({ configDir: ".storybook", initialGlobals: { theme } }), + ], + resolve: { + tsconfigPaths: true, + // registry combobox の @base-ui/react barrel import が React を二重解決するのを防ぐ + dedupe: ["react", "react-dom"], + }, + optimizeDeps: { + // 事前バンドルから漏れた依存を実行中に見つけると Vite が再最適化を挟み、 + // "Vite unexpectedly reloaded a test" と React 二重解決 + // ("Cannot read properties of null") が出る。 + // + // この一覧は「story が import する依存」ではない。storybook-light と storybook-dark は + // 同じ configDir を渡すので、@storybook/addon-vitest が configDir のハッシュから + // cacheDir を導く結果 (vitest-plugin の oneWayHash(configDir))、2 つの project が + // 1 つの deps キャッシュを共有する。事前宣言が足りないと、両者が実行中に別々の依存を + // 見つけて互いのキャッシュを無効化し合う。到達しない名前でも減らしてはいけない。 + // + // story 53 件の段で実測した (2026-09-20)。 + // この一覧なし + 2 project : 98 件失敗 / reloaded 8 + // この一覧あり + 2 project : 444 件 pass / reloaded 0 + // どちらでも 1 project なら 222 件 pass / reloaded 0 + // + // axe-core だけは理由が別で、addon-a11y の preview が import("axe-core") で読む + // (dist/_browser-chunks/chunk-P5J2FJ2Z.js)。動的 import なので静的な走査に出ない + include: [ + "@tanstack/react-query", + "@tanstack/react-form", + "axe-core", + "class-variance-authority", + "cn", + ], + // @tanstack/react-start 系は exclude しない: @storybook/tanstack-react の framework + // preset (viteFinal) が moduleInterceptionPlugin で @tanstack/react-start / + // react-start/server / react-start-server / start-server-core への import を + // resolveId でモック (export-mocks/start.js) へ差し替え、同じ 4 モジュールを自らの + // optimizeDeps.exclude にも登録している。storybookTest() はこの viteFinal を経由して + // 読み込むため (addon-vitest の vitest-plugin が presets.apply("viteFinal", ...) を + // 呼ぶ)、この project では実パッケージへ到達せず二重に書く必要がない + }, + test: { + name: `storybook-${theme}`, + browser: { + enabled: true, + provider: playwright(), + headless: true, + // browser project と同じ寸法で走らせる。書かないと Playwright の既定に落ち、 + // 2 つの project が別の baseline を持つ + viewport: DEFAULT_VIEWPORT, + instances: [{ browser: "chromium" }], + }, + }, + }); +}