Compare commits

...

3 commits

Author SHA1 Message Date
25996c38a9 Merge pull request #18 from codex/issue-13-storybook
feat: add Storybook component explorer
2026-06-18 19:04:18 +02:00
vince
17c9d24e31 fix: address Storybook review findings 2026-06-18 19:01:53 +02:00
vince
d95f064189 feat: add Storybook component explorer 2026-06-18 18:53:48 +02:00
49 changed files with 1831 additions and 8 deletions

1
.gitignore vendored
View file

@ -13,3 +13,4 @@ data/*.sqlite-*
coverage/
playwright-report/
test-results/
storybook-static/

19
.storybook/main.ts Normal file
View file

@ -0,0 +1,19 @@
import type { StorybookConfig } from "@storybook/sveltekit";
const config: StorybookConfig = {
stories: ["../src/**/*.stories.@(js|ts|svelte)"],
addons: [
"@storybook/addon-svelte-csf",
"@storybook/addon-a11y",
"@storybook/addon-vitest",
],
framework: {
name: "@storybook/sveltekit",
options: {},
},
docs: {
autodocs: "tag",
},
};
export default config;

23
.storybook/preview.ts Normal file
View file

@ -0,0 +1,23 @@
import "../src/app.css";
import type { Preview } from "@storybook/sveltekit";
const preview: Preview = {
parameters: {
backgrounds: {
default: "canvas",
values: [
{ name: "canvas", value: "#020302" },
{ name: "raised", value: "#0b0d0c" },
],
},
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
layout: "fullscreen",
},
};
export default preview;

View file

@ -23,6 +23,8 @@ This MVP uses Drizzle with Bun SQLite for local file-backed persistence.
- `bun run test`: run Vitest.
- `bun run build`: build the production app.
- `bun run preview`: preview the production build.
- `bun run storybook`: start the component explorer on port 6006.
- `bun run build-storybook`: build the static Storybook review artifact.
- `bun run db:generate`: generate Drizzle migrations from the server schema.
- `bun run db:check`: validate migration consistency.
@ -51,4 +53,10 @@ with an explicit migration error.
The SvelteKit build uses the Node adapter, and the current persistence runtime
is Bun because the MVP SQLite driver is `bun:sqlite`. Later issues add the
component system, Storybook, seed data expansion, and rendering pipeline.
seed data expansion and rendering pipeline.
## Storybook
Storybook covers the reusable UI components with generic fixtures only. Stories
must not import environment-specific dashboard content; the presentation layer
accepts labels, values, icons, status, and links through typed props.

337
bun.lock
View file

@ -12,12 +12,17 @@
"drizzle-orm": "^0.45.2",
},
"devDependencies": {
"@storybook/addon-a11y": "^10.4.6",
"@storybook/addon-svelte-csf": "^5.1.2",
"@storybook/addon-vitest": "^10.4.6",
"@storybook/sveltekit": "^10.4.6",
"@sveltejs/adapter-node": "^5.5.4",
"@sveltejs/kit": "^2.65.2",
"@sveltejs/vite-plugin-svelte": "^7.1.2",
"@types/bun": "^1.3.14",
"@types/node": "^25.9.3",
"drizzle-kit": "^0.31.10",
"storybook": "^10.4.6",
"svelte": "^5.56.3",
"svelte-check": "^4.6.0",
"typescript": "^6.0.3",
@ -27,11 +32,19 @@
},
},
"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=="],
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
"@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="],
"@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.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
"@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=="],
@ -107,7 +120,85 @@
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.5", "", { "dependencies": { "@tybys/wasm-util": "^0.10.2" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q=="],
"@oxc-project/types": ["@oxc-project/types@0.133.0", "", {}, "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA=="],
"@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.3", "", { "os": "android", "cpu": "arm" }, "sha512-eNU11A2WNizh04v3uyaJCootrHIaS0B9aHYXvAvVnPNk4xYSjMUjHnhQ6dewPN2MRYDskV85d1N0Aw0WNWhcyg=="],
"@oxc-resolver/binding-android-arm64": ["@oxc-resolver/binding-android-arm64@11.21.3", "", { "os": "android", "cpu": "arm64" }, "sha512-8Q+ZjTLvn2dIcWsrmhdrEihm7q+ag/k+mkry7Z+t0QbbHaVxXQfvH9AewyVMh/WrpEKhQ3DDgx9fYbqeCpeOEw=="],
"@oxc-resolver/binding-darwin-arm64": ["@oxc-resolver/binding-darwin-arm64@11.21.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wkh0qKZGHXVUDxFw3oA1TXnU2BDYY/r775oJflGeIr8uDPPoN2pk8gijQIzYRT6hoql/lg3+Tx/SaTn9e2/aGg=="],
"@oxc-resolver/binding-darwin-x64": ["@oxc-resolver/binding-darwin-x64@11.21.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-HbNc23FAQYbuyDV2vBWMez4u4mrsm5RAkniGZAWqr6lYZ3N4beeqIb776jzwRl8qL2zRhHVXpUj97X0QgogVzg=="],
"@oxc-resolver/binding-freebsd-x64": ["@oxc-resolver/binding-freebsd-x64@11.21.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-K6xNsTUPEUdfrn0+kbMq5nOUB5w1C5pavPQngt4TM2FpN91lP0PBe2srSpamb4d69O7h86oAi/qWX/kZNRSjkw=="],
"@oxc-resolver/binding-linux-arm-gnueabihf": ["@oxc-resolver/binding-linux-arm-gnueabihf@11.21.3", "", { "os": "linux", "cpu": "arm" }, "sha512-VcFmOpcpWX1zoEy8M58tR2M9YxM+Z9RuQhqAx5q0CTmrruaP7Gveejg75hzd/5sg5nk9G3aLALEa3hE2FsmmTQ=="],
"@oxc-resolver/binding-linux-arm-musleabihf": ["@oxc-resolver/binding-linux-arm-musleabihf@11.21.3", "", { "os": "linux", "cpu": "arm" }, "sha512-quVoxFLBy43hWaQbbDtQNRwAX5vX76mv7n64icAtQcJ3eNgVeblqmkupF/hAneNthdqSlnd1sTjb3aQSaDPaCQ=="],
"@oxc-resolver/binding-linux-arm64-gnu": ["@oxc-resolver/binding-linux-arm64-gnu@11.21.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-X0AqNZgcD07Q4V3RDK18/vYOj/HQT/FnmEFGYS2jTWqY7JO13ryE3TEs3eAIgUJhBnNkpEaiXqz3VK8M7qQhWQ=="],
"@oxc-resolver/binding-linux-arm64-musl": ["@oxc-resolver/binding-linux-arm64-musl@11.21.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-YkaQnaKYdbuaXvRt5Qd0GpbihzVnyfR6z1SpYfIUC6RTu4NF7lDKPjVkYb+jRI2gedVO2rVpN35Y6akG6ud4Lw=="],
"@oxc-resolver/binding-linux-ppc64-gnu": ["@oxc-resolver/binding-linux-ppc64-gnu@11.21.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gB9HwhrPiFqUzDeEq+y/CgAijz1YdI6BnXz5GaH2Pa9cWdutchlkGFAiAuGb/PjVQpiK6NFKzFuztxrweoit7A=="],
"@oxc-resolver/binding-linux-riscv64-gnu": ["@oxc-resolver/binding-linux-riscv64-gnu@11.21.3", "", { "os": "linux", "cpu": "none" }, "sha512-zjDWBlYk8QGv0H8dsPUWqkfjYIIjG2TvspGkzXL0eImbgxtZorA/klKeHyolevoT3Kvbi+1iMr9Lhrh7jf54Og=="],
"@oxc-resolver/binding-linux-riscv64-musl": ["@oxc-resolver/binding-linux-riscv64-musl@11.21.3", "", { "os": "linux", "cpu": "none" }, "sha512-4UfsQvacV388y1zpXL7C1x1FNYaV52JtuNRiuzrfQA2z1z6ElVrsidkGsrvQ5EgeSq1Pj7kaKqrgGkvFuxJ/tw=="],
"@oxc-resolver/binding-linux-s390x-gnu": ["@oxc-resolver/binding-linux-s390x-gnu@11.21.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-b5uH+HKH0MP5mNBYaK75SKsJbw52URqrx2LavYdq6wb0l3ExAG5niYRP9DWUNHdKilpaBVM2bXk9HNWrH3ew7Q=="],
"@oxc-resolver/binding-linux-x64-gnu": ["@oxc-resolver/binding-linux-x64-gnu@11.21.3", "", { "os": "linux", "cpu": "x64" }, "sha512-PjYlmilBpNRh2ntXNYAK3Am5w/nPfEpnU/96iNx7CI8EzAn12J4JRiec63wHJTH31nLoCNxBg/829pN+3CfG3Q=="],
"@oxc-resolver/binding-linux-x64-musl": ["@oxc-resolver/binding-linux-x64-musl@11.21.3", "", { "os": "linux", "cpu": "x64" }, "sha512-QTBAb7JuHlZ7JUEyM8UiQi2f7m/L4swBhP2TNpYIDc9Wp/wRw1G/8sl6i13aIzQAXH7LKIm294LeOHd0lQR8zA=="],
"@oxc-resolver/binding-openharmony-arm64": ["@oxc-resolver/binding-openharmony-arm64@11.21.3", "", { "os": "none", "cpu": "arm64" }, "sha512-4j1DFwjwv36ec9kds0jU/ucQ5Ha4ERO/H95BxR5JFf0kqUUAJ1kwII7XhTc1vZrkdJkvLGC9Q2MbpObpum8RBg=="],
"@oxc-resolver/binding-wasm32-wasi": ["@oxc-resolver/binding-wasm32-wasi@11.21.3", "", { "dependencies": { "@emnapi/core": "1.11.0", "@emnapi/runtime": "1.11.0", "@napi-rs/wasm-runtime": "^1.1.5" }, "cpu": "none" }, "sha512-i8oluoel5kru/j1WNrjmQSiA3GQ7wvIYVR1IwIoZtKogAhya2iub+ZKIeSIkcJOrnzQ18Tzl/F+kL3fYOxZLvA=="],
"@oxc-resolver/binding-win32-arm64-msvc": ["@oxc-resolver/binding-win32-arm64-msvc@11.21.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-M/8dw8dD6aOs+NlPJax401CZB9I7Aut84isQLgALGGwke4Afvw+/7yYhZb94yXf6t2sPLhQLmSmtSV+2FhsOWg=="],
"@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.21.3", "", { "os": "win32", "cpu": "x64" }, "sha512-H7BCt/VnS9hnmMp42eGhZ99izSCRvlnWwy/N71K1/J8QoExwY4262Z8QiEkMDtduRJrztayDxETTckmUuAVL9Q=="],
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
@ -205,6 +296,28 @@
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@storybook/addon-a11y": ["@storybook/addon-a11y@10.4.6", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^10.4.6" } }, "sha512-XCJy+f0DFOiCgUU9knRDlLDxVFI+AAQ3/wE/NF85zB9iDPPS2DwkSN+mas3zDgHt66zhN8Cq3/UiyCDUweV9Zw=="],
"@storybook/addon-svelte-csf": ["@storybook/addon-svelte-csf@5.1.2", "", { "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", "@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", "svelte": "^5.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-NpImknEb48J7yr/ArTYpvhDSvGUrgm5Nuybu9PCicjSKTACsXX7cln2R19572ORtns399RTE+t20BBOKxSPm2g=="],
"@storybook/addon-vitest": ["@storybook/addon-vitest@10.4.6", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.2" }, "peerDependencies": { "@vitest/browser": "^3.0.0 || ^4.0.0", "@vitest/browser-playwright": "^4.0.0", "@vitest/runner": "^3.0.0 || ^4.0.0", "storybook": "^10.4.6", "vitest": "^3.0.0 || ^4.0.0" }, "optionalPeers": ["@vitest/browser", "@vitest/browser-playwright", "@vitest/runner", "vitest"] }, "sha512-VvskHge0GZy86LG6kcY5Ww34z8rDV8JBxqSdUpcJVsWfIvyX6MfAbqI76LlereSyBIJGZJZsqaLwRXsQoVY+0Q=="],
"@storybook/builder-vite": ["@storybook/builder-vite@10.4.6", "", { "dependencies": { "@storybook/csf-plugin": "10.4.6", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.4.6", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-BHBtD81HiXUiDQz/CaFynLtWmm7AFUQn8VnXuHipZ8KlnUANopa4yqdVuy/Gwz8ub254uFI5NMZsW/KlgWNgNg=="],
"@storybook/csf": ["@storybook/csf@0.1.13", "", { "dependencies": { "type-fest": "^2.19.0" } }, "sha512-7xOOwCLGB3ebM87eemep89MYRFTko+D8qE7EdAAq74lgdqRR5cOUtYWJLjO2dLtP94nqoOdHJo6MdLLKzg412Q=="],
"@storybook/csf-plugin": ["@storybook/csf-plugin@10.4.6", "", { "dependencies": { "unplugin": "^2.3.5" }, "peerDependencies": { "esbuild": "*", "rollup": "*", "storybook": "^10.4.6", "vite": "*", "webpack": "*" }, "optionalPeers": ["esbuild", "rollup", "vite", "webpack"] }, "sha512-NILLxDqpA/JR/AazGWpsz+4fadJwRU4uhHephGtYpVOWnQA/DkJfKT6zpcJVq8+QA8A2zKMLX3GVKsXIrxjuDA=="],
"@storybook/global": ["@storybook/global@5.0.0", "", {}, "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ=="],
"@storybook/icons": ["@storybook/icons@2.0.2", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-KZBCpXsshAIjczYNXR/rlxEtCUX/eAbpFNwKi8bcOomrLA4t/SyPz5RF+lVPO2oZBUE4sAkt43mfJUevQDSEEw=="],
"@storybook/svelte": ["@storybook/svelte@10.4.6", "", { "dependencies": { "ts-dedent": "^2.0.0", "type-fest": "^5.6.0" }, "peerDependencies": { "storybook": "^10.4.6", "svelte": "^5.0.0" } }, "sha512-U73tDy/2vgY83Zjjy7Q5Lz0ElpZZjGQHZHZd1rrD0gv1+Zas/7aTnRjRqIUrBNqHBx1S8VA5Tzp5YI0FkM1IOw=="],
"@storybook/svelte-vite": ["@storybook/svelte-vite@10.4.6", "", { "dependencies": { "@storybook/builder-vite": "10.4.6", "@storybook/svelte": "10.4.6", "magic-string": "^0.30.0", "svelte2tsx": "^0.7.44", "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.4.6", "svelte": "^5.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-zyFg12tksWb0gLK29ucyZO5oNh4veu4biMeJgAEPEALjHw8HysP7mTA4547JYprk3JzORpwSqQ7Y9TPPB0ZwFA=="],
"@storybook/sveltekit": ["@storybook/sveltekit@10.4.6", "", { "dependencies": { "@storybook/builder-vite": "10.4.6", "@storybook/svelte": "10.4.6", "@storybook/svelte-vite": "10.4.6" }, "peerDependencies": { "storybook": "^10.4.6", "svelte": "^5.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-DAbNFUr3B/XiUUUZak2TcjxCxB7eRCCgt4P0MSm9LdFTyePBdU13jybuArUfHqp6lgwqNgbFIjv1nazPjHq51g=="],
"@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.10", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-4WfKk68eTih+MiJD4fSbxN7E8kVBmTMPWHUPYjvl2N0rMs53YLTT8/YjKU5Dtnz5LqDjl7LEw4U7lXR2W3J5WA=="],
"@sveltejs/adapter-node": ["@sveltejs/adapter-node@5.5.4", "", { "dependencies": { "@rollup/plugin-commonjs": "^29.0.0", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.0", "rollup": "^4.59.0" }, "peerDependencies": { "@sveltejs/kit": "^2.4.0" } }, "sha512-45X92CXW+2J8ZUzPv3eLlKWEzINKiiGeFWTjyER4ZN4sGgNoaoeSkCY/QYNxHpPXy71QPsctwccBo9jJs0ySPQ=="],
@ -215,8 +328,16 @@
"@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@7.1.2", "", { "dependencies": { "deepmerge": "^4.3.1", "magic-string": "^0.30.21", "obug": "^2.1.0", "vitefu": "^1.1.2" }, "peerDependencies": { "svelte": "^5.46.4", "vite": "^8.0.0-beta.7 || ^8.0.0" } }, "sha512-DrUBA2UXRfDmUX/ZTiEopd3X40yavsJF1FX2RygcuIScHL7o5YX1fMvoYnDhjeJQC4weCOklirpNWlcb2NiSeA=="],
"@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "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-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="],
"@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.1", "", { "peerDependencies": { "@testing-library/dom": ">=7.21.4" } }, "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw=="],
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="],
"@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="],
"@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
@ -233,7 +354,7 @@
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
"@vitest/expect": ["@vitest/expect@4.1.9", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.9", "@vitest/utils": "4.1.9", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA=="],
"@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/mocker": ["@vitest/mocker@4.1.9", "", { "dependencies": { "@vitest/spy": "4.1.9", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw=="],
@ -243,27 +364,41 @@
"@vitest/snapshot": ["@vitest/snapshot@4.1.9", "", { "dependencies": { "@vitest/pretty-format": "4.1.9", "@vitest/utils": "4.1.9", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA=="],
"@vitest/spy": ["@vitest/spy@4.1.9", "", {}, "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA=="],
"@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="],
"@vitest/utils": ["@vitest/utils@4.1.9", "", { "dependencies": { "@vitest/pretty-format": "4.1.9", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA=="],
"@webcontainer/env": ["@webcontainer/env@1.1.1", "", {}, "sha512-6aN99yL695Hi9SuIk1oC88l9o0gmxL1nGWWQ/kNy81HigJ0FoaoTXpytCj6ItzgyCEwA9kF1wixsTuv5cjsgng=="],
"acorn": ["acorn@8.17.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg=="],
"ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="],
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
"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.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="],
"axe-core": ["axe-core@4.12.1", "", {}, "sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA=="],
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
"chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
"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=="],
@ -275,12 +410,30 @@
"cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="],
"css.escape": ["css.escape@1.5.1", "", {}, "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="],
"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.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="],
"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.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="],
"drizzle-kit": ["drizzle-kit@0.31.10", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw=="],
"drizzle-orm": ["drizzle-orm@0.45.2", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "prisma": "*", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "prisma", "sql.js", "sqlite3"] }, "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q=="],
@ -289,11 +442,15 @@
"es-module-lexer": ["es-module-lexer@2.1.0", "", {}, "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ=="],
"es-toolkit": ["es-toolkit@1.47.1", "", {}, "sha512-5RAqEwf4P4E17p+W75KLOWw/nOvKZzSQpxM32IpI2KZLaVonjTrZ0Ai5ghMaVI9eKC2p8eoQgcBdkEDgzFk6+Q=="],
"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.11", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, "peerDependencies": { "@typescript-eslint/types": "^8.2.0" }, "optionalPeers": ["@typescript-eslint/types"] }, "sha512-gPdx+I+BjYEinNMQaBXFjbaJVyoPMU4ZODg5mE+M4DqVG9VusAVHHjcBX+zqyITlI0DIARwDMMzZwAWj36dRoQ=="],
"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=="],
"estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
@ -313,12 +470,22 @@
"hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="],
"indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
"is-core-module": ["is-core-module@2.16.2", "", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="],
"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-module": ["is-module@1.0.0", "", {}, "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="],
"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=="],
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
@ -349,8 +516,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=="],
@ -359,18 +532,38 @@
"obug": ["obug@2.1.3", "", {}, "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg=="],
"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.3", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.21.3", "@oxc-resolver/binding-android-arm64": "11.21.3", "@oxc-resolver/binding-darwin-arm64": "11.21.3", "@oxc-resolver/binding-darwin-x64": "11.21.3", "@oxc-resolver/binding-freebsd-x64": "11.21.3", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.21.3", "@oxc-resolver/binding-linux-arm-musleabihf": "11.21.3", "@oxc-resolver/binding-linux-arm64-gnu": "11.21.3", "@oxc-resolver/binding-linux-arm64-musl": "11.21.3", "@oxc-resolver/binding-linux-ppc64-gnu": "11.21.3", "@oxc-resolver/binding-linux-riscv64-gnu": "11.21.3", "@oxc-resolver/binding-linux-riscv64-musl": "11.21.3", "@oxc-resolver/binding-linux-s390x-gnu": "11.21.3", "@oxc-resolver/binding-linux-x64-gnu": "11.21.3", "@oxc-resolver/binding-linux-x64-musl": "11.21.3", "@oxc-resolver/binding-openharmony-arm64": "11.21.3", "@oxc-resolver/binding-wasm32-wasi": "11.21.3", "@oxc-resolver/binding-win32-arm64-msvc": "11.21.3", "@oxc-resolver/binding-win32-x64-msvc": "11.21.3" } }, "sha512-2Mx3fKQz7+xgrBONjsxOgCGtMHOn38/HxMzW1I5efwXB5a4lRN0Vp40gYUJFBWJslcrvwoofTrqoTnLbwTd3pA=="],
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"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=="],
"postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="],
"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.2.7", "", {}, "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ=="],
"react-dom": ["react-dom@19.2.7", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.7" } }, "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ=="],
"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.11", "", { "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-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="],
"redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="],
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
"resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="],
@ -381,8 +574,16 @@
"rollup": ["rollup@4.62.0", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.62.0", "@rollup/rollup-android-arm64": "4.62.0", "@rollup/rollup-darwin-arm64": "4.62.0", "@rollup/rollup-darwin-x64": "4.62.0", "@rollup/rollup-freebsd-arm64": "4.62.0", "@rollup/rollup-freebsd-x64": "4.62.0", "@rollup/rollup-linux-arm-gnueabihf": "4.62.0", "@rollup/rollup-linux-arm-musleabihf": "4.62.0", "@rollup/rollup-linux-arm64-gnu": "4.62.0", "@rollup/rollup-linux-arm64-musl": "4.62.0", "@rollup/rollup-linux-loong64-gnu": "4.62.0", "@rollup/rollup-linux-loong64-musl": "4.62.0", "@rollup/rollup-linux-ppc64-gnu": "4.62.0", "@rollup/rollup-linux-ppc64-musl": "4.62.0", "@rollup/rollup-linux-riscv64-gnu": "4.62.0", "@rollup/rollup-linux-riscv64-musl": "4.62.0", "@rollup/rollup-linux-s390x-gnu": "4.62.0", "@rollup/rollup-linux-x64-gnu": "4.62.0", "@rollup/rollup-linux-x64-musl": "4.62.0", "@rollup/rollup-openbsd-x64": "4.62.0", "@rollup/rollup-openharmony-arm64": "4.62.0", "@rollup/rollup-win32-arm64-msvc": "4.62.0", "@rollup/rollup-win32-ia32-msvc": "4.62.0", "@rollup/rollup-win32-x64-gnu": "4.62.0", "@rollup/rollup-win32-x64-msvc": "4.62.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-nc72Wgq62I7rtDV4izT5/aaS0zxy3kttkinf9586ApknY3jZO9NYsmtc24fUckA0X7Q2v+ML4a15pdUlV5V/jA=="],
"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=="],
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="],
"semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="],
"set-cookie-parser": ["set-cookie-parser@3.1.0", "", {}, "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw=="],
"siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
@ -399,12 +600,24 @@
"std-env": ["std-env@4.1.0", "", {}, "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ=="],
"storybook": ["storybook@10.4.6", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.2", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", "@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", "open": "^10.2.0", "oxc-parser": "^0.127.0", "oxc-resolver": "^11.19.1", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "prettier": "^2 || ^3", "vite-plus": "^0.1.15" }, "optionalPeers": ["@types/react", "prettier", "vite-plus"], "bin": "./dist/bin/dispatcher.js" }, "sha512-6wkA6LxfDSSilloITsrFOJfsnw0mDUP2h8Ls+lRt8oRsudtz2RWFhLv+Toiwg6NW7hUpdTDc2hzR7DztJid6+A=="],
"strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="],
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
"svelte": ["svelte@5.56.3", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.10", "@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.11", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-w7JvrM5IFl5cmfbY0TLik9o7mjRUJmRMhOR51tBPu708Gr/MjbGs7VnJnr/B0CaXeI4vtnOh7RKxDr0cwhMdDA=="],
"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.6.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "@sveltejs/load-config": "0.1.1", "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-KhVnDFDSid57mmZtHz8gfW8AAGylOZ0vPnOIzVmAL+urzwK8sBYXRss953gD8T0OdgAQ11mdWhE6uadmtOz8TQ=="],
"svelte2tsx": ["svelte2tsx@0.7.56", "", { "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-NTvqqL+goYlW8gWNajk81L07+uu7jw5V2m1Az5MZbYm3GEydcHXh+uTrLHM9SuGuaqCtF90vlMXkOVBotfH94g=="],
"tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="],
"tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
"tinyexec": ["tinyexec@1.2.4", "", {}, "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg=="],
@ -413,34 +626,86 @@
"tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="],
"tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="],
"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=="],
"tsx": ["tsx@4.22.4", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg=="],
"type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="],
"typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
"undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
"unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="],
"use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
"vite": ["vite@8.0.16", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.15", "rolldown": "1.0.3", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw=="],
"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=="],
"vitest": ["vitest@4.1.9", "", { "dependencies": { "@vitest/expect": "4.1.9", "@vitest/mocker": "4.1.9", "@vitest/pretty-format": "4.1.9", "@vitest/runner": "4.1.9", "@vitest/snapshot": "4.1.9", "@vitest/spy": "4.1.9", "@vitest/utils": "4.1.9", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.9", "@vitest/browser-preview": "4.1.9", "@vitest/browser-webdriverio": "4.1.9", "@vitest/coverage-istanbul": "4.1.9", "@vitest/coverage-v8": "4.1.9", "@vitest/ui": "4.1.9", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "./vitest.mjs" } }, "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ=="],
"webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
"why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
"ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="],
"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=="],
"@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
"@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=="],
"@rolldown/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="],
"@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
"@rollup/plugin-commonjs/is-reference": ["is-reference@1.2.1", "", { "dependencies": { "@types/estree": "*" } }, "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ=="],
"@storybook/svelte/type-fest": ["type-fest@5.7.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg=="],
"@storybook/svelte-vite/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"@testing-library/dom/aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="],
"@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="],
"@vitest/expect/@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=="],
"@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="],
"@vitest/mocker/@vitest/spy": ["@vitest/spy@4.1.9", "", {}, "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA=="],
"@vitest/mocker/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
"rolldown/@oxc-project/types": ["@oxc-project/types@0.133.0", "", {}, "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA=="],
"storybook/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
"svelte/esrap": ["esrap@2.2.11", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, "peerDependencies": { "@typescript-eslint/types": "^8.2.0" }, "optionalPeers": ["@typescript-eslint/types"] }, "sha512-gPdx+I+BjYEinNMQaBXFjbaJVyoPMU4ZODg5mE+M4DqVG9VusAVHHjcBX+zqyITlI0DIARwDMMzZwAWj36dRoQ=="],
"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=="],
"tsx/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
"vitest/@vitest/expect": ["@vitest/expect@4.1.9", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.9", "@vitest/utils": "4.1.9", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA=="],
"vitest/@vitest/spy": ["@vitest/spy@4.1.9", "", {}, "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="],
@ -485,6 +750,62 @@
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
"@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=="],
"@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
"storybook/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
"storybook/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="],
"storybook/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="],
"storybook/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="],
"storybook/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="],
"storybook/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="],
"storybook/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="],
"storybook/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="],
"storybook/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="],
"storybook/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="],
"storybook/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="],
"storybook/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="],
"storybook/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="],
"storybook/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="],
"storybook/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="],
"storybook/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="],
"storybook/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="],
"storybook/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="],
"storybook/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="],
"storybook/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="],
"storybook/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="],
"storybook/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="],
"storybook/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="],
"storybook/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="],
"storybook/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="],
"storybook/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="],
"tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
"tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="],
@ -536,5 +857,7 @@
"tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="],
"tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="],
"vitest/@vitest/expect/chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
}
}

View file

@ -7,6 +7,8 @@
"dev": "vite --host 0.0.0.0",
"build": "vite build",
"preview": "vite preview --host 0.0.0.0",
"storybook": "storybook dev -p 6006 --host 0.0.0.0",
"build-storybook": "storybook build",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"test": "vitest run",
"db:generate": "drizzle-kit generate",
@ -20,12 +22,17 @@
"drizzle-orm": "^0.45.2"
},
"devDependencies": {
"@storybook/addon-a11y": "^10.4.6",
"@storybook/addon-svelte-csf": "^5.1.2",
"@storybook/addon-vitest": "^10.4.6",
"@storybook/sveltekit": "^10.4.6",
"@sveltejs/adapter-node": "^5.5.4",
"@sveltejs/kit": "^2.65.2",
"@sveltejs/vite-plugin-svelte": "^7.1.2",
"@types/bun": "^1.3.14",
"@types/node": "^25.9.3",
"drizzle-kit": "^0.31.10",
"storybook": "^10.4.6",
"svelte": "^5.56.3",
"svelte-check": "^4.6.0",
"typescript": "^6.0.3",

View file

@ -0,0 +1,14 @@
<script lang="ts">
import type { UiSeverity } from "../types";
import StatusBadge from "./StatusBadge.svelte";
let {
label,
severity = "neutral",
}: {
label: string;
severity?: UiSeverity;
} = $props();
</script>
<StatusBadge {label} {severity} />

View file

@ -0,0 +1,85 @@
<script lang="ts">
import type { HTMLButtonAttributes } from "svelte/elements";
import IconGlyph from "./IconGlyph.svelte";
type ButtonProps = Omit<HTMLButtonAttributes, "type"> & {
label: string;
variant?: "primary" | "secondary" | "danger" | "ghost";
size?: "default" | "compact";
icon?: string;
loading?: boolean;
type?: "button" | "submit" | "reset";
};
let {
label,
variant = "primary",
size = "default",
icon,
disabled = false,
loading = false,
type = "button",
class: className = "",
...buttonProps
}: ButtonProps = $props();
</script>
<button
{...buttonProps}
class={className ? `ui-button ${className}` : "ui-button"}
data-variant={variant}
data-size={size}
data-loading={loading}
{disabled}
{type}
>
{#if icon}
<IconGlyph name={icon} size="sm" />
{/if}
<span>{loading ? "Loading" : label}</span>
</button>
<style>
.ui-button {
display: inline-grid;
grid-auto-flow: column;
gap: var(--ui-space-2);
align-items: center;
justify-content: center;
min-height: 2.5rem;
border: var(--ui-border);
background: var(--ui-color-accent);
color: var(--ui-color-canvas);
cursor: pointer;
font-size: 0.76rem;
font-weight: 850;
letter-spacing: 0;
padding: 0 var(--ui-space-4);
text-transform: uppercase;
}
.ui-button[data-size="compact"] {
min-height: 2rem;
padding-inline: var(--ui-space-3);
}
.ui-button[data-variant="secondary"] {
background: var(--ui-color-surface-raised);
color: var(--ui-color-text);
}
.ui-button[data-variant="danger"] {
background: var(--ui-color-danger);
color: var(--ui-color-text);
}
.ui-button[data-variant="ghost"] {
background: transparent;
color: var(--ui-color-muted);
}
.ui-button:disabled {
cursor: not-allowed;
opacity: 0.48;
}
</style>

View file

@ -0,0 +1,71 @@
<script lang="ts">
import type { UiModuleBlock } from "../types";
import ModuleCard from "./ModuleCard.svelte";
let {
title,
subtitle,
eyebrow,
module,
}: {
title: string;
subtitle?: string;
eyebrow?: string;
module?: UiModuleBlock;
} = $props();
const titleId = $props.id();
</script>
<header class="dashboard-header" aria-labelledby={titleId}>
<div>
{#if eyebrow}
<p>{eyebrow}</p>
{/if}
<h1 id={titleId}>{title}</h1>
{#if subtitle}
<span>{subtitle}</span>
{/if}
</div>
{#if module}
<ModuleCard {module} />
{/if}
</header>
<style>
.dashboard-header {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: var(--ui-space-4);
align-items: start;
border-bottom: var(--ui-border);
padding: var(--ui-space-3);
}
p,
span,
h1 {
margin: 0;
text-transform: uppercase;
}
p,
span {
color: var(--ui-color-muted);
font-size: 0.78rem;
font-weight: 800;
}
h1 {
overflow-wrap: anywhere;
font-family: var(--ui-font-display);
font-size: clamp(2.45rem, 5vw, 4.2rem);
line-height: 0.85;
}
@media (max-width: 780px) {
.dashboard-header {
grid-template-columns: 1fr;
}
}
</style>

View file

@ -0,0 +1,8 @@
<script lang="ts">
import type { UiStatusItem } from "../types";
import FooterCell from "./FooterCell.svelte";
let { item }: { item: UiStatusItem } = $props();
</script>
<FooterCell {item} />

View file

@ -0,0 +1,30 @@
<script lang="ts">
import type { Snippet } from "svelte";
let {
children,
density = "dense",
}: {
children?: Snippet;
density?: "compact" | "dense";
} = $props();
</script>
<section class="grid-frame" data-density={density}>
{@render children?.()}
</section>
<style>
.grid-frame {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
gap: var(--ui-space-3);
padding: var(--ui-space-3);
}
.grid-frame[data-density="compact"] {
grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
gap: var(--ui-space-2);
padding: var(--ui-space-2);
}
</style>

View file

@ -0,0 +1,57 @@
<script lang="ts">
import type { HTMLButtonAttributes } from "svelte/elements";
import IconGlyph from "./IconGlyph.svelte";
type IconButtonProps = Omit<HTMLButtonAttributes, "type"> & {
icon: string;
label: string;
active?: boolean;
type?: "button" | "submit" | "reset";
};
let {
icon,
label,
active = false,
disabled = false,
type = "button",
class: className = "",
...buttonProps
}: IconButtonProps = $props();
</script>
<button
{...buttonProps}
class={className ? `icon-button ${className}` : "icon-button"}
aria-label={label}
data-active={active}
{disabled}
{type}
>
<IconGlyph name={icon} size="sm" />
</button>
<style>
.icon-button {
display: inline-grid;
width: 2.5rem;
height: 2.5rem;
place-items: center;
border: var(--ui-border);
background: rgba(2, 3, 2, 0.92);
color: var(--ui-color-muted);
cursor: pointer;
padding: 0;
}
.icon-button[data-active="true"],
.icon-button:hover {
border-color: var(--ui-color-accent);
color: var(--ui-color-accent);
}
.icon-button:disabled {
cursor: not-allowed;
opacity: 0.45;
}
</style>

View file

@ -0,0 +1,58 @@
<script lang="ts">
import { clampPercent } from "../format";
import type { UiSeverity } from "../types";
let {
value,
severity = "neutral",
label = "Progress",
}: {
value?: number;
severity?: UiSeverity;
label?: string;
} = $props();
let progress = $derived(value === undefined ? null : clampPercent(value));
</script>
<div
class="progress-meter"
data-severity={severity}
role="meter"
aria-label={label}
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow={progress ?? undefined}
data-empty={progress === null}
>
{#if progress !== null}
<span style={`--meter-progress: ${progress}%`}></span>
{/if}
</div>
<style>
.progress-meter {
height: 0.4rem;
border: var(--ui-border);
background: #030403;
}
.progress-meter span {
display: block;
width: var(--meter-progress);
height: 100%;
background: var(--ui-color-accent);
}
.progress-meter[data-severity="warning"] span {
background: var(--ui-color-warning);
}
.progress-meter[data-severity="danger"] span {
background: var(--ui-color-danger);
}
.progress-meter[data-empty="true"] {
opacity: 0.46;
}
</style>

View file

@ -0,0 +1,39 @@
<script lang="ts">
let {
orientation = "horizontal",
dense = false,
}: {
orientation?: "horizontal" | "vertical";
dense?: boolean;
} = $props();
</script>
<div
class="separator"
data-orientation={orientation}
data-dense={dense}
role="separator"
aria-orientation={orientation}
></div>
<style>
.separator {
background: var(--ui-color-line);
}
.separator[data-orientation="horizontal"] {
width: 100%;
height: 1px;
margin-block: var(--ui-space-3);
}
.separator[data-orientation="vertical"] {
width: 1px;
min-height: 3rem;
align-self: stretch;
}
.separator[data-dense="true"] {
margin-block: var(--ui-space-1);
}
</style>

View file

@ -0,0 +1,8 @@
<script lang="ts">
import type { UiServiceGroup } from "../types";
import ServicePanel from "./ServicePanel.svelte";
let { group }: { group: UiServiceGroup } = $props();
</script>
<ServicePanel {group} />

View file

@ -0,0 +1,58 @@
<script lang="ts">
import type { UiSeverity } from "../types";
let {
values = [],
severity = "neutral",
}: {
values?: number[];
severity?: UiSeverity;
} = $props();
let points = $derived(toPoints(values));
</script>
<svg class="sparkline" data-severity={severity} viewBox="0 0 100 24" aria-hidden="true">
{#if points}
<polyline {points} />
{/if}
</svg>
<style>
.sparkline {
width: 100%;
height: 1.5rem;
color: var(--ui-color-accent);
}
.sparkline[data-severity="warning"] {
color: var(--ui-color-warning);
}
.sparkline[data-severity="danger"] {
color: var(--ui-color-danger);
}
polyline {
fill: none;
stroke: currentColor;
stroke-linecap: square;
stroke-linejoin: miter;
stroke-width: 2;
}
</style>
<script lang="ts" module>
function toPoints(values: number[]): string {
if (values.length === 0) return "";
const max = Math.max(...values, 1);
const step = values.length > 1 ? 100 / (values.length - 1) : 100;
return values
.map((value, index) => {
const x = index * step;
const y = 24 - (Math.max(0, value) / max) * 22;
return `${x.toFixed(2)},${y.toFixed(2)}`;
})
.join(" ");
}
</script>

View file

@ -0,0 +1,67 @@
<script lang="ts">
import type { UiSeverity } from "../types";
import IconGlyph from "./IconGlyph.svelte";
let {
title,
detail,
icon = "mdi:information-outline",
severity = "neutral",
}: {
title: string;
detail?: string;
icon?: string;
severity?: UiSeverity;
} = $props();
</script>
<section class="system-state" data-severity={severity}>
<IconGlyph name={icon} size="lg" />
<div>
<h2>{title}</h2>
{#if detail}
<p>{detail}</p>
{/if}
</div>
</section>
<style>
.system-state {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
gap: var(--ui-space-3);
align-items: center;
border: var(--ui-border);
background: rgba(6, 8, 7, 0.82);
padding: var(--ui-space-4);
}
.system-state[data-severity="warning"] {
color: var(--ui-color-warning);
}
.system-state[data-severity="danger"],
.system-state[data-severity="unavailable"] {
color: var(--ui-color-danger);
}
.system-state[data-severity="loading"] {
color: var(--ui-color-accent);
}
h2,
p {
margin: 0;
text-transform: uppercase;
}
h2 {
font-size: 1rem;
}
p {
margin-top: var(--ui-space-1);
color: var(--ui-color-muted);
font-size: 0.72rem;
}
</style>

View file

@ -0,0 +1,8 @@
<script lang="ts">
import type { UiTelemetryCard } from "../types";
import TelemetryGrid from "./TelemetryGrid.svelte";
let { cards }: { cards: UiTelemetryCard[] } = $props();
</script>
<TelemetryGrid {cards} />

View file

@ -0,0 +1,8 @@
<script lang="ts">
import type { UiModuleBlock } from "../types";
import ModuleCard from "./ModuleCard.svelte";
let { module }: { module: UiModuleBlock } = $props();
</script>
<ModuleCard {module} />

View file

@ -1,7 +1,9 @@
import { render } from "svelte/server";
import { describe, expect, test } from "vitest";
import Button from "./Button.svelte";
import DashboardFrame from "./DashboardFrame.svelte";
import FooterCell from "./FooterCell.svelte";
import IconButton from "./IconButton.svelte";
import ServiceRow from "./ServiceRow.svelte";
import TelemetryCard from "./TelemetryCard.svelte";
import { dashboardPreviewFixtures } from "../fixtures";
@ -94,4 +96,31 @@ describe("dashboard UI components", () => {
expect(withoutProgress.body).not.toContain("telemetry-card__bar");
expect(withProgress.body).toContain("--metric-progress: 42%");
});
test("base button controls forward native attributes", () => {
const button = render(Button, {
props: {
label: "Refresh",
id: "refresh-action",
class: "custom-action",
"aria-controls": "refresh-target",
},
});
const iconButton = render(IconButton, {
props: {
icon: "mdi:refresh",
label: "Refresh status",
id: "refresh-icon-action",
class: "custom-icon-action",
"aria-expanded": "false",
},
});
expect(button.body).toContain("id=\"refresh-action\"");
expect(button.body).toContain("class=\"ui-button custom-action ");
expect(button.body).toContain("aria-controls=\"refresh-target\"");
expect(iconButton.body).toContain("id=\"refresh-icon-action\"");
expect(iconButton.body).toContain("class=\"icon-button custom-icon-action ");
expect(iconButton.body).toContain("aria-expanded=\"false\"");
});
});

View file

@ -1,14 +1,27 @@
export { default as Badge } from "./components/Badge.svelte";
export { default as Button } from "./components/Button.svelte";
export { default as DashboardHeader } from "./components/DashboardHeader.svelte";
export { default as DashboardFrame } from "./components/DashboardFrame.svelte";
export { default as FooterCell } from "./components/FooterCell.svelte";
export { default as FooterStatusCell } from "./components/FooterStatusCell.svelte";
export { default as GridFrame } from "./components/GridFrame.svelte";
export { default as IconGlyph } from "./components/IconGlyph.svelte";
export { default as IconButton } from "./components/IconButton.svelte";
export { default as ModuleCard } from "./components/ModuleCard.svelte";
export { default as Panel } from "./components/Panel.svelte";
export { default as ProgressMeter } from "./components/ProgressMeter.svelte";
export { default as Separator } from "./components/Separator.svelte";
export { default as ServiceGroupPanel } from "./components/ServiceGroupPanel.svelte";
export { default as ServicePanel } from "./components/ServicePanel.svelte";
export { default as ServiceRow } from "./components/ServiceRow.svelte";
export { default as Sparkline } from "./components/Sparkline.svelte";
export { default as StatusBadge } from "./components/StatusBadge.svelte";
export { default as StatusStrip } from "./components/StatusStrip.svelte";
export { default as SystemState } from "./components/SystemState.svelte";
export { default as TelemetryCard } from "./components/TelemetryCard.svelte";
export { default as TelemetryGrid } from "./components/TelemetryGrid.svelte";
export { default as TelemetryStrip } from "./components/TelemetryStrip.svelte";
export { default as WeatherModule } from "./components/WeatherModule.svelte";
export { dashboardPreviewFixtures } from "./fixtures";
export type {
UiDashboardPreview,

View file

@ -0,0 +1,21 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import Badge from "../components/Badge.svelte";
const { Story } = defineMeta({
title: "Base UI/Badge",
component: Badge,
tags: ["autodocs"],
});
</script>
<Story name="Neutral" args={{ label: "Neutral", severity: "neutral" }} />
<Story name="Ok" args={{ label: "Operational", severity: "ok" }} />
<Story name="Warning" args={{ label: "Warning", severity: "warning" }} />
<Story name="Danger" args={{ label: "Incident", severity: "danger" }} />
<Story name="Stale" args={{ label: "Stale", severity: "stale" }} />
<Story name="Unavailable" args={{ label: "Unavailable", severity: "unavailable" }} />
<Story
name="Long Label"
args={{ label: "Long generic operational label", severity: "warning" }}
/>

View file

@ -0,0 +1,19 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import Button from "../components/Button.svelte";
const { Story } = defineMeta({
title: "Base UI/Button",
component: Button,
tags: ["autodocs"],
});
</script>
<Story name="Default" args={{ label: "Refresh" }} />
<Story name="Secondary" args={{ label: "Preview", variant: "secondary" }} />
<Story name="Danger" args={{ label: "Stop", variant: "danger" }} />
<Story name="Ghost Outline" args={{ label: "Inspect", variant: "ghost" }} />
<Story name="Disabled" args={{ label: "Publish", disabled: true }} />
<Story name="Loading" args={{ label: "Sync", loading: true }} />
<Story name="Icon Leading" args={{ label: "Open", icon: "mdi:open-in-new" }} />
<Story name="Compact" args={{ label: "Apply", size: "compact" }} />

View file

@ -0,0 +1,15 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import DashboardFrame from "../components/DashboardFrame.svelte";
import { emptyDashboard, fullCompositionDashboard, primaryDashboard } from "./story-data";
const { Story } = defineMeta({
title: "Composition/DashboardFrame",
component: DashboardFrame,
tags: ["autodocs"],
});
</script>
<Story name="Generic Runtime" args={{ dashboard: primaryDashboard }} />
<Story name="Dense Generic Runtime" args={{ dashboard: fullCompositionDashboard }} />
<Story name="Empty Regions" args={{ dashboard: emptyDashboard }} />

View file

@ -0,0 +1,31 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import DashboardHeader from "../components/DashboardHeader.svelte";
import { moduleBlocks } from "./story-data";
const { Story } = defineMeta({
title: "Composition/DashboardHeader",
component: DashboardHeader,
tags: ["autodocs"],
});
</script>
<Story
name="Title Subtitle Module"
args={{
eyebrow: "Command Surface",
title: "Operations Console",
subtitle: "Capacity and queue health",
module: moduleBlocks.compact,
}}
/>
<Story
name="Long Title"
args={{
eyebrow: "Preview Surface",
title: "Long Generic Dashboard Title That Must Wrap",
subtitle: "Secondary status text wraps independently",
module: moduleBlocks.long,
}}
/>
<Story name="No Module" args={{ title: "Standalone Console", subtitle: "Header without side module" }} />

View file

@ -0,0 +1,18 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import DashboardFrame from "../components/DashboardFrame.svelte";
import { fullCompositionDashboard } from "./story-data";
const { Story } = defineMeta({
title: "Composition/DashboardOnePager",
component: DashboardFrame,
tags: ["autodocs"],
});
</script>
<Story name="Desktop Generic Dashboard" args={{ dashboard: fullCompositionDashboard }} />
<Story name="Mobile Generic Dashboard" asChild>
<div style="max-width: 390px">
<DashboardFrame dashboard={fullCompositionDashboard} />
</div>
</Story>

View file

@ -0,0 +1,33 @@
<script lang="ts">
import { onMount, tick, type Snippet } from "svelte";
let {
children,
selector = "button, a[href]",
}: {
children?: Snippet;
selector?: string;
} = $props();
let root: HTMLDivElement;
onMount(async () => {
await tick();
root.querySelector<HTMLElement>(selector)?.focus();
});
</script>
<div class="focus-preview" bind:this={root}>
{@render children?.()}
</div>
<style>
.focus-preview {
display: inline-grid;
padding: var(--ui-space-2);
}
.focus-preview :global(:is(a, button)) {
box-shadow: var(--ui-focus-ring);
}
</style>

View file

@ -0,0 +1,16 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import FooterCell from "../components/FooterCell.svelte";
import { statusItems } from "./story-data";
const { Story } = defineMeta({
title: "Dashboard Primitives/FooterCell",
component: FooterCell,
tags: ["autodocs"],
});
</script>
<Story name="Label Value" args={{ item: statusItems.ok }} />
<Story name="Action Cell" args={{ item: statusItems.action }} />
<Story name="Stale" args={{ item: statusItems.stale }} />
<Story name="Long Value" args={{ item: statusItems.long }} />

View file

@ -0,0 +1,16 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import FooterStatusCell from "../components/FooterStatusCell.svelte";
import { statusItems } from "./story-data";
const { Story } = defineMeta({
title: "Dashboard Primitives/FooterStatusCell",
component: FooterStatusCell,
tags: ["autodocs"],
});
</script>
<Story name="Label Value" args={{ item: statusItems.ok }} />
<Story name="Action Cell" args={{ item: statusItems.action }} />
<Story name="Stale" args={{ item: statusItems.stale }} />
<Story name="Long Value" args={{ item: statusItems.long }} />

View file

@ -0,0 +1,37 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import GridFrame from "../components/GridFrame.svelte";
import SystemState from "../components/SystemState.svelte";
const { Story } = defineMeta({
title: "Dashboard Primitives/GridFrame",
component: GridFrame,
tags: ["autodocs"],
});
</script>
<Story name="Desktop Grid" asChild>
<GridFrame>
<SystemState title="Cell One" />
<SystemState title="Cell Two" />
<SystemState title="Cell Three" />
<SystemState title="Cell Four" />
</GridFrame>
</Story>
<Story name="Tablet Wrap" asChild>
<div style="max-width: 760px">
<GridFrame density="dense">
<SystemState title="Cell One" />
<SystemState title="Cell Two" />
<SystemState title="Cell Three" />
</GridFrame>
</div>
</Story>
<Story name="Mobile Stack" asChild>
<div style="max-width: 390px">
<GridFrame density="compact">
<SystemState title="Cell One" />
<SystemState title="Cell Two" />
</GridFrame>
</div>
</Story>

View file

@ -0,0 +1,22 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import IconButton from "../components/IconButton.svelte";
import FocusPreview from "./FocusPreview.svelte";
const { Story } = defineMeta({
title: "Base UI/IconButton",
component: IconButton,
tags: ["autodocs"],
});
</script>
<Story name="Refresh" args={{ icon: "mdi:refresh", label: "Refresh" }} />
<Story name="Open" args={{ icon: "mdi:open-in-new", label: "Open" }} />
<Story name="Inspect" args={{ icon: "mdi:magnify-scan", label: "Inspect" }} />
<Story name="Disabled" args={{ icon: "mdi:refresh", label: "Disabled", disabled: true }} />
<Story name="Active" args={{ icon: "mdi:refresh", label: "Active", active: true }} />
<Story name="Focus Visible" asChild>
<FocusPreview>
<IconButton icon="mdi:keyboard" label="Focus visible" />
</FocusPreview>
</Story>

View file

@ -0,0 +1,21 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import IconGlyph from "../components/IconGlyph.svelte";
const { Story } = defineMeta({
title: "Base UI/IconGlyph",
component: IconGlyph,
tags: ["autodocs"],
});
</script>
<Story name="Valid Icon" args={{ name: "mdi:server-network" }} />
<Story name="Missing Icon Fallback" args={{ name: undefined }} />
<Story name="Small" args={{ name: "mdi:radar", size: "sm" }} />
<Story name="Medium" args={{ name: "mdi:radar", size: "md" }} />
<Story name="Large" args={{ name: "mdi:radar", size: "lg" }} />
<Story name="Severity Color" asChild>
<div style="color: var(--ui-color-warning)">
<IconGlyph name="mdi:alert-outline" size="lg" />
</div>
</Story>

View file

@ -0,0 +1,15 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import ModuleCard from "../components/ModuleCard.svelte";
import { moduleBlocks } from "./story-data";
const { Story } = defineMeta({
title: "Dashboard Primitives/ModuleCard",
component: ModuleCard,
tags: ["autodocs"],
});
</script>
<Story name="Compact Summary" args={{ module: moduleBlocks.compact }} />
<Story name="Unavailable" args={{ module: moduleBlocks.unavailable }} />
<Story name="Long Value" args={{ module: moduleBlocks.long }} />

View file

@ -0,0 +1,42 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import Panel from "../components/Panel.svelte";
import SystemState from "../components/SystemState.svelte";
const { Story } = defineMeta({
title: "Dashboard Primitives/Panel",
component: Panel,
tags: ["autodocs"],
});
</script>
<Story name="Default" asChild>
<Panel title="Generic Panel">
<p>Reusable panel content.</p>
</Panel>
</Story>
<Story name="Dense" asChild>
<Panel title="Dense Panel" density="compact">
<p>Compact repeated content.</p>
</Panel>
</Story>
<Story name="Warning" asChild>
<Panel title="Warning Panel">
<SystemState title="Warning" detail="Generic warning state" severity="warning" />
</Panel>
</Story>
<Story name="Danger" asChild>
<Panel title="Danger Panel">
<SystemState title="Danger" detail="Generic danger state" severity="danger" />
</Panel>
</Story>
<Story name="Empty" asChild>
<Panel title="Empty Panel">
<SystemState title="Empty" detail="No generic records available" severity="neutral" />
</Panel>
</Story>
<Story name="Nested Content Guard" asChild>
<Panel title="Outer Panel">
<SystemState title="Nested content" detail="No nested card styling required" />
</Panel>
</Story>

View file

@ -0,0 +1,16 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import ProgressMeter from "../components/ProgressMeter.svelte";
const { Story } = defineMeta({
title: "Dashboard Primitives/ProgressMeter",
component: ProgressMeter,
tags: ["autodocs"],
});
</script>
<Story name="Low" args={{ value: 12, label: "Low progress" }} />
<Story name="Normal" args={{ value: 63, severity: "ok", label: "Normal progress" }} />
<Story name="Warn" args={{ value: 76, severity: "warning", label: "Warning progress" }} />
<Story name="Danger" args={{ value: 94, severity: "danger", label: "Danger progress" }} />
<Story name="Empty" args={{ value: undefined, label: "Missing progress" }} />

View file

@ -0,0 +1,21 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import Separator from "../components/Separator.svelte";
import Panel from "../components/Panel.svelte";
const { Story } = defineMeta({
title: "Base UI/Separator",
component: Separator,
tags: ["autodocs"],
});
</script>
<Story name="Horizontal" args={{ orientation: "horizontal" }} />
<Story name="Vertical" args={{ orientation: "vertical" }} />
<Story name="Dense Panel Use" asChild>
<Panel title="Dense Panel">
<p>Generic section one</p>
<Separator dense />
<p>Generic section two</p>
</Panel>
</Story>

View file

@ -0,0 +1,16 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import ServiceGroupPanel from "../components/ServiceGroupPanel.svelte";
import { serviceGroups } from "./story-data";
const { Story } = defineMeta({
title: "Dashboard Primitives/ServiceGroupPanel",
component: ServiceGroupPanel,
tags: ["autodocs"],
});
</script>
<Story name="Short List" args={{ group: serviceGroups.short }} />
<Story name="Long List" args={{ group: serviceGroups.long }} />
<Story name="Empty Group" args={{ group: serviceGroups.empty }} />
<Story name="Mixed Statuses" args={{ group: serviceGroups.mixed }} />

View file

@ -0,0 +1,16 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import ServicePanel from "../components/ServicePanel.svelte";
import { serviceGroups } from "./story-data";
const { Story } = defineMeta({
title: "Dashboard Primitives/ServicePanel",
component: ServicePanel,
tags: ["autodocs"],
});
</script>
<Story name="Short List" args={{ group: serviceGroups.short }} />
<Story name="Long List" args={{ group: serviceGroups.long }} />
<Story name="Empty Group" args={{ group: serviceGroups.empty }} />
<Story name="Mixed Statuses" args={{ group: serviceGroups.mixed }} />

View file

@ -0,0 +1,24 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import ServiceRow from "../components/ServiceRow.svelte";
import FocusPreview from "./FocusPreview.svelte";
import { serviceRows } from "./story-data";
const { Story } = defineMeta({
title: "Dashboard Primitives/ServiceRow",
component: ServiceRow,
tags: ["autodocs"],
});
</script>
<Story name="Normal" args={{ service: serviceRows.normal }} />
<Story name="Down" args={{ service: serviceRows.down }} />
<Story name="Degraded" args={{ service: serviceRows.degraded }} />
<Story name="Stale" args={{ service: serviceRows.stale }} />
<Story name="No Link" args={{ service: serviceRows.noLink }} />
<Story name="Long Title Description" args={{ service: serviceRows.long }} />
<Story name="Keyboard Focus" asChild>
<FocusPreview selector="a">
<ServiceRow service={serviceRows.normal} />
</FocusPreview>
</Story>

View file

@ -0,0 +1,16 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import Sparkline from "../components/Sparkline.svelte";
const { Story } = defineMeta({
title: "Dashboard Primitives/Sparkline",
component: Sparkline,
tags: ["autodocs"],
});
</script>
<Story name="Flat" args={{ values: [20, 20, 20, 20, 20, 20] }} />
<Story name="Rising" args={{ values: [4, 12, 18, 28, 38, 54], severity: "ok" }} />
<Story name="Spiky" args={{ values: [8, 50, 12, 64, 18, 72], severity: "warning" }} />
<Story name="Missing Data" args={{ values: [] }} />
<Story name="Stale" args={{ values: [32, 31, 31, 30, 30, 30], severity: "stale" }} />

View file

@ -0,0 +1,18 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import StatusBadge from "../components/StatusBadge.svelte";
const { Story } = defineMeta({
title: "Base UI/StatusBadge",
component: StatusBadge,
tags: ["autodocs"],
});
</script>
<Story name="Neutral" args={{ label: "Neutral" }} />
<Story name="Ok" args={{ label: "Operational", severity: "ok" }} />
<Story name="Warning" args={{ label: "Warning", severity: "warning" }} />
<Story name="Danger" args={{ label: "Danger", severity: "danger" }} />
<Story name="Stale" args={{ label: "Stale", severity: "stale" }} />
<Story name="Unavailable" args={{ label: "Unavailable", severity: "unavailable" }} />
<Story name="Long Label" args={{ label: "Long Generic Badge Label", severity: "neutral" }} />

View file

@ -0,0 +1,27 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import StatusStrip from "../components/StatusStrip.svelte";
import { mixedStatusStrip, statusItems } from "./story-data";
const { Story } = defineMeta({
title: "Dashboard Primitives/StatusStrip",
component: StatusStrip,
tags: ["autodocs"],
});
</script>
<Story
name="All Operational"
args={{
items: [
{ ...statusItems.ok, id: "status-ok-a" },
{ ...statusItems.ok, id: "status-ok-b", label: "Queue" },
{ ...statusItems.ok, id: "status-ok-c", label: "Sync" },
],
}}
/>
<Story name="Degraded" args={{ items: [statusItems.ok, statusItems.degraded, statusItems.stale] }} />
<Story name="Incident" args={{ items: [statusItems.ok, statusItems.incident, statusItems.degraded] }} />
<Story name="Syncing" args={{ items: [statusItems.syncing, statusItems.ok, statusItems.action] }} />
<Story name="Stale" args={{ items: [statusItems.stale, statusItems.long] }} />
<Story name="Mixed Compact" args={{ items: mixedStatusStrip, compact: true }} />

View file

@ -0,0 +1,16 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import SystemState from "../components/SystemState.svelte";
const { Story } = defineMeta({
title: "Dashboard Primitives/SystemState",
component: SystemState,
tags: ["autodocs"],
});
</script>
<Story name="Loading" args={{ title: "Loading", detail: "Waiting for records", severity: "loading", icon: "mdi:progress-clock" }} />
<Story name="Empty" args={{ title: "Empty", detail: "No generic records available", icon: "mdi:tray" }} />
<Story name="Error" args={{ title: "Error", detail: "Generic request failed", severity: "danger", icon: "mdi:alert-octagon-outline" }} />
<Story name="Unavailable" args={{ title: "Unavailable", detail: "External signal unavailable", severity: "unavailable", icon: "mdi:cloud-off-outline" }} />
<Story name="Validation Failure" args={{ title: "Validation Failure", detail: "Document did not match schema", severity: "warning", icon: "mdi:file-alert-outline" }} />

View file

@ -0,0 +1,21 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import TelemetryCard from "../components/TelemetryCard.svelte";
import { telemetryCards } from "./story-data";
const { Story } = defineMeta({
title: "Dashboard Primitives/TelemetryCard",
component: TelemetryCard,
tags: ["autodocs"],
});
</script>
<Story name="Percent" args={{ card: telemetryCards.percent }} />
<Story name="Bytes" args={{ card: telemetryCards.bytes }} />
<Story name="Temperature" args={{ card: telemetryCards.temperature }} />
<Story name="Latency" args={{ card: telemetryCards.latency }} />
<Story name="No Data" args={{ card: telemetryCards.unavailable }} />
<Story name="Stale" args={{ card: telemetryCards.stale }} />
<Story name="Warning" args={{ card: telemetryCards.latency }} />
<Story name="Danger" args={{ card: telemetryCards.danger }} />
<Story name="Long Source Label" args={{ card: telemetryCards.long }} />

View file

@ -0,0 +1,15 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import TelemetryGrid from "../components/TelemetryGrid.svelte";
import { eightTelemetryCards, sixteenTelemetryCards } from "./story-data";
const { Story } = defineMeta({
title: "Dashboard Primitives/TelemetryGrid",
component: TelemetryGrid,
tags: ["autodocs"],
});
</script>
<Story name="Eight Card Density" args={{ cards: eightTelemetryCards }} />
<Story name="Sixteen Card Density" args={{ cards: sixteenTelemetryCards }} />
<Story name="Empty" args={{ cards: [] }} />

View file

@ -0,0 +1,15 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import TelemetryStrip from "../components/TelemetryStrip.svelte";
import { eightTelemetryCards, sixteenTelemetryCards } from "./story-data";
const { Story } = defineMeta({
title: "Composition/TelemetryStrip",
component: TelemetryStrip,
tags: ["autodocs"],
});
</script>
<Story name="Eight Card Density" args={{ cards: eightTelemetryCards }} />
<Story name="Sixteen Card Density" args={{ cards: sixteenTelemetryCards }} />
<Story name="Mixed Severity Wrap" args={{ cards: eightTelemetryCards }} />

View file

@ -0,0 +1,16 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import WeatherModule from "../components/WeatherModule.svelte";
import { moduleBlocks } from "./story-data";
const { Story } = defineMeta({
title: "Dashboard Primitives/WeatherModule",
component: WeatherModule,
tags: ["autodocs"],
});
</script>
<Story name="Compact Summary" args={{ module: moduleBlocks.compact }} />
<Story name="Unavailable" args={{ module: moduleBlocks.unavailable }} />
<Story name="Long Location Value" args={{ module: moduleBlocks.long }} />
<Story name="Generic Module Variant" args={{ module: { ...moduleBlocks.compact, title: "Signal Module", icon: "mdi:radar" } }} />

View file

@ -0,0 +1,258 @@
import { dashboardPreviewFixtures } from "../fixtures";
import type {
UiDashboardPreview,
UiModuleBlock,
UiServiceGroup,
UiServiceRow,
UiSeverity,
UiStatusItem,
UiTelemetryCard,
} from "../types";
export const primaryDashboard = dashboardPreviewFixtures.primary;
export const secondaryDashboard = dashboardPreviewFixtures.secondary;
export const emptyDashboard: UiDashboardPreview = {
eyebrow: "Preview Surface",
title: "Empty Console",
subtitle: "No records available",
telemetry: [],
modules: [],
serviceGroups: [],
statusItems: [],
};
export const moduleBlocks: Record<string, UiModuleBlock> = {
compact: {
id: "module-compact",
title: "Local Node",
value: "21.4 C",
detail: "static sample",
icon: "mdi:weather-partly-cloudy",
severity: "ok",
},
unavailable: {
id: "module-unavailable",
title: "External Signal",
value: "n/a",
detail: "fallback value",
icon: "mdi:cloud-off-outline",
severity: "unavailable",
},
long: {
id: "module-long",
title: "Regional Aggregate Signal",
value: "manual review queued",
detail: "long generic label wraps without layout shift",
icon: "mdi:map-marker-radius-outline",
severity: "warning",
},
};
export const telemetryCards: Record<string, UiTelemetryCard> = {
percent: {
id: "telemetry-percent",
label: "Capacity Used",
value: { kind: "percent", value: 82 },
progress: 82,
icon: "mdi:gauge",
severity: "ok",
detail: "static sample",
sparkline: [24, 36, 28, 44, 52, 82],
},
bytes: {
id: "telemetry-bytes",
label: "Data Volume",
value: { kind: "bytes", value: 982451653, precision: 1 },
icon: "mdi:database",
severity: "neutral",
detail: "static sample",
sparkline: [8, 12, 20, 18, 24, 29],
},
temperature: {
id: "telemetry-temperature",
label: "Node Temperature",
value: { kind: "temperature", value: 21.4, precision: 1 },
icon: "mdi:thermometer",
severity: "neutral",
detail: "static sample",
sparkline: [18, 20, 19, 21, 20, 21.4],
},
latency: {
id: "telemetry-latency",
label: "Median Latency",
value: { kind: "latency", value: 87 },
icon: "mdi:timer-outline",
severity: "warning",
detail: "static sample",
sparkline: [45, 51, 72, 63, 80, 87],
},
unavailable: {
id: "telemetry-unavailable",
label: "Remote Signal",
value: { kind: "text", value: "n/a" },
icon: "mdi:cloud-off-outline",
severity: "unavailable",
detail: "fallback value",
},
stale: {
id: "telemetry-stale",
label: "Sync Age",
value: { kind: "text", value: "stale" },
icon: "mdi:clock-alert-outline",
severity: "stale",
detail: "cached sample",
sparkline: [22, 22, 22, 22, 22, 22],
},
danger: {
id: "telemetry-danger",
label: "Error Budget",
value: { kind: "percent", value: 7 },
progress: 7,
icon: "mdi:alert-octagon-outline",
severity: "danger",
detail: "threshold breached",
sparkline: [56, 41, 34, 20, 13, 7],
},
loading: {
id: "telemetry-loading",
label: "Pending Refresh",
value: { kind: "text", value: "sync" },
icon: "mdi:progress-clock",
severity: "loading",
detail: "waiting for update",
},
long: {
id: "telemetry-long",
label: "Very Long Generic Source Label That Must Wrap",
value: { kind: "percent", value: 63 },
progress: 63,
icon: "mdi:chart-box-outline",
severity: "neutral",
detail: "long source label",
sparkline: [14, 20, 28, 31, 49, 63],
},
};
export const serviceRows: Record<string, UiServiceRow> = {
normal: row("service-normal", "Ingest", "Event intake pipeline", "mdi:arrow-collapse-down", "ok", "1.252 ms", {
href: "#ingest",
label: "Open ingest",
}),
down: row("service-down", "Replica", "Replica traffic cell", "mdi:access-point-off", "danger", "down"),
degraded: row("service-degraded", "Scheduler", "Timed job coordinator", "mdi:calendar-clock", "warning", "1.487 ms"),
stale: row("service-stale", "Archive", "Cold storage transfer", "mdi:archive-clock", "stale", "paused"),
noLink: row("service-no-link", "Policy", "Rules evaluation", "mdi:shield-check", "neutral", "manual"),
long: row(
"service-long",
"Long Generic Service Name That Wraps Cleanly",
"Long generic service description with enough detail to exercise wrapping in constrained panels",
"mdi:text-box-search-outline",
"warning",
"review queued",
),
};
export const serviceGroups: Record<string, UiServiceGroup> = {
short: {
id: "group-short",
title: "Short List",
services: [serviceRows.normal, serviceRows.degraded],
},
long: {
id: "group-long",
title: "Long List",
services: [
serviceRows.normal,
serviceRows.degraded,
serviceRows.stale,
serviceRows.down,
serviceRows.noLink,
serviceRows.long,
],
},
empty: {
id: "group-empty",
title: "Empty Group",
services: [],
},
mixed: {
id: "group-mixed",
title: "Mixed Statuses",
services: [serviceRows.normal, serviceRows.down, serviceRows.degraded, serviceRows.stale],
summary: [
{ id: "summary-ok", label: "Ok", value: "3", severity: "ok" },
{ id: "summary-warn", label: "Warn", value: "1", severity: "warning" },
{ id: "summary-down", label: "Down", value: "1", severity: "danger" },
],
},
};
export const statusItems: Record<string, UiStatusItem> = {
ok: { id: "status-ok", label: "System", value: "Operational", severity: "ok" },
degraded: { id: "status-degraded", label: "Routing", value: "Degraded", severity: "warning" },
incident: { id: "status-incident", label: "Incident", value: "Active", severity: "danger" },
syncing: { id: "status-syncing", label: "Refresh", value: "Syncing", severity: "loading" },
stale: { id: "status-stale", label: "Last Sync", value: "18 minutes ago", severity: "stale" },
action: {
id: "status-action",
label: "Action",
value: "Inspect",
severity: "neutral",
link: { href: "#inspect", label: "Inspect status" },
},
long: {
id: "status-long",
label: "Long Generic Status Label",
value: "long generic status value wraps",
severity: "neutral",
},
};
export const eightTelemetryCards = [
telemetryCards.percent,
telemetryCards.bytes,
telemetryCards.temperature,
telemetryCards.latency,
telemetryCards.unavailable,
telemetryCards.stale,
telemetryCards.danger,
telemetryCards.loading,
];
export const sixteenTelemetryCards = Array.from({ length: 16 }, (_, index) => {
const card = eightTelemetryCards[index % eightTelemetryCards.length];
return {
...card,
id: `${card.id}-${index}`,
label: `${card.label} ${index + 1}`,
};
});
export const mixedStatusStrip = [
statusItems.ok,
statusItems.degraded,
statusItems.incident,
statusItems.syncing,
statusItems.stale,
];
export const fullCompositionDashboard: UiDashboardPreview = {
...primaryDashboard,
telemetry: eightTelemetryCards,
modules: [moduleBlocks.compact, moduleBlocks.unavailable],
serviceGroups: [serviceGroups.mixed, serviceGroups.short, serviceGroups.long],
statusItems: mixedStatusStrip,
};
function row(
id: string,
label: string,
description: string,
icon: string,
severity: UiSeverity,
detail: string,
link?: UiServiceRow["link"],
): UiServiceRow {
return { id, label, description, icon, severity, detail, link };
}

View file

@ -0,0 +1,88 @@
import { existsSync, readdirSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
const root = process.cwd();
const componentsDir = join(root, "src/lib/ui/components");
const storiesDir = join(root, "src/lib/ui/stories");
const requiredStoryFiles = [
"Badge.stories.svelte",
"Button.stories.svelte",
"DashboardFrame.stories.svelte",
"DashboardHeader.stories.svelte",
"DashboardOnePager.stories.svelte",
"FooterCell.stories.svelte",
"FooterStatusCell.stories.svelte",
"GridFrame.stories.svelte",
"IconButton.stories.svelte",
"IconGlyph.stories.svelte",
"ModuleCard.stories.svelte",
"Panel.stories.svelte",
"ProgressMeter.stories.svelte",
"Separator.stories.svelte",
"ServiceGroupPanel.stories.svelte",
"ServicePanel.stories.svelte",
"ServiceRow.stories.svelte",
"Sparkline.stories.svelte",
"StatusBadge.stories.svelte",
"StatusStrip.stories.svelte",
"SystemState.stories.svelte",
"TelemetryCard.stories.svelte",
"TelemetryGrid.stories.svelte",
"TelemetryStrip.stories.svelte",
"WeatherModule.stories.svelte",
] as const;
const forbiddenStoryContent = [
"dimension lab",
"dimensionlab",
"vince",
"homepage",
] as const;
describe("Storybook inventory", () => {
test("exposes scripts for local and static Storybook review", () => {
const packageJson = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as {
scripts?: Record<string, string>;
};
expect(packageJson.scripts?.storybook).toBeTypeOf("string");
expect(packageJson.scripts?.storybook).toContain("storybook dev");
expect(packageJson.scripts?.["build-storybook"]).toBe("storybook build");
});
test("has a story for every reusable dashboard UI component", () => {
for (const filename of requiredStoryFiles) {
expect(existsSync(join(storiesDir, filename)), `${filename} is missing`).toBe(true);
}
});
test("keeps component and story files paired as the UI inventory changes", () => {
const componentStoryFiles = readdirSync(componentsDir)
.filter((filename) => filename.endsWith(".svelte"))
.map((filename) => filename.replace(".svelte", ".stories.svelte"));
for (const filename of componentStoryFiles) {
expect(existsSync(join(storiesDir, filename)), `${filename} is missing`).toBe(true);
}
});
test("keeps Storybook fixtures generic and content-free", () => {
const storyText = readdirSync(storiesDir)
.filter((filename) => filename.endsWith(".svelte") || filename.endsWith(".ts"))
.map((filename) => readFileSync(join(storiesDir, filename), "utf8").toLowerCase())
.join("\n");
for (const forbidden of forbiddenStoryContent) {
expect(storyText).not.toContain(forbidden);
}
});
test("does not add deferred form/navigation primitives", () => {
for (const component of ["Input", "ToggleGroup", "ScrollArea"]) {
expect(existsSync(join(root, `src/lib/ui/components/${component}.svelte`))).toBe(false);
expect(existsSync(join(storiesDir, `${component}.stories.svelte`))).toBe(false);
}
});
});