From ffd275df0792b70e695e42bb6dd996ec02818294 Mon Sep 17 00:00:00 2001 From: Horuse <39675195+Horuse@users.noreply.github.com> Date: Sun, 27 Sep 2026 21:15:41 +0300 Subject: [PATCH] feat(storybook): add Storybook with node and form stories, drop /preview route --- .gitignore | 3 + .storybook/main.ts | 38 +++ .storybook/preview.ts | 39 +++ AGENTS.md | 2 + CONTRIBUTING.md | 2 +- bun.lock | 270 +++++++++++++++++- docs/DEVELOPMENT.md | 1 + docs/FEATURES.md | 3 +- docs/RULES.md | 3 +- docs/UI.md | 48 ++++ package.json | 8 +- src/routes/preview/+page.svelte | 40 --- src/routes/preview/_fake_signal.ts | 104 ------- .../components/copy_button.stories.svelte | 18 ++ .../components/meter_bar.stories.svelte | 42 +++ .../components/signal_bars.stories.svelte | 23 ++ src/stories/forms/combobox.stories.svelte | 69 +++++ .../forms/number_stepper.stories.svelte | 32 +++ .../forms/password_input.stories.svelte | 22 ++ .../forms/segmented_buttons.stories.svelte | 47 +++ src/stories/forms/slider.stories.svelte | 59 ++++ src/stories/forms/toggle.stories.svelte | 31 ++ src/stories/harness/args.ts | 101 +++++++ src/stories/harness/backend.ts | 258 +++++++++++++++++ src/stories/harness/controlled.svelte | 27 ++ src/stories/harness/env_scope.svelte | 11 + .../harness/gallery.svelte} | 38 ++- src/stories/harness/node_canvas.svelte | 81 ++++++ src/stories/harness/signal.ts | 171 +++++++++++ src/stories/harness/stub_source.svelte | 19 ++ src/stories/nodes/app_audio.stories.svelte | 27 ++ src/stories/nodes/audio_file.stories.svelte | 36 +++ .../nodes/channel_balance.stories.svelte | 25 ++ src/stories/nodes/compressor.stories.svelte | 35 +++ src/stories/nodes/de_esser.stories.svelte | 29 ++ src/stories/nodes/declick.stories.svelte | 24 ++ src/stories/nodes/delay.stories.svelte | 29 ++ src/stories/nodes/eq.stories.svelte | 22 ++ .../nodes/file_recording.stories.svelte | 55 ++++ src/stories/nodes/gain.stories.svelte | 26 ++ src/stories/nodes/gallery.stories.svelte | 16 ++ src/stories/nodes/level_meter.stories.svelte | 22 ++ src/stories/nodes/limiter.stories.svelte | 30 ++ src/stories/nodes/lufs_meter.stories.svelte | 32 +++ src/stories/nodes/microphone.stories.svelte | 27 ++ src/stories/nodes/mute.stories.svelte | 36 +++ src/stories/nodes/net_receiver.stories.svelte | 23 ++ src/stories/nodes/net_sender.stories.svelte | 35 +++ src/stories/nodes/noise_gate.stories.svelte | 34 +++ .../nodes/noise_suppressor.stories.svelte | 33 +++ src/stories/nodes/plugin.stories.svelte | 49 ++++ src/stories/nodes/reverb.stories.svelte | 31 ++ src/stories/nodes/saturator.stories.svelte | 26 ++ src/stories/nodes/speaker.stories.svelte | 29 ++ src/stories/nodes/spectrum.stories.svelte | 22 ++ src/stories/nodes/system_audio.stories.svelte | 27 ++ src/stories/nodes/waveform.stories.svelte | 24 ++ src/stories/nodes/webrtc.stories.svelte | 34 +++ 58 files changed, 2277 insertions(+), 171 deletions(-) create mode 100644 .storybook/main.ts create mode 100644 .storybook/preview.ts create mode 100644 docs/UI.md delete mode 100644 src/routes/preview/+page.svelte delete mode 100644 src/routes/preview/_fake_signal.ts create mode 100644 src/stories/components/copy_button.stories.svelte create mode 100644 src/stories/components/meter_bar.stories.svelte create mode 100644 src/stories/components/signal_bars.stories.svelte create mode 100644 src/stories/forms/combobox.stories.svelte create mode 100644 src/stories/forms/number_stepper.stories.svelte create mode 100644 src/stories/forms/password_input.stories.svelte create mode 100644 src/stories/forms/segmented_buttons.stories.svelte create mode 100644 src/stories/forms/slider.stories.svelte create mode 100644 src/stories/forms/toggle.stories.svelte create mode 100644 src/stories/harness/args.ts create mode 100644 src/stories/harness/backend.ts create mode 100644 src/stories/harness/controlled.svelte create mode 100644 src/stories/harness/env_scope.svelte rename src/{routes/preview/_grid.svelte => stories/harness/gallery.svelte} (57%) create mode 100644 src/stories/harness/node_canvas.svelte create mode 100644 src/stories/harness/signal.ts create mode 100644 src/stories/harness/stub_source.svelte create mode 100644 src/stories/nodes/app_audio.stories.svelte create mode 100644 src/stories/nodes/audio_file.stories.svelte create mode 100644 src/stories/nodes/channel_balance.stories.svelte create mode 100644 src/stories/nodes/compressor.stories.svelte create mode 100644 src/stories/nodes/de_esser.stories.svelte create mode 100644 src/stories/nodes/declick.stories.svelte create mode 100644 src/stories/nodes/delay.stories.svelte create mode 100644 src/stories/nodes/eq.stories.svelte create mode 100644 src/stories/nodes/file_recording.stories.svelte create mode 100644 src/stories/nodes/gain.stories.svelte create mode 100644 src/stories/nodes/gallery.stories.svelte create mode 100644 src/stories/nodes/level_meter.stories.svelte create mode 100644 src/stories/nodes/limiter.stories.svelte create mode 100644 src/stories/nodes/lufs_meter.stories.svelte create mode 100644 src/stories/nodes/microphone.stories.svelte create mode 100644 src/stories/nodes/mute.stories.svelte create mode 100644 src/stories/nodes/net_receiver.stories.svelte create mode 100644 src/stories/nodes/net_sender.stories.svelte create mode 100644 src/stories/nodes/noise_gate.stories.svelte create mode 100644 src/stories/nodes/noise_suppressor.stories.svelte create mode 100644 src/stories/nodes/plugin.stories.svelte create mode 100644 src/stories/nodes/reverb.stories.svelte create mode 100644 src/stories/nodes/saturator.stories.svelte create mode 100644 src/stories/nodes/speaker.stories.svelte create mode 100644 src/stories/nodes/spectrum.stories.svelte create mode 100644 src/stories/nodes/system_audio.stories.svelte create mode 100644 src/stories/nodes/waveform.stories.svelte create mode 100644 src/stories/nodes/webrtc.stories.svelte diff --git a/.gitignore b/.gitignore index ee2b6947..c81ee9cd 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ vite.config.ts.timestamp-* /flatpak/.flatpak-builder/ /flatpak/splitwave.deb /flatpak/*.flatpak + +# storybook +/storybook-static diff --git a/.storybook/main.ts b/.storybook/main.ts new file mode 100644 index 00000000..61b1e68a --- /dev/null +++ b/.storybook/main.ts @@ -0,0 +1,38 @@ +import type { StorybookConfig } from '@storybook/sveltekit'; + +const CSF_ADDON = '@storybook/addon-svelte-csf'; +const KIT_MOCKS = '@storybook/sveltekit/internal/mocks'; +// Deps that ship uncompiled Svelte (components, runes). The Svelte plugin on +// this Vite cannot hook esbuild pre-bundling, so they are served unbundled. +const RAW_SVELTE_DEPS = [CSF_ADDON, KIT_MOCKS]; +const isRawSvelte = (d: string) => RAW_SVELTE_DEPS.some((p) => d.startsWith(p)); + +const config: StorybookConfig & { optimizeViteDeps?: (deps: string[]) => string[] } = { + stories: ['../src/**/*.stories.svelte'], + addons: [CSF_ADDON], + framework: '@storybook/sveltekit', + optimizeViteDeps: (deps) => deps.filter((d) => !isRawSvelte(d)), + viteFinal(config) { + config.plugins = [ + ...(config.plugins ?? []), + { + name: 'splitwave:unbundle-svelte-csf', + enforce: 'post', + config(c) { + const deps = c.optimizeDeps ?? {}; + deps.include = (deps.include ?? []).filter((d) => !isRawSvelte(d)); + deps.exclude = [ + ...(deps.exclude ?? []), + CSF_ADDON, + `${CSF_ADDON}/internal/create-runtime-stories`, + ...['forms', 'navigation', 'stores', 'state.svelte.js'].map((m) => `${KIT_MOCKS}/app/${m}`) + ]; + c.optimizeDeps = deps; + } + } + ]; + return config; + } +}; + +export default config; diff --git a/.storybook/preview.ts b/.storybook/preview.ts new file mode 100644 index 00000000..e938346f --- /dev/null +++ b/.storybook/preview.ts @@ -0,0 +1,39 @@ +import type { Preview } from '@storybook/sveltekit'; +import '../src/app.css'; +import '@xyflow/svelte/dist/base.css'; +import { DEFAULT_ENV, installMockBackend } from '../src/stories/harness/backend'; + +// Stories that never mount a node canvas still touch Tauri through shared stores. +installMockBackend(DEFAULT_ENV); +// The app shell never scrolls; story pages taller than the viewport must. +document.body.style.overflow = 'auto'; + +const preview: Preview = { + globalTypes: { + theme: { + description: 'Colour theme', + toolbar: { + title: 'Theme', + icon: 'mirror', + items: [ + { value: 'light', title: 'Light' }, + { value: 'dark', title: 'Dark' } + ], + dynamicTitle: true + } + } + }, + initialGlobals: { theme: 'dark' }, + decorators: [ + (story, context) => { + document.documentElement.classList.toggle('dark', context.globals.theme === 'dark'); + return story(); + } + ], + parameters: { + layout: 'fullscreen', + controls: { expanded: true, sort: 'none' } + } +}; + +export default preview; diff --git a/AGENTS.md b/AGENTS.md index 1b356d7a..6c3be8ff 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,6 +5,7 @@ Docs live in `docs/`, not here: - [docs/CONCEPT.md](docs/CONCEPT.md) — engine architecture: RT audio path, pacing, effects, layout, frontend conventions, Rust quirks, platforms. - [docs/RULES.md](docs/RULES.md) — universal rules for every change. - [docs/FEATURES.md](docs/FEATURES.md) — how a new feature must look (categories, inline params, scope, per-platform behaviour). +- [docs/UI.md](docs/UI.md) — building UI: reuse Storybook components, stories required for every node and shared component. - [CONTRIBUTING.md](CONTRIBUTING.md) — diff hygiene, per-OS testing, PR checklist. - [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) — build and run. @@ -34,4 +35,5 @@ Without these, one chunk takes ~16 ms and the worker stalls. - Read the current code, not earlier explanations. - RT path change → `cargo check`. - Svelte change → `bun run check`. +- New node or shared UI component → reuse existing Storybook components first, add its stories under `src/stories/` in the same commit (see [docs/UI.md](docs/UI.md)). - Rust `#[derive(TS)]` change → `bun run generate`, commit the generated files with the Rust change. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9b43f074..5dd0354c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,7 +62,7 @@ cargo check --manifest-path src-tauri/Cargo.toml bun run format # Prettier + rustfmt ``` -Both checks must pass, and `format` must leave the tree clean. If you changed a `#[derive(TS)]` type in Rust, run `bun run generate` and commit the regenerated files in `src/lib/modules/pipeline/generated/` together with the Rust change — never by hand, and never on their own. +Both checks must pass, and `format` must leave the tree clean. A new node or shared component ships with its Storybook stories in the same PR; see [docs/UI.md](docs/UI.md). If you changed a `#[derive(TS)]` type in Rust, run `bun run generate` and commit the regenerated files in `src/lib/modules/pipeline/generated/` together with the Rust change — never by hand, and never on their own. Anything persisted to disk — pipeline JSON, `virtual-devices.json`, the macOS driver plist — has to keep loading for people upgrading. Add a `#[serde(default)]` or a versioned migration; do not silently change the meaning of an existing field. diff --git a/bun.lock b/bun.lock index 71e2e757..5d37b6b5 100644 --- a/bun.lock +++ b/bun.lock @@ -27,6 +27,9 @@ "tailwindcss": "^4.3.0", }, "devDependencies": { + "@storybook/addon-svelte-csf": "^5.1.3", + "@storybook/svelte": "^10.6.0", + "@storybook/sveltekit": "^10.6.0", "@sveltejs/adapter-static": "^3.0.6", "@sveltejs/kit": "^2.9.0", "@sveltejs/vite-plugin-svelte": "^7.1.2", @@ -34,6 +37,7 @@ "@types/semver": "^7.8.0", "bun-types": "^1.4.2", "prettier": "3.8.3", + "storybook": "^10.6.0", "svelte": "^5.0.0", "svelte-check": "^4.0.0", "typescript": "~5.6.2", @@ -42,6 +46,20 @@ }, }, "packages": { + "@adobe/css-tools": ["@adobe/css-tools@4.5.0", "", {}, "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q=="], + + "@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="], + + "@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], @@ -112,8 +130,90 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g=="], + "@noble/hashes": ["@noble/hashes@2.2.0", "", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="], + "@oxc-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.127.0", "", { "os": "android", "cpu": "arm" }, "sha512-0LC7ye4hvqbIKxAzThzvswgHLFu2AURKzYLeSVvLdu2TBOYWQDmHnTqPLeA597BcUCxiLqLsS4CJ5uoI5WYWCQ=="], + + "@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.127.0", "", { "os": "android", "cpu": "arm64" }, "sha512-b5jtVTH6AU5CJXHNdj7Jj9IEiR9yVjjnwHzPJhGyHGPdcsZSzBCkS9GBbV33niRMvKthDwQRFRJfI4a+k4PvYg=="], + + "@oxc-parser/binding-darwin-arm64": ["@oxc-parser/binding-darwin-arm64@0.127.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-obCE8B7ISKkJidjlhv9xRGJPOSDG2Yu6PRga9Ruaz35uintHxbp1Ki/Yc71wx4rj3Edrm0a1kzG1TAwit0wFpg=="], + + "@oxc-parser/binding-darwin-x64": ["@oxc-parser/binding-darwin-x64@0.127.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-JL6Xb5IwPQT8rUzlpsX7E+AgfcdNklXNPFp8pjCQQ5MQOQo5rtEB2ui+3Hgg9Sn7Y9Egj6YOLLiHhLpdAe12Aw=="], + + "@oxc-parser/binding-freebsd-x64": ["@oxc-parser/binding-freebsd-x64@0.127.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-SDQ/3MQFw58fqQz3Z1PhSKFF3JoCF4gmlNjziDm8X02tTahCw0qJbd7FGPDKw1i4VTBZene9JPyC3mHtSvi+wA=="], + + "@oxc-parser/binding-linux-arm-gnueabihf": ["@oxc-parser/binding-linux-arm-gnueabihf@0.127.0", "", { "os": "linux", "cpu": "arm" }, "sha512-Av+D1MIqzV0YMGPT9we2SIZaMKD7Cxs4CvXSx/yxaWHewZjYEjScpOf5igc8IILASViw4WTnjlwUdI1KzVtDHQ=="], + + "@oxc-parser/binding-linux-arm-musleabihf": ["@oxc-parser/binding-linux-arm-musleabihf@0.127.0", "", { "os": "linux", "cpu": "arm" }, "sha512-Cs2fdJ8cPpFdeebj6p4dag8A4+56hPvZ0AhQQzlaLswGz1tz7bXt1nETLeorrM9+AMcWFFkqxcXwDGfTVidY8g=="], + + "@oxc-parser/binding-linux-arm64-gnu": ["@oxc-parser/binding-linux-arm64-gnu@0.127.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-qdOfTcT6SY8gsJrrV92uyEUyjqMGPpIB5JZUG6QN5dukYd+7/j0kX6MwK1DgQj39jtUYixxPiaRUiEN1+0CXgQ=="], + + "@oxc-parser/binding-linux-arm64-musl": ["@oxc-parser/binding-linux-arm64-musl@0.127.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-EoTCZneNFU/P2qrpEM+RHmQwt+CvDkyGESG6qhr7KaegXLZwePfbrkCDfAk8/rhxbDUVGsZILX+2tqPzFtoFWA=="], + + "@oxc-parser/binding-linux-ppc64-gnu": ["@oxc-parser/binding-linux-ppc64-gnu@0.127.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-zALjmZYgxFLHjXeudcDF0xFGNydTAtkAeXAr2EuC17ywCyFxcmQra4w0BMde0Yi/re4Bi4iwEoEXtYN7l6eBLQ=="], + + "@oxc-parser/binding-linux-riscv64-gnu": ["@oxc-parser/binding-linux-riscv64-gnu@0.127.0", "", { "os": "linux", "cpu": "none" }, "sha512-fPP8M6zQLS7Jz7o9d5ArUSuAuSK3e+WCYVrCpdzeCOejidtZExJ9tjhDrAd3HEPqARBCPmdpqxESPFqy44vkBQ=="], + + "@oxc-parser/binding-linux-riscv64-musl": ["@oxc-parser/binding-linux-riscv64-musl@0.127.0", "", { "os": "linux", "cpu": "none" }, "sha512-7IcC4Ao02oGpfnjt+X/oF4U2mllo2qoSkw5xxiXNKL9MCTsTiAC6616beOuehdxGcnz1bRoPC1RQ2f1GQDdN+g=="], + + "@oxc-parser/binding-linux-s390x-gnu": ["@oxc-parser/binding-linux-s390x-gnu@0.127.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-pbXIhiNFHoqWeqDNLiJ9JkpHz1IM9k4DXa66x+1GTWMG7iLxtkXgE53iiuKSXwmk3zIYmaPVfBvgcAhS583K4Q=="], + + "@oxc-parser/binding-linux-x64-gnu": ["@oxc-parser/binding-linux-x64-gnu@0.127.0", "", { "os": "linux", "cpu": "x64" }, "sha512-MYCguB9RvBvlSd6gbuNI7QwiLoCCAlGnlRJFPrzLI6U1/9wkC/WK6LtBAUln55H1Ctqw45PWmqrobKoMhsYQzQ=="], + + "@oxc-parser/binding-linux-x64-musl": ["@oxc-parser/binding-linux-x64-musl@0.127.0", "", { "os": "linux", "cpu": "x64" }, "sha512-5eY0B/bxf1xIUxb4NOTvOI3KWtBQfPWYyKAzgcrCt0mDibSZygVpO1Pz8bkeiSZ5Jj9+M09dkggG3H8I5d0Uyg=="], + + "@oxc-parser/binding-openharmony-arm64": ["@oxc-parser/binding-openharmony-arm64@0.127.0", "", { "os": "none", "cpu": "arm64" }, "sha512-Gld0ajrFTUXNtdw20fVBuTQx66FA75nIVg+//pPfR3sXkuABB4mTBhl3r9JNzrJpgW//qiwxf0nWXUWGJSL3UQ=="], + + "@oxc-parser/binding-wasm32-wasi": ["@oxc-parser/binding-wasm32-wasi@0.127.0", "", { "dependencies": { "@emnapi/core": "1.9.2", "@emnapi/runtime": "1.9.2", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-T6KVD7rhLzFlwGRXMnxUFfkCZD8FHnb968wVXW1mXzgRFc5RNXOBY2mPPDZ77x5Ln76ltLMgtPg0cOkU1NSrEQ=="], + + "@oxc-parser/binding-win32-arm64-msvc": ["@oxc-parser/binding-win32-arm64-msvc@0.127.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Ujvw4X+LD1CCGULcsQcvb4YNVoBGqt+JHgNNzGGaCImELiZLk477ifUH53gIbE7EKd933NdTi25JWEr9K2HwXw=="], + + "@oxc-parser/binding-win32-ia32-msvc": ["@oxc-parser/binding-win32-ia32-msvc@0.127.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-0cwxKO7KHQQQfo4Uf4B2SQrhgm+cJaP9OvFFhx52Tkg4bezsacu83GB2/In5bC415Ueeym+kXdnge/57rbSfTw=="], + + "@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.127.0", "", { "os": "win32", "cpu": "x64" }, "sha512-rOrnSQSCbhI2kowr9XxE7m9a8oQXnBHjnS6j95LxxAnEZ0+Fz20WlRXG4ondQb+ejjt2KOsa65sE6++L6kUd+w=="], + + "@oxc-project/types": ["@oxc-project/types@0.127.0", "", {}, "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ=="], + + "@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.21.2", "", { "os": "android", "cpu": "arm" }, "sha512-xQoCRv+gKax9KTdwdaQNnAFOai8neay7g3jExDIORzhbrejwGSJaZNTdOJHR5ziLg2joMxOCMOFMo4zuxza2uQ=="], + + "@oxc-resolver/binding-android-arm64": ["@oxc-resolver/binding-android-arm64@11.21.2", "", { "os": "android", "cpu": "arm64" }, "sha512-HF5oiE2L05yInPYCFD/4uxSrEZW4SuIfn99Y6L1xnJnzl066JR+MJs2rIdstw8A2MPlAKH+13dpFPNycjqzvGg=="], + + "@oxc-resolver/binding-darwin-arm64": ["@oxc-resolver/binding-darwin-arm64@11.21.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-UX4u49CVCAD8QZNELaW8eMGgMAGwFWYEPbvNsh+3r/gs4NX3KfpiACMVwRQT0EuH3uat9hM5Zl+Ppm9pJD8tgg=="], + + "@oxc-resolver/binding-darwin-x64": ["@oxc-resolver/binding-darwin-x64@11.21.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-J9xPx7YBkrRmJ+xl561ztnMWEc1aOyjEIxBiGX1dVb3u7bGSnfObfcZk+Pd+uM0HZAPNsQ1xvD8j52A/uOSqNQ=="], + + "@oxc-resolver/binding-freebsd-x64": ["@oxc-resolver/binding-freebsd-x64@11.21.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-fRlt7OvSaQkWj6+EDTVxawVxOlqJB2QSnBfkeCyK4RTvsGctbw3BiH2Tb7DzMs7bikc4BRBpvWP5zF9K8b54Zg=="], + + "@oxc-resolver/binding-linux-arm-gnueabihf": ["@oxc-resolver/binding-linux-arm-gnueabihf@11.21.2", "", { "os": "linux", "cpu": "arm" }, "sha512-gK+vPUcPQITkGwBKpZGrcDHSlU6eDGl7AQacxS2CEKAZIBHWkOVFeJwLZ4tYnA1acJqRM5lt7yYwPCVqGHIJ7A=="], + + "@oxc-resolver/binding-linux-arm-musleabihf": ["@oxc-resolver/binding-linux-arm-musleabihf@11.21.2", "", { "os": "linux", "cpu": "arm" }, "sha512-L/Rgas7SrOKy/z7IH+HxSFRqVO4PuLDKLEGKvnhoCBBq3UJ0YzGBou3qMzaAGgkJwsIvX2pBF+7ojzfUhLiZxQ=="], + + "@oxc-resolver/binding-linux-arm64-gnu": ["@oxc-resolver/binding-linux-arm64-gnu@11.21.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-CUEYvlX1Fk7E9kUMzuswru1J9HLxMwnpDeQGjQuI4ZH+iNCoa2X9T+pvyzrbsgl7WnIeTFTlNlHsRfVdf5g9/g=="], + + "@oxc-resolver/binding-linux-arm64-musl": ["@oxc-resolver/binding-linux-arm64-musl@11.21.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-ViN1ZibQyxwC67GpoP33oo+S9UyUnkog13vzQb9+v9bCNvrVzJuk0MRdacjtv/9xfRMVF/eqHFyl8YOeykI10Q=="], + + "@oxc-resolver/binding-linux-ppc64-gnu": ["@oxc-resolver/binding-linux-ppc64-gnu@11.21.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CU1sCqWnhGqYD5I1HedHk5pujrn7ssDkNB/AEQd/pd3D/EojVSgJUlpbafwIbyhia3PgIfkvdFpRPWSALzVumA=="], + + "@oxc-resolver/binding-linux-riscv64-gnu": ["@oxc-resolver/binding-linux-riscv64-gnu@11.21.2", "", { "os": "linux", "cpu": "none" }, "sha512-jWVyZtIHca4Gb96x7dag+y69vlei7ffjrsveLkmf2ZhqEAz6ZSBnY1GWvgZUaZlwZP62A3xD092BW97Q5VGc+g=="], + + "@oxc-resolver/binding-linux-riscv64-musl": ["@oxc-resolver/binding-linux-riscv64-musl@11.21.2", "", { "os": "linux", "cpu": "none" }, "sha512-LF29obFqNgBUgDX7rmUK7M4D0JQG5LxhYzn3xXmECcHU9aQAdWG7NiY052qybtesEdwHQXKNTWYQ7mTsybNvWg=="], + + "@oxc-resolver/binding-linux-s390x-gnu": ["@oxc-resolver/binding-linux-s390x-gnu@11.21.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-+NYcm+cCHBbtdQQ3A4phQTSuVRYnNHz7wrl9XRAPEovcdoqi0mb1K5ZOl+jN54ZD+q1zz3V0vltbFJmzecJKmw=="], + + "@oxc-resolver/binding-linux-x64-gnu": ["@oxc-resolver/binding-linux-x64-gnu@11.21.2", "", { "os": "linux", "cpu": "x64" }, "sha512-UQqZDdG2r2HhAOsZEgufkIWHPQ886IUyuJQkoZByvzhW8j51R4UNzGBJFkTiTnLhQnggwJRdJgFWK4uY6ZVIMw=="], + + "@oxc-resolver/binding-linux-x64-musl": ["@oxc-resolver/binding-linux-x64-musl@11.21.2", "", { "os": "linux", "cpu": "x64" }, "sha512-3Q9PMRjalWkT6NZ4jfujuqTCFwoWErg3y3BnOgb544B8IMw4PktiwWOigMfOHNRLMghZeJ7hpfpZf4CP7rV7Og=="], + + "@oxc-resolver/binding-openharmony-arm64": ["@oxc-resolver/binding-openharmony-arm64@11.21.2", "", { "os": "none", "cpu": "arm64" }, "sha512-Eljeq3ndtyKhM+Es8LITi4Zl2htzuRZcrPMF3kMCsrILztvU6AjZ3FuEhHHKobPUB9rMpzLpJP5bDqXI7r+iog=="], + + "@oxc-resolver/binding-wasm32-wasi": ["@oxc-resolver/binding-wasm32-wasi@11.21.2", "", { "dependencies": { "@emnapi/core": "1.11.0", "@emnapi/runtime": "1.11.0", "@napi-rs/wasm-runtime": "^1.1.5" }, "cpu": "none" }, "sha512-HGDbNsIywqc4LxU38+CTJNnB/6BF7rheWOJ259b4eE0aEnelYblC8x+1tEd63bp31fYne63RQz9Jb4yVnC7Yig=="], + + "@oxc-resolver/binding-win32-arm64-msvc": ["@oxc-resolver/binding-win32-arm64-msvc@11.21.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-iWx25CBEgH49iE9q5coEGI/jb1jl5kkCY9z6U5Og67xCkQ/WFMDc2J5U78+AE91SUxM2NqSLhJC8/PLfWnImww=="], + + "@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.21.2", "", { "os": "win32", "cpu": "x64" }, "sha512-VPoCAhKvCQTlG7vxqaBXcmuvbh77BfnGXj8g0pbvVXpm1F/R8rDVwqIWcEbMzrI1JvJlm8v7T9uMVQb6UctMRg=="], + "@paralleldrive/cuid2": ["@paralleldrive/cuid2@3.3.0", "", { "dependencies": { "@noble/hashes": "^2.0.1", "bignumber.js": "^9.3.1", "error-causes": "^3.0.2" }, "bin": { "cuid2": "bin/cuid2.js" } }, "sha512-OqiFvSOF0dBSesELYY2CAMa4YINvlLpvKOz/rv6NeZEqiyttlHgv98Juwv4Ch+GrEV7IZ8jfI2VcEoYUjXXCjw=="], "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], @@ -170,6 +270,22 @@ "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@storybook/addon-svelte-csf": ["@storybook/addon-svelte-csf@5.1.3", "", { "dependencies": { "@storybook/csf": "^0.1.13", "dedent": "^1.5.3", "es-toolkit": "^1.26.1", "esrap": "^1.2.2", "magic-string": "^0.30.12", "svelte-ast-print": "^0.4.0", "zimmerframe": "^1.1.2" }, "peerDependencies": { "@storybook/svelte": "^0.0.0-0 || ^8.2.0 || ^9.0.0 || ^9.1.0-0 || ^10.0.0-0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0 || ^10.5.0-0 || ^10.6.0-0 || ^11.0.0-0", "@sveltejs/vite-plugin-svelte": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", "storybook": "^0.0.0-0 || ^8.2.0 || ^9.0.0 || ^9.1.0-0 || ^10.0.0-0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0 || ^10.5.0-0 || ^10.6.0-0 || ^11.0.0-0", "svelte": "^5.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-WD9bVaimpxwRE+f89vBMvw3pWKe8xDUW4jHeZbVPB0zSpH+7Ad/rFQ1oiAklswiVYwF/NXvnC7SZ4V+UXhngLg=="], + + "@storybook/builder-vite": ["@storybook/builder-vite@10.6.0", "", { "dependencies": { "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.6.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-7zt8tYB6dnSO8fbjUz91MHexGDpf9HrLlA3FAmlVb+2ZL3eWUs499L3I6BDUymcWu7oW5WCBJlB8ERj8ZLLzig=="], + + "@storybook/csf": ["@storybook/csf@0.1.13", "", { "dependencies": { "type-fest": "^2.19.0" } }, "sha512-7xOOwCLGB3ebM87eemep89MYRFTko+D8qE7EdAAq74lgdqRR5cOUtYWJLjO2dLtP94nqoOdHJo6MdLLKzg412Q=="], + + "@storybook/global": ["@storybook/global@5.0.0", "", {}, "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ=="], + + "@storybook/icons": ["@storybook/icons@2.1.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Fxh9vYpX9bQqFeHRiY8h2ApeRGDzRSMLwJwNZ/AIRqnyOKHxRKL+yFe+ctEkVJmuptRE9u1Hrn8ZZNHyfDKKNg=="], + + "@storybook/svelte": ["@storybook/svelte@10.6.0", "", { "dependencies": { "ts-dedent": "^2.0.0", "type-fest": "^5.6.0" }, "peerDependencies": { "storybook": "^10.6.0", "svelte": "^5.0.0" } }, "sha512-H7zdGvuVx/Z4C9LlfwdhVuVPdNlMwVZJ5IV6DZW3huVOZNiZcWMnftOaLGIxiLI2TZmuYJV50cBwjed5RLImLA=="], + + "@storybook/svelte-vite": ["@storybook/svelte-vite@10.6.0", "", { "dependencies": { "@storybook/builder-vite": "10.6.0", "@storybook/svelte": "10.6.0", "magic-string": "^1.1.0", "svelte2tsx": "^0.7.55", "typescript": "^4.9.4 || ^5.0.0" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", "storybook": "^10.6.0", "svelte": "^5.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-j1X/AOn0Z5dxcmtbox6z1tvz7erTuD55kR1tW3OGJpbV5mjdSuvc9aEqz+s3hgxTJgC500327K7z55Co7pG2Pw=="], + + "@storybook/sveltekit": ["@storybook/sveltekit@10.6.0", "", { "dependencies": { "@storybook/builder-vite": "10.6.0", "@storybook/svelte": "10.6.0", "@storybook/svelte-vite": "10.6.0" }, "peerDependencies": { "storybook": "^10.6.0", "svelte": "^5.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-4WIa6xzrFMlGfoI0r9irJrNKYl8faPLxfFitHKmDcZ3FAaY9wLOlkAn3mLb/EXfzgz3nftK7ZYMlWkU3rsCXzw=="], + "@svelte-put/shortcut": ["@svelte-put/shortcut@4.1.0", "", { "peerDependencies": { "svelte": "^5.1.0" } }, "sha512-wImNEIkbxAIWFqlfuhcbC+jRPDeRa/uJGIXHMEVVD+jqL9xCwWNnkGQJ6Qb2XVszuRLHlb8SGZDL3Io/h3vs8w=="], "@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.9", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA=="], @@ -252,6 +368,18 @@ "@tauri-apps/plugin-updater": ["@tauri-apps/plugin-updater@2.10.1", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-NFYMg+tWOZPJdzE/PpFj2qfqwAWwNS3kXrb1tm1gnBJ9mYzZ4WDRrwy8udzWoAnfGCHLuePNLY1WVCNHnh3eRA=="], + "@testing-library/dom": ["@testing-library/dom@10.4.2", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-yzr2S9HyAIdhz2/6qHgbs665Q7PKVcDF05vsOlHPxG1mo36gKVesdYVeDLnXgfjJ03CrKRk08knc6+E/9m8v2Q=="], + + "@testing-library/jest-dom": ["@testing-library/jest-dom@6.9.1", "", { "dependencies": { "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", "css.escape": "^1.5.1", "dom-accessibility-api": "^0.6.3", "picocolors": "^1.1.1", "redent": "^3.0.0" } }, "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA=="], + + "@testing-library/user-event": ["@testing-library/user-event@14.6.7", "", { "peerDependencies": { "@testing-library/dom": ">=7.21.4" } }, "sha512-MPCpX8bxe8zS+JmmTwLp8jd0dy1rAm60Te/SL8JrQM3qvQJcBOs1d7IefJMyZzqM3EWBrDn/LWDt1BCGu4ASfg=="], + + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.4", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A=="], + + "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="], + + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], + "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="], "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], @@ -266,6 +394,8 @@ "@types/d3-zoom": ["@types/d3-zoom@3.0.8", "", { "dependencies": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" } }, "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw=="], + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], "@types/node": ["@types/node@22.20.2", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-xlvWf4Vs9n1PEVYwP1n4vvG07M6y8WgvJ2t0vbrWTmijsIHp1cS+uJ2kMIRdY3nHZK0nCYKrPeD171+SzF4/zw=="], @@ -274,26 +404,52 @@ "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], + "@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], + + "@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], + + "@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], + + "@webcontainer/env": ["@webcontainer/env@1.1.1", "", {}, "sha512-6aN99yL695Hi9SuIk1oC88l9o0gmxL1nGWWQ/kNy81HigJ0FoaoTXpytCj6ItzgyCEwA9kF1wixsTuv5cjsgng=="], + "@xyflow/svelte": ["@xyflow/svelte@1.5.2", "", { "dependencies": { "@svelte-put/shortcut": "^4.1.0", "@xyflow/system": "0.0.76" }, "peerDependencies": { "svelte": "^5.25.0" } }, "sha512-jPLc2cwRmrkXSv/jvsNDQqTGCsNyTWURKtBl28UCH3BdOAA3CIc0/oQ0EvIcjPdzr0NwvWMgq9TmLdjOIccCEQ=="], "@xyflow/system": ["@xyflow/system@0.0.76", "", { "dependencies": { "@types/d3-drag": "^3.0.7", "@types/d3-interpolate": "^3.0.4", "@types/d3-selection": "^3.0.10", "@types/d3-transition": "^3.0.8", "@types/d3-zoom": "^3.0.8", "d3-drag": "^3.0.0", "d3-interpolate": "^3.0.1", "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0" } }, "sha512-hvwvnRS1B3REwVDlWexsq7YQaPZeG3/mKo1jv38UmnpWmxihp14bW6VtEOuHEwJX2FvzFw8k77LyKSk/wiZVNA=="], "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], + "aria-query": ["aria-query@5.3.1", "", {}, "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g=="], + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + + "ast-types": ["ast-types@0.16.3", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-FvWoWYfSCM6kRxCSH+MGLHIKKGRL6A6AW7Zek2O32REPQRdg131428uRTKMBYAeRd3XXAaHDS60Wpri7CdKDrA=="], + "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], "bun-types": ["bun-types@1.4.2", "", { "dependencies": { "@types/node": "*" } }, "sha512-bxV1FgK7yBIzjRe5zBozIM4Bem11ZJcCXSrjWRG3YWLt8yFDePu4cLjpebO8OvPeIE9trbyPF4fuj3Cia4Fj3w=="], + "bundle-name": ["bundle-name@4.1.1", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA=="], + + "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], + + "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="], + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], "cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="], + "css.escape": ["css.escape@1.5.1", "", {}, "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="], + "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="], "d3-dispatch": ["d3-dispatch@3.0.1", "", {}, "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg=="], @@ -312,21 +468,41 @@ "d3-zoom": ["d3-zoom@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "2 - 3", "d3-transition": "2 - 3" } }, "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw=="], + "dedent": ["dedent@1.7.2", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA=="], + + "dedent-js": ["dedent-js@1.0.1", "", {}, "sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ=="], + + "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], + "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], + "default-browser": ["default-browser@5.5.1", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw=="], + + "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], + + "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "devalue": ["devalue@5.8.1", "", {}, "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw=="], + "dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], + "enhanced-resolve": ["enhanced-resolve@5.21.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q=="], "error-causes": ["error-causes@3.0.2", "", {}, "sha512-i0B8zq1dHL6mM85FGoxaJnVtx6LD5nL2v0hlpGdntg5FOSyzQ46c9lmz5qx0xRS2+PWHGOHcYxGIBC5Le2dRMw=="], + "es-toolkit": ["es-toolkit@1.52.0", "", {}, "sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA=="], + "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="], - "esrap": ["esrap@2.2.8", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, "peerDependencies": { "@typescript-eslint/types": "^8.2.0" }, "optionalPeers": ["@typescript-eslint/types"] }, "sha512-MPweq2EvEGj8jwOI7Hgycw/QIHzqA1EbAM8lG7p+FBfZbZq/hQ6h3AMsqnu/djzisH1KVWNzbb7LSgIVtMlPSg=="], + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], + + "esrap": ["esrap@1.4.9", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-3OMlcd0a03UGuZpPeUC1HxR3nA23l+HEyCiZw3b3FumJIN9KphoGzDJKMXI1S72jVS1dsenDyQC0kJlO1U9E1g=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], @@ -334,10 +510,22 @@ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], + + "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], + + "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], + "is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="], + "is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], + "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], + "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], @@ -366,8 +554,14 @@ "locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="], + "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], + + "lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="], + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + "min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="], + "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], @@ -376,6 +570,14 @@ "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], + "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], + + "oxc-parser": ["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" } }, "sha512-bkgD4qHlN7WxLdX8bLXdaU54TtQtAIg/ZBAfm0aje/mo3MRDo3P0hZSgr4U7O3xfX+fQmR5AP04JS/TGcZLcFA=="], + + "oxc-resolver": ["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" } }, "sha512-w5tLwYN3Zo24w5EeWJjJWZOwhYqTtC8PS2B1tIt7BZUuqTIcU07sQValbDw+rq7+AuAGzOHklgK+ifsy4lpXfw=="], + + "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], @@ -388,22 +590,44 @@ "prettier-plugin-tailwindcss": ["prettier-plugin-tailwindcss@0.8.0", "", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-hermes": "*", "@prettier/plugin-oxc": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-hermes", "@prettier/plugin-oxc", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-svelte"] }, "sha512-V8ITGH87yuBDF6JpEZTOVlUz/saAwqb8f3HRgUj8Lh+tGCcrmorhsLpYqzygwFwK0PE2Ib6Mv3M7T/uE2tZV1g=="], + "pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="], + + "react": ["react@19.3.0", "", {}, "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog=="], + + "react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + "recast": ["recast@0.23.21", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-mFAyJq9vUbSTARLZUvAEf1z3YxlvAwswbmxMx2mPA/MSm4KmpwvwvhsH/NIrZhyOuwD60Lzyw2qh83uCbgTPYw=="], + + "redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="], + "rollup": ["rollup@4.60.4", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.4", "@rollup/rollup-android-arm64": "4.60.4", "@rollup/rollup-darwin-arm64": "4.60.4", "@rollup/rollup-darwin-x64": "4.60.4", "@rollup/rollup-freebsd-arm64": "4.60.4", "@rollup/rollup-freebsd-x64": "4.60.4", "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", "@rollup/rollup-linux-arm-musleabihf": "4.60.4", "@rollup/rollup-linux-arm64-gnu": "4.60.4", "@rollup/rollup-linux-arm64-musl": "4.60.4", "@rollup/rollup-linux-loong64-gnu": "4.60.4", "@rollup/rollup-linux-loong64-musl": "4.60.4", "@rollup/rollup-linux-ppc64-gnu": "4.60.4", "@rollup/rollup-linux-ppc64-musl": "4.60.4", "@rollup/rollup-linux-riscv64-gnu": "4.60.4", "@rollup/rollup-linux-riscv64-musl": "4.60.4", "@rollup/rollup-linux-s390x-gnu": "4.60.4", "@rollup/rollup-linux-x64-gnu": "4.60.4", "@rollup/rollup-linux-x64-musl": "4.60.4", "@rollup/rollup-openbsd-x64": "4.60.4", "@rollup/rollup-openharmony-arm64": "4.60.4", "@rollup/rollup-win32-arm64-msvc": "4.60.4", "@rollup/rollup-win32-ia32-msvc": "4.60.4", "@rollup/rollup-win32-x64-gnu": "4.60.4", "@rollup/rollup-win32-x64-msvc": "4.60.4", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g=="], + "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], + "sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="], + "scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="], + "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], "set-cookie-parser": ["set-cookie-parser@3.1.0", "", {}, "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw=="], "sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="], + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "storybook": ["storybook@10.6.0", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.2", "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "6.9.1", "@testing-library/user-event": "^14.6.3", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "@webcontainer/env": "^1.1.1", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0 || ^0.28.0", "jsonc-parser": "^3.3.1", "open": "^10.2.0", "oxc-parser": "^0.127.0", "oxc-resolver": "11.21.2", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.21.1" }, "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" }, "optionalPeers": ["@types/react", "prettier", "vite-plus"], "bin": "./dist/bin/dispatcher.js" }, "sha512-H48X6AURAFTpfN2zEux6vNesee5Oq8Gxj1IanYmblPZ218sfKSeyyerF1NC7GD7BrruoC8L4/cU0zII4y+d6zw=="], + + "strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="], + "svelte": ["svelte@5.55.7", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.8.1", "esm-env": "^1.2.1", "esrap": "^2.2.4", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-ymI5ykLPwIHW839E053FQbI1G+jnRFJEw3Kv5Y4njixVWywQBx+NUFpkkKyk5LIb36Fg9DVXSYpqiGekLD0hyw=="], + "svelte-ast-print": ["svelte-ast-print@0.4.2", "", { "dependencies": { "esrap": "1.2.2", "zimmerframe": "1.1.2" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-hRHHufbJoArFmDYQKCpCvc0xUuIEfwYksvyLYEQyH+1xb5LD5sM/IthfooCdXZQtOIqXz6xm7NmaqdfwG4kh6w=="], + "svelte-check": ["svelte-check@4.4.8", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-67adfgBox5eNSNIvIIwgFizKGdcRrGpiMoNO2obHcYuLz7iTa8Xgm/NGU3ntMFnNm8K1grFOIG6HhMLX/vcN8w=="], "svelte-floating-ui": ["svelte-floating-ui@1.6.2", "", { "dependencies": { "@floating-ui/dom": "^1.6.13" } }, "sha512-EC+DZtBey50P6l3NSzNQWon3cip8a1bzwdpmCdc45kymqEWL4BKhPemAq7SQ9QLebDPaMECW6YodxFbs2d+O/w=="], @@ -414,24 +638,54 @@ "svelte-writable-derived": ["svelte-writable-derived@3.1.1", "", { "peerDependencies": { "svelte": "^3.2.1 || ^4.0.0-next.1 || ^5.0.0-next.94" } }, "sha512-w4LR6/bYZEuCs7SGr+M54oipk/UQKtiMadyOhW0PTwAtJ/Ai12QS77sLngEcfBx2q4H8ZBQucc9ktSA5sUGZWw=="], + "svelte2tsx": ["svelte2tsx@0.7.61", "", { "dependencies": { "dedent-js": "^1.0.1", "scule": "^1.3.0" }, "peerDependencies": { "svelte": "^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0", "typescript": "^4.9.4 || ^5.0.0 || ^6.0.0" } }, "sha512-EpQ/+UHITBULeUojx/LLD3uTYasZXcX1BrqlrzfLUnT9vdUhaOWK59a3ryWcFU8L0sY1SP+gRhJ2Beiis98+qw=="], + + "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], + "tailwindcss": ["tailwindcss@4.3.0", "", {}, "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q=="], "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], + "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], + "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], + "tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], + + "tinyspy": ["tinyspy@4.0.6", "", {}, "sha512-u8KszXvGfU68hVcZpRHKG28T0krMuv2G5nDhiHaMLen/gIuFEgIJhaJuO69qjnXg5paSrbPMFfx3brNuN8eVSg=="], + "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="], + "ts-dedent": ["ts-dedent@2.3.0", "", {}, "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "type-fest": ["type-fest@5.10.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-NoSdpq/WEiAg5sjmBkmV/hfxv6HJH4NqPNrqjtSO5CwRmpsDfaf4begxW34KdJykH/l1yHtwBWQkCRdoXO8mPA=="], + "typescript": ["typescript@5.6.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="], "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "use-sync-external-store": ["use-sync-external-store@1.7.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A=="], + "vite": ["vite@6.4.2", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ=="], "vitefu": ["vitefu@1.1.3", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="], + "ws": ["ws@8.22.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg=="], + + "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], + "zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="], + "@oxc-resolver/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q=="], + + "@oxc-resolver/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg=="], + + "@storybook/csf/type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="], + + "@storybook/svelte-vite/magic-string": ["magic-string@1.4.2", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.6.0" } }, "sha512-vG+rjFRj1PqdIBozIxAGMjPlOhaVe+GXpbttY/iSK7rGcJRMlwNJO7dcUwmUqkymsFLJiNGI06t4D7Fr7yRC9g=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], @@ -444,6 +698,20 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@testing-library/dom/aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="], + + "@testing-library/jest-dom/dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="], + "rollup/@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "svelte/esrap": ["esrap@2.2.8", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, "peerDependencies": { "@typescript-eslint/types": "^8.2.0" }, "optionalPeers": ["@typescript-eslint/types"] }, "sha512-MPweq2EvEGj8jwOI7Hgycw/QIHzqA1EbAM8lG7p+FBfZbZq/hQ6h3AMsqnu/djzisH1KVWNzbb7LSgIVtMlPSg=="], + + "svelte-ast-print/esrap": ["esrap@1.2.2", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15", "@types/estree": "^1.0.1" } }, "sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw=="], + + "svelte-ast-print/zimmerframe": ["zimmerframe@1.1.2", "", {}, "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w=="], + + "@oxc-resolver/binding-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], + + "@storybook/svelte-vite/magic-string/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.6.0", "", {}, "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw=="], } } diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index a3b254d2..1dc0082a 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -40,6 +40,7 @@ bun run tauri dev ```bash bun run check # svelte-check + tsc bun run generate # regenerate TypeScript types from Rust (ts-rs) +bun run storybook # Storybook on :6006, nodes and shared components with mocked engine cargo check --manifest-path src-tauri/Cargo.toml bun run tauri build --bundles app # local .app build (macOS) bun run tauri build --bundles appimage # local AppImage build (Linux) diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 396b45c0..d17aa002 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -62,4 +62,5 @@ A platform that cannot support the feature returns a real error from that backen 2. Confirm the category: input, effect, output, monitor, or network. 3. Sketch the node's inline parameters — every one must fit in the node. 4. Check which per-OS files the change reaches. -5. Split into smallest viable PRs. Each PR follows [CONTRIBUTING.md](../CONTRIBUTING.md). +5. Build the node from existing Storybook components and add its stories, per [UI.md](UI.md). +6. Split into smallest viable PRs. Each PR follows [CONTRIBUTING.md](../CONTRIBUTING.md). diff --git a/docs/RULES.md b/docs/RULES.md index b73dc08c..221212db 100644 --- a/docs/RULES.md +++ b/docs/RULES.md @@ -1,6 +1,6 @@ # RULES.md -Universal rules that apply to every change in this repo. Concept and architecture background: [CONCEPT.md](CONCEPT.md). What a new feature must look like: [FEATURES.md](FEATURES.md). PR hygiene: [CONTRIBUTING.md](../CONTRIBUTING.md). +Universal rules that apply to every change in this repo. Concept and architecture background: [CONCEPT.md](CONCEPT.md). What a new feature must look like: [FEATURES.md](FEATURES.md). How UI is built: [UI.md](UI.md). PR hygiene: [CONTRIBUTING.md](../CONTRIBUTING.md). ## Sections @@ -47,4 +47,5 @@ type(scope): subject - Read the current code, not earlier explanations. - RT path change → `cargo check`. - Svelte change → `bun run check`. +- New node or shared component → add its stories in the same commit, see [UI.md](UI.md). - Rust `#[derive(TS)]` change → `bun run generate`, commit the generated files with the Rust change. diff --git a/docs/UI.md b/docs/UI.md new file mode 100644 index 00000000..3cd23664 --- /dev/null +++ b/docs/UI.md @@ -0,0 +1,48 @@ +# UI.md + +How node and app UI is built. Code conventions for Svelte: [CONCEPT.md](CONCEPT.md#frontend). What a node may contain: [FEATURES.md](FEATURES.md). + +## Sections + +- [Storybook is the catalogue](#storybook-is-the-catalogue) +- [Reuse before you build](#reuse-before-you-build) +- [Adding a node](#adding-a-node) +- [Adding a shared component](#adding-a-shared-component) +- [Mocked engine](#mocked-engine) + +## Storybook is the catalogue + +`bun run storybook` opens every node and every shared component on :6006, driven by a mocked engine. Start there before writing UI: it shows what already exists and how each piece looks in every state, in light and dark. + +| Folder | Contents | +| ------------------------- | ---------------------------------------------------- | +| `src/stories/nodes/` | One file per node kind, plus `Nodes/Gallery` | +| `src/stories/forms/` | Inputs: combobox, toggle, slider, stepper, segmented | +| `src/stories/components/` | Display pieces: meter bar, signal bars, copy button | +| `src/stories/harness/` | Node canvas, mocked engine, fake signal, arg helpers | + +## Reuse before you build + +- Pick controls from Storybook first. A node that needs a dropdown uses `Combobox`, a parameter uses the effect `Slider`, an on/off uses `Toggle`, a small choice uses `SegmentedButtons`. +- If nothing fits, extend the existing component rather than cloning it into the node. +- A new visual element used, or likely to be used, in more than one place becomes a shared component, not markup inside a node. + +## Adding a node + +The node's stories land in the same commit as the node, in `src/stories/nodes/.stories.svelte`: + +- Render it through `NodeCanvas` with `kind` and the story args. +- Expose every field of the node's data as a control (`dataArgs`), plus the signal (`signalArgs`), wiring (`wiringArgs`) and engine state (`envArgs`) the node reacts to. +- One named story per visual state: defaults, extremes, bypassed, unwired, multichannel, missing device or file, errors, per-platform differences. + +## Adding a shared component + +Its stories land in the same commit, in `src/stories/forms/` for inputs or `src/stories/components/` for everything else: + +- Controls for every prop that changes how it looks. +- Controlled inputs go through `Controlled` so they respond to clicks, with changes logged in the Actions panel via `fn()`. +- One named story per state: empty, filled, disabled, compact size, overflow (long labels, long lists). + +## Mocked engine + +Stories never call the real Tauri backend. `harness/backend.ts` answers every command the UI sends, `harness/signal.ts` emits scope, meter, loudness, gain-reduction and transport events. When a node needs a new command or event, add it there with an `envArgs` / `signalArgs` control; do not stub it inside a single story. diff --git a/package.json b/package.json index b0b884a1..e875cecd 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,9 @@ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "tauri": "tauri", "format": "prettier --write \"**/*.{ts,tsx,md,svelte,json}\" && cargo fmt --manifest-path src-tauri/Cargo.toml", - "generate": "cd src-tauri && cargo test" + "generate": "cd src-tauri && cargo test", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build" }, "license": "MIT", "dependencies": { @@ -37,6 +39,9 @@ "tailwindcss": "^4.3.0" }, "devDependencies": { + "@storybook/addon-svelte-csf": "^5.1.3", + "@storybook/svelte": "^10.6.0", + "@storybook/sveltekit": "^10.6.0", "@sveltejs/adapter-static": "^3.0.6", "@sveltejs/kit": "^2.9.0", "@sveltejs/vite-plugin-svelte": "^7.1.2", @@ -44,6 +49,7 @@ "@types/semver": "^7.8.0", "bun-types": "^1.4.2", "prettier": "3.8.3", + "storybook": "^10.6.0", "svelte": "^5.0.0", "svelte-check": "^4.0.0", "typescript": "~5.6.2", diff --git a/src/routes/preview/+page.svelte b/src/routes/preview/+page.svelte deleted file mode 100644 index 3c7e3ed0..00000000 --- a/src/routes/preview/+page.svelte +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - diff --git a/src/routes/preview/_fake_signal.ts b/src/routes/preview/_fake_signal.ts deleted file mode 100644 index 0536bd6d..00000000 --- a/src/routes/preview/_fake_signal.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { emit } from '@tauri-apps/api/event'; - -const SR = 48_000; -const SCOPE_FRAMES = 1024; -const SPECTRUM_FRAMES = 4096; // the engine's spectrum window size -const TICK_MS = 40; - -const PARTIALS = [ - { f: 110, a: 0.32 }, - { f: 220, a: 0.18 }, - { f: 440, a: 0.22 }, - { f: 1200, a: 0.12 }, - { f: 3500, a: 0.06 }, - { f: 7800, a: 0.03 } -]; - -interface MonitorIds { - levelMeter: string; - lufsMeter: string; - waveform: string; - spectrum: string; -} - -/** Synthetic 2-channel signal so the monitor nodes render live in the preview. */ -export function startFakeSignal(ids: MonitorIds): () => void { - const phase = PARTIALS.map(() => 0); - let t = 0; - - function render(frames: number): number[][] { - const left = new Array(frames); - const right = new Array(frames); - for (let i = 0; i < frames; i++) { - // Slow swell keeps the meters moving instead of sitting on one value. - const env = 0.55 + 0.4 * Math.sin(2 * Math.PI * 0.12 * (t + i / SR)); - let s = 0; - for (let p = 0; p < PARTIALS.length; p++) { - s += PARTIALS[p].a * Math.sin(phase[p] + (2 * Math.PI * PARTIALS[p].f * i) / SR); - } - s = (s + 0.02 * (Math.random() * 2 - 1)) * env; - left[i] = s; - right[i] = s * 0.85 + 0.03 * (Math.random() * 2 - 1); - } - return [left, right]; - } - - function advance(frames: number) { - for (let p = 0; p < PARTIALS.length; p++) { - phase[p] = (phase[p] + (2 * Math.PI * PARTIALS[p].f * frames) / SR) % (2 * Math.PI); - } - t += frames / SR; - } - - function stats(chans: number[][]) { - return chans.map((c) => { - let peak = 0; - let sum = 0; - for (const v of c) { - const a = Math.abs(v); - if (a > peak) peak = a; - sum += v * v; - } - return { peak, rms: Math.sqrt(sum / c.length) }; - }); - } - - const timer = setInterval(() => { - const block = render(SCOPE_FRAMES); - advance(SCOPE_FRAMES); - const st = stats(block); - - emit('audio://scope', { nodeId: ids.waveform, channels: 2, data: block, sampleRate: SR }); - emit('audio://scope', { - nodeId: ids.spectrum, - channels: 2, - data: render(SPECTRUM_FRAMES), - sampleRate: SR - }); - emit('audio://meter', { - nodeId: ids.levelMeter, - peaks: st.map((s) => s.peak), - rms: st.map((s) => s.rms) - }); - - const lufs = -18 + 4 * Math.sin(2 * Math.PI * 0.08 * t); - const tp = 20 * Math.log10(Math.max(st[0].peak, 1e-6)); - emit('audio://lufs', { - nodeId: ids.lufsMeter, - momentary: lufs, - shortterm: lufs - 0.8, - integrated: -17.4, - tpL: tp, - tpR: tp - 1.2, - lra: 6.3, - rms: 20 * Math.log10(Math.max(st[0].rms, 1e-6)), - noiseFloor: -68, - samplePeak: tp, - dcOffset: 0.0008, - correlation: 0.86, - clips: 0 - }); - }, TICK_MS); - - return () => clearInterval(timer); -} diff --git a/src/stories/components/copy_button.stories.svelte b/src/stories/components/copy_button.stories.svelte new file mode 100644 index 00000000..47150d9d --- /dev/null +++ b/src/stories/components/copy_button.stories.svelte @@ -0,0 +1,18 @@ + + +{#snippet template(args: Record)} +
+{/snippet} + + + diff --git a/src/stories/components/meter_bar.stories.svelte b/src/stories/components/meter_bar.stories.svelte new file mode 100644 index 00000000..f5651b2f --- /dev/null +++ b/src/stories/components/meter_bar.stories.svelte @@ -0,0 +1,42 @@ + + +{#snippet template(args: Record)} +
+
+ +
+
+{/snippet} + + + + + + diff --git a/src/stories/components/signal_bars.stories.svelte b/src/stories/components/signal_bars.stories.svelte new file mode 100644 index 00000000..b6358285 --- /dev/null +++ b/src/stories/components/signal_bars.stories.svelte @@ -0,0 +1,23 @@ + + +{#snippet template(args: Record)} +
+{/snippet} + + + + + diff --git a/src/stories/forms/combobox.stories.svelte b/src/stories/forms/combobox.stories.svelte new file mode 100644 index 00000000..d8e7e1c5 --- /dev/null +++ b/src/stories/forms/combobox.stories.svelte @@ -0,0 +1,69 @@ + + +{#snippet rescan()} + new Promise((r) => setTimeout(r, 800))} /> +{/snippet} + +{#snippet action(close: () => void)} + + new Promise((r) => setTimeout(r, 800))} /> +{/snippet} + +{#snippet template({ footer, onChange, ...args }: Record)} +
+
+ +
+
+{/snippet} + + + + + + + + + diff --git a/src/stories/forms/number_stepper.stories.svelte b/src/stories/forms/number_stepper.stories.svelte new file mode 100644 index 00000000..742e15c0 --- /dev/null +++ b/src/stories/forms/number_stepper.stories.svelte @@ -0,0 +1,32 @@ + + +{#snippet template({ onchange, ...args }: Record)} +
+ +
+{/snippet} + + + + + + diff --git a/src/stories/forms/password_input.stories.svelte b/src/stories/forms/password_input.stories.svelte new file mode 100644 index 00000000..c68839d1 --- /dev/null +++ b/src/stories/forms/password_input.stories.svelte @@ -0,0 +1,22 @@ + + +{#snippet template(args: Record)} +
+
+ +
+
+{/snippet} + + + + diff --git a/src/stories/forms/segmented_buttons.stories.svelte b/src/stories/forms/segmented_buttons.stories.svelte new file mode 100644 index 00000000..e1ab86b8 --- /dev/null +++ b/src/stories/forms/segmented_buttons.stories.svelte @@ -0,0 +1,47 @@ + + +{#snippet template({ onSelect, ...args }: Record)} +
+ +
+{/snippet} + + + + + + diff --git a/src/stories/forms/slider.stories.svelte b/src/stories/forms/slider.stories.svelte new file mode 100644 index 00000000..44f721f4 --- /dev/null +++ b/src/stories/forms/slider.stories.svelte @@ -0,0 +1,59 @@ + + +{#snippet template({ onChange, ...args }: Record)} +
+ +
+{/snippet} + + + + + + + diff --git a/src/stories/forms/toggle.stories.svelte b/src/stories/forms/toggle.stories.svelte new file mode 100644 index 00000000..39b1dc39 --- /dev/null +++ b/src/stories/forms/toggle.stories.svelte @@ -0,0 +1,31 @@ + + +{#snippet template({ onChange, ...args }: Record)} +
+ +
+{/snippet} + + + + + + + diff --git a/src/stories/harness/args.ts b/src/stories/harness/args.ts new file mode 100644 index 00000000..f8677da0 --- /dev/null +++ b/src/stories/harness/args.ts @@ -0,0 +1,101 @@ +import type { InputType } from 'storybook/internal/types'; +import { DEFAULT_ENV, type MockEnv } from './backend'; +import { DEFAULT_SIGNAL, SIGNAL_SHAPES, type SignalOptions } from './signal'; + +type ArgTypes = Record; + +/** Channels cabled into the node from a stub source; grows channel sockets. */ +export const WIRED_INPUTS = 'wiredInputs'; + +const range = (min: number, max: number, step = 1) => ({ type: 'range' as const, min, max, step }); + +const SIGNAL_ARG_TYPES: Record = { + signal: { control: 'select', options: SIGNAL_SHAPES, description: 'Fake stream shape; `off` sends nothing' }, + signalLevel: { control: range(-60, 6), description: 'Peak level, dBFS' }, + signalFrequency: { control: range(20, 20_000, 10), description: 'Sine frequency, Hz' }, + signalChannels: { control: range(1, 8), description: 'Channels in the fake stream' }, + gainReduction: { control: range(0, 24, 0.5), description: 'Reported gain reduction, dB' }, + signalMotion: { control: 'boolean', description: 'Swing level and gain reduction' }, + signalClipping: { control: 'boolean', description: 'Overdrive into full-scale clipping' } +}; + +const ENV_ARG_TYPES: Record = { + platform: { control: 'inline-radio', options: ['macos', 'windows', 'linux'] }, + running: { control: 'boolean', description: 'Pipeline active' }, + pipelineSampleRate: { control: 'select', options: [44_100, 48_000, 88_200, 96_000] }, + inputDevices: { control: range(0, 6) }, + outputDevices: { control: range(0, 6) }, + deviceChannels: { control: range(1, 16) }, + deviceSampleRate: { control: 'select', options: [44_100, 48_000, 88_200, 96_000] }, + deviceVolume: { control: 'inline-radio', options: ['supported', 'unsupported', 'unsynced'] }, + apps: { control: range(0, 8) }, + capturePermission: { control: 'inline-radio', options: ['none', 'allowed', 'denied', 'unknown'] }, + plugins: { control: range(0, 6) }, + pluginParams: { control: range(0, 10) }, + pluginEditor: { control: 'boolean', description: 'Plugin exposes a GUI' }, + link: { control: 'inline-radio', options: ['offline', 'good', 'lossy'] }, + linkChannels: { control: range(1, 8) }, + webrtcPhase: { control: 'inline-radio', options: ['idle', 'hosting', 'joining'] }, + webrtcPeers: { control: range(0, 5) }, + missingFile: { control: 'boolean', description: 'Chosen file no longer exists' } +}; + +function withCategory(types: Record, category: string): ArgTypes { + return Object.fromEntries(Object.entries(types).map(([k, v]) => [k, { ...v, table: { category } }])); +} + +/** Controls for the fake stream; all of them unless a subset is named. */ +export function signalArgs(keys: (keyof SignalOptions)[] = Object.keys(SIGNAL_ARG_TYPES) as (keyof SignalOptions)[]) { + const types = Object.fromEntries(keys.map((k) => [k, SIGNAL_ARG_TYPES[k]])); + const args = Object.fromEntries(keys.map((k) => [k, DEFAULT_SIGNAL[k]])); + return { argTypes: withCategory(types, 'Signal'), args }; +} + +/** Controls for the mocked engine; only the named ones are shown. */ +export function envArgs(keys: (keyof MockEnv)[]) { + const types = Object.fromEntries(keys.map((k) => [k, ENV_ARG_TYPES[k]])); + const args = Object.fromEntries(keys.map((k) => [k, DEFAULT_ENV[k]])); + return { argTypes: withCategory(types, 'Engine'), args }; +} + +export function wiringArgs(defaultChannels = 2, max = 8) { + return { + argTypes: withCategory({ [WIRED_INPUTS]: { control: range(0, max), description: 'Channels cabled in' } }, 'Wiring'), + args: { [WIRED_INPUTS]: defaultChannels } + }; +} + +export function dataArgs(types: ArgTypes, defaults: Record) { + return { argTypes: withCategory(types, 'Node data'), args: defaults }; +} + +/** Merges groups built by the helpers above into one meta `args` / `argTypes`. */ +export function compose(...groups: { argTypes: ArgTypes; args: Record }[]) { + return { + argTypes: Object.assign({}, ...groups.map((g) => g.argTypes)) as ArgTypes, + args: Object.assign({}, ...groups.map((g) => g.args)) as Record + }; +} + +export interface SplitArgs { + env: MockEnv; + signal: SignalOptions; + wiredInputs: number; + data: Record; +} + +export function splitArgs(args: Record): SplitArgs { + const env: Record = { ...DEFAULT_ENV }; + const signal: Record = { ...DEFAULT_SIGNAL }; + const data: Record = {}; + let wiredInputs = 0; + for (const [key, value] of Object.entries(args)) { + if (key in DEFAULT_ENV) env[key] = value; + else if (key in DEFAULT_SIGNAL) signal[key] = value; + else if (key === WIRED_INPUTS) wiredInputs = Number(value); + else data[key] = value; + } + return { env: env as unknown as MockEnv, signal: signal as unknown as SignalOptions, wiredInputs, data }; +} + +export { range }; diff --git a/src/stories/harness/backend.ts b/src/stories/harness/backend.ts new file mode 100644 index 00000000..4bbf6d91 --- /dev/null +++ b/src/stories/harness/backend.ts @@ -0,0 +1,258 @@ +import { mockIPC } from '@tauri-apps/api/mocks'; +import type { AudioApplication, AudioDevice, PluginDescriptor, PluginParam } from '$lib/modules/audio/types'; +import { audioStore } from '$lib/modules/audio/stores.svelte'; +import { appSettings } from '$lib/modules/settings/stores.svelte'; + +export type Platform = 'macos' | 'windows' | 'linux'; +export type LinkQuality = 'offline' | 'good' | 'lossy'; + +/** Everything the node UI reads from the engine, driven by story controls. */ +export interface MockEnv { + platform: Platform; + running: boolean; + pipelineSampleRate: number; + inputDevices: number; + outputDevices: number; + deviceChannels: number; + deviceSampleRate: number; + deviceVolume: 'supported' | 'unsupported' | 'unsynced'; + apps: number; + capturePermission: 'none' | 'allowed' | 'denied' | 'unknown'; + plugins: number; + pluginParams: number; + pluginEditor: boolean; + link: LinkQuality; + linkChannels: number; + webrtcPhase: 'idle' | 'hosting' | 'joining'; + webrtcPeers: number; + missingFile: boolean; +} + +export const DEFAULT_ENV: MockEnv = { + platform: 'macos', + running: false, + pipelineSampleRate: 48_000, + inputDevices: 3, + outputDevices: 3, + deviceChannels: 2, + deviceSampleRate: 48_000, + deviceVolume: 'supported', + apps: 4, + capturePermission: 'none', + plugins: 4, + pluginParams: 6, + pluginEditor: true, + link: 'good', + linkChannels: 2, + webrtcPhase: 'idle', + webrtcPeers: 0, + missingFile: false +}; + +const INPUT_NAMES = ['MacBook Pro Microphone', 'Scarlett 2i2 USB', 'Splitwave', 'AirPods Pro', 'Shure MV7', 'RODECaster Pro II']; +const OUTPUT_NAMES = ['MacBook Pro Speakers', 'Scarlett 2i2 USB', 'Splitwave', 'AirPods Pro', 'LG UltraFine Display', 'BlackHole 16ch']; +const APP_NAMES = ['Spotify', 'Google Chrome', 'Discord', 'Zoom', 'Music', 'OBS Studio', 'Firefox', 'Slack']; +const PLUGINS = [ + { name: 'Pro-Q 3', vendor: 'FabFilter', format: 'clap' as const }, + { name: 'Valhalla Supermassive', vendor: 'Valhalla DSP', format: 'clap' as const }, + { name: 'AUDelay', vendor: 'Apple', format: 'au' as const }, + { name: 'Surge XT', vendor: 'Surge Synth Team', format: 'clap' as const }, + { name: 'OTT', vendor: 'Xfer Records', format: 'au' as const }, + { name: 'Vital', vendor: 'Matt Tytel', format: 'clap' as const } +]; +const PARAM_NAMES = ['Gain', 'Mix', 'Frequency', 'Resonance', 'Attack', 'Release', 'Drive', 'Width', 'Output', 'Mode']; +const PEER_NAMES = ['Alex', 'Sam', 'Jordan', 'Riley', 'Casey']; + +function take(names: T[], count: number): T[] { + return Array.from({ length: count }, (_, i) => names[i % names.length]); +} + +export function mockInputDevices(env: MockEnv): AudioDevice[] { + return take(INPUT_NAMES, env.inputDevices).map((name, i) => ({ id: `in-${i + 1}`, name, kind: 'input' })); +} + +export function mockOutputDevices(env: MockEnv): AudioDevice[] { + return take(OUTPUT_NAMES, env.outputDevices).map((name, i) => ({ id: `out-${i + 1}`, name, kind: 'output' })); +} + +function mockApps(env: MockEnv): AudioApplication[] { + return take(APP_NAMES, env.apps).map((name, i) => ({ bundleId: `com.example.app${i + 1}`, name })); +} + +export function mockPlugins(env: MockEnv): PluginDescriptor[] { + return take(PLUGINS, env.plugins).map((p, i) => ({ + uid: `plugin-${i + 1}`, + format: p.format, + path: `/Library/Audio/Plug-Ins/${p.format.toUpperCase()}/${p.name}.${p.format === 'clap' ? 'clap' : 'component'}`, + pluginId: `com.example.plugin${i + 1}`, + name: p.name, + vendor: p.vendor, + version: '1.0.0' + })); +} + +function mockParams(count: number): PluginParam[] { + return take(PARAM_NAMES, count).map((name, i) => { + const stepped = name === 'Mode'; + return { + id: i, + name, + min: 0, + max: stepped ? 3 : 1, + default: stepped ? 0 : 0.5, + value: stepped ? 1 : ((i * 37) % 100) / 100, + step: stepped ? 1 : 0, + readOnly: false + }; + }); +} + +/** Node data the plugin status probe answers with; set by the canvas. */ +let nodeData: (id: string) => Record | undefined = () => undefined; + +export function setNodeDataLookup(lookup: typeof nodeData): void { + nodeData = lookup; +} + +interface Counters { + startedAt: number; +} + +function linkStats(env: MockEnv, counters: Counters) { + if (env.link === 'offline' || !env.running) return null; + const seconds = (performance.now() - counters.startedAt) / 1000; + const packets = Math.floor(seconds * 50); + const lost = env.link === 'lossy' ? Math.floor(packets * 0.04) : 0; + return { packets, lost, bytes: Math.floor(seconds * 12_000 * env.linkChannels) }; +} + +export function installMockBackend(env: MockEnv): void { + (window as unknown as Record).__TAURI_OS_PLUGIN_INTERNALS__ = { + platform: env.platform, + family: env.platform === 'windows' ? 'windows' : 'unix', + os_type: env.platform, + version: '1.0.0', + arch: 'aarch64', + eol: env.platform === 'windows' ? '\r\n' : '\n', + exe_extension: env.platform === 'windows' ? 'exe' : '' + }; + + const inputs = mockInputDevices(env); + const outputs = mockOutputDevices(env); + const apps = mockApps(env); + const plugins = mockPlugins(env); + const params = new Map(); + const volumes = new Map(); + const counters: Counters = { startedAt: performance.now() }; + const peers = take(PEER_NAMES, env.webrtcPeers).map((name, i) => ({ + peerId: `peer-${i + 1}`, + muted: i === 1, + name, + channels: [i % 2 === 0 ? 2 : 1] + })); + + mockIPC( + (cmd, payload) => { + const args = (payload ?? {}) as Record; + switch (cmd) { + case 'list_input_devices': + return inputs; + case 'list_output_devices': + return outputs; + case 'list_audio_applications': + return apps; + case 'get_app_icons': + return {}; + case 'device_info': + case 'capture_device_info': + return { sampleRate: env.deviceSampleRate, channels: env.deviceChannels, sampleFormat: 'f32' }; + case 'get_device_volume': { + if (env.deviceVolume === 'unsupported') return null; + const scalar = volumes.get(String(args.name)) ?? 0.75; + return { scalar, db: 20 * Math.log10(Math.max(scalar, 1e-4)) }; + } + case 'watch_device_volume': + if (env.deviceVolume === 'unsynced') throw new Error('volume notifications unavailable'); + return null; + case 'set_device_volume': + volumes.set(String(args.name), Number(args.scalar)); + return null; + case 'check_capture_permission': + return env.capturePermission === 'none' ? { kind: 'none', state: 'unknown' } : { kind: 'screenrecording', state: env.capturePermission }; + case 'is_pipeline_running': + return env.running; + case 'path_exists': + return !env.missingFile; + case 'scan_plugins': + return plugins; + case 'plugin_status': { + const path = nodeData(String(args.nodeId))?.path; + return { path: env.running && path ? path : null, hasEditor: env.pluginEditor }; + } + case 'get_plugin_params': { + const id = String(args.nodeId); + if (!params.has(id)) params.set(id, mockParams(env.pluginParams)); + return params.get(id); + } + case 'get_plugin_state': + return 'bW9jay1zdGF0ZQ=='; + case 'open_plugin_editor': + if (!env.pluginEditor) throw new Error('Plugin editor failed: this plugin has no GUI'); + return null; + case 'update_effect': { + const values = (args.data as Record | undefined)?.pluginParams as Record | undefined; + const list = params.get(String(args.nodeId)); + if (values && list) for (const [pid, v] of Object.entries(values)) list[Number(pid)].value = v; + return null; + } + case 'net_receiver_stats': { + const s = linkStats(env, counters); + if (!s) return null; + return { + ...s, + channels: env.linkChannels, + bufferMs: env.link === 'lossy' ? 60 : 20, + sampleRate: 48_000, + format: 'opus', + opusBitrate: 96_000, + opusApp: 'audio' + }; + } + case 'net_sender_stats': { + const s = linkStats(env, counters); + return s && { bytes: s.bytes, packets: s.packets }; + } + case 'webrtc_session_state': + return { phase: env.webrtcPhase, roomCode: env.webrtcPhase === 'idle' ? null : 'K7Q2XF', peers: env.webrtcPhase === 'idle' ? [] : peers }; + case 'webrtc_create_room': + return 'K7Q2XF'; + case 'webrtc_peer_pings': + return Object.fromEntries(peers.map((p, i) => [p.peerId, 18 + i * 25])); + case 'webrtc_peer_stats': { + const s = linkStats(env, counters) ?? { packets: 0, lost: 0 }; + return Object.fromEntries(peers.map((p, i) => [p.peerId, { pingMs: 18 + i * 25, packets: s.packets, lost: s.lost * i }])); + } + case 'webrtc_buffer_ms': + return env.link === 'lossy' ? 80 : 30; + case 'plugin:dialog|open': + return '/Users/demo/Music/demo-track.wav'; + case 'plugin:dialog|save': + return '/Users/demo/Recordings/take-01.wav'; + case 'plugin:store|entries': + case 'plugin:store|keys': + return []; + default: + return null; + } + }, + { shouldMockEvents: true } + ); + + audioStore.inputDevices = inputs; + audioStore.outputDevices = outputs; + audioStore.audioApplications = apps; + audioStore.isRunning = env.running; + audioStore.startedAt = env.running ? Date.now() : null; + audioStore.missingFilePaths = new Set(env.missingFile ? ['/Users/demo/Music/demo-track.wav'] : []); + appSettings.pipelineSampleRate = env.pipelineSampleRate; +} diff --git a/src/stories/harness/controlled.svelte b/src/stories/harness/controlled.svelte new file mode 100644 index 00000000..aac15b3d --- /dev/null +++ b/src/stories/harness/controlled.svelte @@ -0,0 +1,27 @@ + + + diff --git a/src/stories/harness/env_scope.svelte b/src/stories/harness/env_scope.svelte new file mode 100644 index 00000000..871bff54 --- /dev/null +++ b/src/stories/harness/env_scope.svelte @@ -0,0 +1,11 @@ + + +{@render children()} diff --git a/src/routes/preview/_grid.svelte b/src/stories/harness/gallery.svelte similarity index 57% rename from src/routes/preview/_grid.svelte rename to src/stories/harness/gallery.svelte index cf0d5fdc..eb828caa 100644 --- a/src/routes/preview/_grid.svelte +++ b/src/stories/harness/gallery.svelte @@ -1,44 +1,38 @@
{#each categoryOrder as cat (cat)}
-

- {categoryLabel[cat]} -

+

{categoryLabel[cat]}

{#each kindsByCategory[cat] as kind (kind)} {@const Comp = registry[kind].component} diff --git a/src/stories/harness/node_canvas.svelte b/src/stories/harness/node_canvas.svelte new file mode 100644 index 00000000..cdcfd9cc --- /dev/null +++ b/src/stories/harness/node_canvas.svelte @@ -0,0 +1,81 @@ + + +{#key envKey} + + +
+ +
+
+
+{/key} diff --git a/src/stories/harness/signal.ts b/src/stories/harness/signal.ts new file mode 100644 index 00000000..c5650e92 --- /dev/null +++ b/src/stories/harness/signal.ts @@ -0,0 +1,171 @@ +import { emit } from '@tauri-apps/api/event'; + +export type SignalShape = 'music' | 'sine' | 'noise' | 'impulses' | 'sweep' | 'silence' | 'off'; + +/** The synthetic stream a story feeds the node, as the engine would. */ +export interface SignalOptions { + signal: SignalShape; + /** Peak level of the generated signal, dBFS. */ + signalLevel: number; + signalFrequency: number; + signalChannels: number; + /** Gain reduction reported by dynamics nodes, dB. */ + gainReduction: number; + /** Swing the level and gain reduction so meters move. */ + signalMotion: boolean; + signalClipping: boolean; +} + +export const DEFAULT_SIGNAL: SignalOptions = { + signal: 'music', + signalLevel: -6, + signalFrequency: 440, + signalChannels: 2, + gainReduction: 4, + signalMotion: true, + signalClipping: false +}; + +export const SIGNAL_SHAPES: SignalShape[] = ['music', 'sine', 'noise', 'impulses', 'sweep', 'silence', 'off']; + +const SR = 48_000; +const SCOPE_FRAMES = 1024; +const SPECTRUM_FRAMES = 4096; +const TICK_MS = 40; + +const PARTIALS = [ + { f: 110, a: 0.32 }, + { f: 220, a: 0.18 }, + { f: 440, a: 0.22 }, + { f: 1200, a: 0.12 }, + { f: 3500, a: 0.06 }, + { f: 7800, a: 0.03 } +]; + +function dbToAmp(db: number): number { + return Math.pow(10, db / 20); +} + +function ampToDb(amp: number): number { + return 20 * Math.log10(Math.max(amp, 1e-6)); +} + +export interface SignalTargets { + nodeId: string; + /** Spectrum analyses a longer window than the scope. */ + wideScope: boolean; + running: boolean; +} + +/** Emits scope, meter, loudness, gain-reduction and transport ticks for one node. */ +export function startSignal(targets: SignalTargets, opts: SignalOptions): () => void { + if (opts.signal === 'off') return () => {}; + const { nodeId } = targets; + const channels = Math.max(1, opts.signalChannels); + let t = 0; + let transportFrames = 0; + const TOTAL_FRAMES = SR * 185; + + function envelope(time: number): number { + return opts.signalMotion ? 0.55 + 0.45 * Math.sin(2 * Math.PI * 0.12 * time) : 1; + } + + function sample(time: number, ch: number): number { + const detune = 1 + ch * 0.003; + switch (opts.signal) { + case 'sine': + return Math.sin(2 * Math.PI * opts.signalFrequency * detune * time); + case 'noise': + return Math.random() * 2 - 1; + case 'impulses': + return (time * 4) % 1 < 0.002 ? 1 : 0.01 * (Math.random() * 2 - 1); + case 'sweep': { + const phase = (time % 4) / 4; + const f = 20 * Math.pow(1000, phase); + return Math.sin(2 * Math.PI * f * time); + } + case 'silence': + return 0; + default: { + let s = 0; + for (const p of PARTIALS) s += p.a * Math.sin(2 * Math.PI * p.f * detune * time); + return s + 0.02 * (Math.random() * 2 - 1); + } + } + } + + function render(frames: number): number[][] { + const peak = dbToAmp(opts.signalLevel); + const out = Array.from({ length: channels }, () => new Array(frames)); + for (let i = 0; i < frames; i++) { + const time = t + i / SR; + const env = envelope(time) * peak; + for (let ch = 0; ch < channels; ch++) { + const v = sample(time, ch) * env * (ch === 0 ? 1 : 0.85); + out[ch][i] = opts.signalClipping ? Math.max(-1, Math.min(1, v * 4)) : v; + } + } + return out; + } + + function stats(block: number[][]) { + return block.map((c) => { + let peak = 0; + let sum = 0; + for (const v of c) { + const a = Math.abs(v); + if (a > peak) peak = a; + sum += v * v; + } + return { peak, rms: Math.sqrt(sum / c.length) }; + }); + } + + const timer = setInterval(() => { + const block = render(SCOPE_FRAMES); + const scope = targets.wideScope ? render(SPECTRUM_FRAMES) : block; + t += SCOPE_FRAMES / SR; + const st = stats(block); + + emit('audio://scope', { nodeId, channels, data: scope, sampleRate: SR }); + emit('audio://meter', { nodeId, peaks: st.map((s) => s.peak), rms: st.map((s) => s.rms) }); + + const grDb = opts.gainReduction * (opts.signalMotion ? 0.5 + 0.5 * Math.abs(Math.sin(2 * Math.PI * 0.3 * t)) : 1); + emit('audio://gr', { nodeId, grLin: dbToAmp(-grDb) }); + + const peakDb = ampToDb(st[0].peak); + const rmsDb = ampToDb(st[0].rms); + const loud = opts.signal === 'silence' ? -70 : rmsDb - 0.7; + emit('audio://lufs', { + nodeId, + momentary: loud, + shortterm: loud - 0.8, + integrated: loud - 1.4, + tpL: peakDb, + tpR: ampToDb(st[Math.min(1, st.length - 1)].peak), + lra: opts.signalMotion ? 6.3 : 1.2, + rms: rmsDb, + noiseFloor: -68, + samplePeak: peakDb, + dcOffset: 0.0008, + correlation: channels > 1 ? 0.86 : 1, + clips: opts.signalClipping ? Math.floor(t * 3) : 0 + }); + + if (targets.running) { + transportFrames = (transportFrames + SCOPE_FRAMES) % TOTAL_FRAMES; + emit('audio://audio_file_progress', { + nodeId, + frames: transportFrames, + totalFrames: TOTAL_FRAMES, + sampleRate: 44_100, + channels, + stopped: false, + paused: false + }); + emit('audio://recorder_progress', { nodeId, frames: Math.floor(t * SR), sampleRate: SR, stopped: false }); + } + }, TICK_MS); + + return () => clearInterval(timer); +} diff --git a/src/stories/harness/stub_source.svelte b/src/stories/harness/stub_source.svelte new file mode 100644 index 00000000..d96fcfee --- /dev/null +++ b/src/stories/harness/stub_source.svelte @@ -0,0 +1,19 @@ + + +
+ Source +
+ {#each { length: data.channels } as _, i (i)} +
+ {channelLabel(i, data.channels)} + +
+ {/each} +
+
diff --git a/src/stories/nodes/app_audio.stories.svelte b/src/stories/nodes/app_audio.stories.svelte new file mode 100644 index 00000000..d4a8daae --- /dev/null +++ b/src/stories/nodes/app_audio.stories.svelte @@ -0,0 +1,27 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + + + diff --git a/src/stories/nodes/audio_file.stories.svelte b/src/stories/nodes/audio_file.stories.svelte new file mode 100644 index 00000000..63b24412 --- /dev/null +++ b/src/stories/nodes/audio_file.stories.svelte @@ -0,0 +1,36 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + + + + diff --git a/src/stories/nodes/channel_balance.stories.svelte b/src/stories/nodes/channel_balance.stories.svelte new file mode 100644 index 00000000..e864f52b --- /dev/null +++ b/src/stories/nodes/channel_balance.stories.svelte @@ -0,0 +1,25 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + + diff --git a/src/stories/nodes/compressor.stories.svelte b/src/stories/nodes/compressor.stories.svelte new file mode 100644 index 00000000..f59e4216 --- /dev/null +++ b/src/stories/nodes/compressor.stories.svelte @@ -0,0 +1,35 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + + + diff --git a/src/stories/nodes/de_esser.stories.svelte b/src/stories/nodes/de_esser.stories.svelte new file mode 100644 index 00000000..9c250512 --- /dev/null +++ b/src/stories/nodes/de_esser.stories.svelte @@ -0,0 +1,29 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + diff --git a/src/stories/nodes/declick.stories.svelte b/src/stories/nodes/declick.stories.svelte new file mode 100644 index 00000000..24181e76 --- /dev/null +++ b/src/stories/nodes/declick.stories.svelte @@ -0,0 +1,24 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + diff --git a/src/stories/nodes/delay.stories.svelte b/src/stories/nodes/delay.stories.svelte new file mode 100644 index 00000000..45a3ffdb --- /dev/null +++ b/src/stories/nodes/delay.stories.svelte @@ -0,0 +1,29 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + diff --git a/src/stories/nodes/eq.stories.svelte b/src/stories/nodes/eq.stories.svelte new file mode 100644 index 00000000..b027ac7d --- /dev/null +++ b/src/stories/nodes/eq.stories.svelte @@ -0,0 +1,22 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + + diff --git a/src/stories/nodes/file_recording.stories.svelte b/src/stories/nodes/file_recording.stories.svelte new file mode 100644 index 00000000..2f6ddd76 --- /dev/null +++ b/src/stories/nodes/file_recording.stories.svelte @@ -0,0 +1,55 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + + + + + + + + + + diff --git a/src/stories/nodes/gain.stories.svelte b/src/stories/nodes/gain.stories.svelte new file mode 100644 index 00000000..bce35a7c --- /dev/null +++ b/src/stories/nodes/gain.stories.svelte @@ -0,0 +1,26 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + + + + diff --git a/src/stories/nodes/gallery.stories.svelte b/src/stories/nodes/gallery.stories.svelte new file mode 100644 index 00000000..545323d1 --- /dev/null +++ b/src/stories/nodes/gallery.stories.svelte @@ -0,0 +1,16 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + diff --git a/src/stories/nodes/level_meter.stories.svelte b/src/stories/nodes/level_meter.stories.svelte new file mode 100644 index 00000000..d14ee401 --- /dev/null +++ b/src/stories/nodes/level_meter.stories.svelte @@ -0,0 +1,22 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + + + + + diff --git a/src/stories/nodes/limiter.stories.svelte b/src/stories/nodes/limiter.stories.svelte new file mode 100644 index 00000000..e4e7892b --- /dev/null +++ b/src/stories/nodes/limiter.stories.svelte @@ -0,0 +1,30 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + diff --git a/src/stories/nodes/lufs_meter.stories.svelte b/src/stories/nodes/lufs_meter.stories.svelte new file mode 100644 index 00000000..4a53a857 --- /dev/null +++ b/src/stories/nodes/lufs_meter.stories.svelte @@ -0,0 +1,32 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + + + + + diff --git a/src/stories/nodes/microphone.stories.svelte b/src/stories/nodes/microphone.stories.svelte new file mode 100644 index 00000000..028259cb --- /dev/null +++ b/src/stories/nodes/microphone.stories.svelte @@ -0,0 +1,27 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + + + + + + diff --git a/src/stories/nodes/mute.stories.svelte b/src/stories/nodes/mute.stories.svelte new file mode 100644 index 00000000..16ae7017 --- /dev/null +++ b/src/stories/nodes/mute.stories.svelte @@ -0,0 +1,36 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + + + + diff --git a/src/stories/nodes/net_receiver.stories.svelte b/src/stories/nodes/net_receiver.stories.svelte new file mode 100644 index 00000000..37f80184 --- /dev/null +++ b/src/stories/nodes/net_receiver.stories.svelte @@ -0,0 +1,23 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + + diff --git a/src/stories/nodes/net_sender.stories.svelte b/src/stories/nodes/net_sender.stories.svelte new file mode 100644 index 00000000..79c92133 --- /dev/null +++ b/src/stories/nodes/net_sender.stories.svelte @@ -0,0 +1,35 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + + + + diff --git a/src/stories/nodes/noise_gate.stories.svelte b/src/stories/nodes/noise_gate.stories.svelte new file mode 100644 index 00000000..a2c0f210 --- /dev/null +++ b/src/stories/nodes/noise_gate.stories.svelte @@ -0,0 +1,34 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + + + diff --git a/src/stories/nodes/noise_suppressor.stories.svelte b/src/stories/nodes/noise_suppressor.stories.svelte new file mode 100644 index 00000000..e9a6ec6f --- /dev/null +++ b/src/stories/nodes/noise_suppressor.stories.svelte @@ -0,0 +1,33 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + + diff --git a/src/stories/nodes/plugin.stories.svelte b/src/stories/nodes/plugin.stories.svelte new file mode 100644 index 00000000..267f8c68 --- /dev/null +++ b/src/stories/nodes/plugin.stories.svelte @@ -0,0 +1,49 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + + + + + + diff --git a/src/stories/nodes/reverb.stories.svelte b/src/stories/nodes/reverb.stories.svelte new file mode 100644 index 00000000..7df807a9 --- /dev/null +++ b/src/stories/nodes/reverb.stories.svelte @@ -0,0 +1,31 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + + diff --git a/src/stories/nodes/saturator.stories.svelte b/src/stories/nodes/saturator.stories.svelte new file mode 100644 index 00000000..18c6630a --- /dev/null +++ b/src/stories/nodes/saturator.stories.svelte @@ -0,0 +1,26 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + + diff --git a/src/stories/nodes/speaker.stories.svelte b/src/stories/nodes/speaker.stories.svelte new file mode 100644 index 00000000..55b27cb2 --- /dev/null +++ b/src/stories/nodes/speaker.stories.svelte @@ -0,0 +1,29 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + + + + + + + diff --git a/src/stories/nodes/spectrum.stories.svelte b/src/stories/nodes/spectrum.stories.svelte new file mode 100644 index 00000000..6bfdadb4 --- /dev/null +++ b/src/stories/nodes/spectrum.stories.svelte @@ -0,0 +1,22 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + + + + + diff --git a/src/stories/nodes/system_audio.stories.svelte b/src/stories/nodes/system_audio.stories.svelte new file mode 100644 index 00000000..590242c6 --- /dev/null +++ b/src/stories/nodes/system_audio.stories.svelte @@ -0,0 +1,27 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + + + + + + diff --git a/src/stories/nodes/waveform.stories.svelte b/src/stories/nodes/waveform.stories.svelte new file mode 100644 index 00000000..2b9aa59a --- /dev/null +++ b/src/stories/nodes/waveform.stories.svelte @@ -0,0 +1,24 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + + + + + + + diff --git a/src/stories/nodes/webrtc.stories.svelte b/src/stories/nodes/webrtc.stories.svelte new file mode 100644 index 00000000..8e4a5bd5 --- /dev/null +++ b/src/stories/nodes/webrtc.stories.svelte @@ -0,0 +1,34 @@ + + +{#snippet template(a: Record)} + +{/snippet} + + + + + + + + +