From 4c5b184474e00e1b54d72ac57a40757135ed0202 Mon Sep 17 00:00:00 2001 From: Prabhat Sachdeva Date: Fri, 20 Feb 2026 02:19:32 +0530 Subject: [PATCH] Port frontend from Svelte 5 to React 19 + Vite + Tailwind + Zustand Replace the SvelteKit frontend with a React 19 SPA while keeping the Rust WASM engine untouched. - React 19 + Vite 6 + Tailwind CSS v4 + Zustand 5 - Split 1043-line StorageControlPanel into 7 focused components - Shared useCanvas hook eliminates canvas boilerplate duplication - Design tokens via @theme instead of hardcoded colors - ARIA labels and semantic roles for accessibility - Error boundary on WASM initialization - Netlify config updated for Vite dist/ output --- .gitignore | 1 + frontend/README.md | 42 - frontend/index.html | 19 + frontend/package-lock.json | 4604 ++++++++++------- frontend/package.json | 57 +- frontend/src/App.tsx | 273 + frontend/src/app.d.ts | 13 - frontend/src/app.html | 17 - .../components/canvas/BufferPoolCanvas.tsx | 183 + frontend/src/components/canvas/DiskCanvas.tsx | 167 + .../components/canvas/PageInspectorCanvas.tsx | 328 ++ .../components/layout/CollapsibleSection.tsx | 51 + .../src/components/layout/PaneDivider.tsx | 48 + frontend/src/components/layout/PaneHeader.tsx | 38 + .../src/components/panels/ColumnEditor.tsx | 94 + .../components/panels/CreateTablePanel.tsx | 47 + .../components/panels/EngineConfigPanel.tsx | 98 + .../src/components/panels/LoadDataPanel.tsx | 120 + .../src/components/panels/QuickFillPanel.tsx | 84 + .../components/panels/ScanResultsPanel.tsx | 142 + .../components/panels/StorageControlPanel.tsx | 78 + .../panels/TableOperationsPanel.tsx | 367 ++ frontend/src/hooks/useCanvas.ts | 74 + frontend/src/hooks/useMediaQuery.ts | 17 + frontend/src/index.css | 48 + frontend/src/lib/assets/favicon.svg | 1 - .../lib/components/CollapsibleSection.svelte | 96 - .../src/lib/components/PaneDivider.svelte | 57 - frontend/src/lib/components/PaneHeader.svelte | 91 - .../storage/BufferPoolCanvas.svelte | 220 - .../lib/components/storage/DiskCanvas.svelte | 203 - .../storage/PageInspectorCanvas.svelte | 386 -- .../storage/StorageControlPanel.svelte | 1043 ---- frontend/src/lib/index.ts | 1 - frontend/src/lib/stores/layout.svelte.ts | 32 - frontend/src/lib/stores/storage.svelte.ts | 410 -- frontend/src/lib/utils.ts | 6 + frontend/src/lib/wasm/storage-engine.ts | 102 - frontend/src/lib/wasm/storage-types.ts | 356 -- frontend/src/main.tsx | 10 + frontend/src/routes/+layout.svelte | 93 - frontend/src/routes/+page.svelte | 307 -- frontend/src/stores/layout.ts | 40 + frontend/src/stores/storage.ts | 480 ++ frontend/src/vite-env.d.ts | 6 + frontend/src/wasm/storage-engine.ts | 104 + frontend/src/wasm/storage-types.ts | 449 ++ frontend/static/robots.txt | 3 - frontend/svelte.config.js | 12 - frontend/tsconfig.json | 41 +- frontend/vite.config.ts | 27 +- netlify.toml | 2 +- 52 files changed, 6220 insertions(+), 5368 deletions(-) delete mode 100644 frontend/README.md create mode 100644 frontend/index.html create mode 100644 frontend/src/App.tsx delete mode 100644 frontend/src/app.d.ts delete mode 100644 frontend/src/app.html create mode 100644 frontend/src/components/canvas/BufferPoolCanvas.tsx create mode 100644 frontend/src/components/canvas/DiskCanvas.tsx create mode 100644 frontend/src/components/canvas/PageInspectorCanvas.tsx create mode 100644 frontend/src/components/layout/CollapsibleSection.tsx create mode 100644 frontend/src/components/layout/PaneDivider.tsx create mode 100644 frontend/src/components/layout/PaneHeader.tsx create mode 100644 frontend/src/components/panels/ColumnEditor.tsx create mode 100644 frontend/src/components/panels/CreateTablePanel.tsx create mode 100644 frontend/src/components/panels/EngineConfigPanel.tsx create mode 100644 frontend/src/components/panels/LoadDataPanel.tsx create mode 100644 frontend/src/components/panels/QuickFillPanel.tsx create mode 100644 frontend/src/components/panels/ScanResultsPanel.tsx create mode 100644 frontend/src/components/panels/StorageControlPanel.tsx create mode 100644 frontend/src/components/panels/TableOperationsPanel.tsx create mode 100644 frontend/src/hooks/useCanvas.ts create mode 100644 frontend/src/hooks/useMediaQuery.ts create mode 100644 frontend/src/index.css delete mode 100644 frontend/src/lib/assets/favicon.svg delete mode 100644 frontend/src/lib/components/CollapsibleSection.svelte delete mode 100644 frontend/src/lib/components/PaneDivider.svelte delete mode 100644 frontend/src/lib/components/PaneHeader.svelte delete mode 100644 frontend/src/lib/components/storage/BufferPoolCanvas.svelte delete mode 100644 frontend/src/lib/components/storage/DiskCanvas.svelte delete mode 100644 frontend/src/lib/components/storage/PageInspectorCanvas.svelte delete mode 100644 frontend/src/lib/components/storage/StorageControlPanel.svelte delete mode 100644 frontend/src/lib/index.ts delete mode 100644 frontend/src/lib/stores/layout.svelte.ts delete mode 100644 frontend/src/lib/stores/storage.svelte.ts create mode 100644 frontend/src/lib/utils.ts delete mode 100644 frontend/src/lib/wasm/storage-engine.ts delete mode 100644 frontend/src/lib/wasm/storage-types.ts create mode 100644 frontend/src/main.tsx delete mode 100644 frontend/src/routes/+layout.svelte delete mode 100644 frontend/src/routes/+page.svelte create mode 100644 frontend/src/stores/layout.ts create mode 100644 frontend/src/stores/storage.ts create mode 100644 frontend/src/vite-env.d.ts create mode 100644 frontend/src/wasm/storage-engine.ts create mode 100644 frontend/src/wasm/storage-types.ts delete mode 100644 frontend/static/robots.txt delete mode 100644 frontend/svelte.config.js diff --git a/.gitignore b/.gitignore index 6361690..5b00689 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ wasm-engine/target/ frontend/node_modules/ frontend/.svelte-kit/ frontend/build/ +frontend/dist/ # OS .DS_Store diff --git a/frontend/README.md b/frontend/README.md deleted file mode 100644 index 8337263..0000000 --- a/frontend/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# sv - -Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). - -## Creating a project - -If you're seeing this, you've probably already done this step. Congrats! - -```sh -# create a new project -npx sv create my-app -``` - -To recreate this project with the same configuration: - -```sh -# recreate this project -npx sv create --template minimal --types ts --no-install frontend -``` - -## Developing - -Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: - -```sh -npm run dev - -# or start the server and open the app in a new browser tab -npm run dev -- --open -``` - -## Building - -To create a production version of your app: - -```sh -npm run build -``` - -You can preview the production build with `npm run preview`. - -> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..fe50c0d --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,19 @@ + + + + + + + + + + + + wizalloc + + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a1a8b9e..caee215 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,1828 +1,2780 @@ { - "name": "wizalloc", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "wizalloc", - "version": "0.1.0", - "dependencies": { - "@sveltejs/adapter-static": "^3.0.10", - "wizalloc-engine": "file:../wasm-engine/pkg" - }, - "devDependencies": { - "@sveltejs/adapter-auto": "^7.0.0", - "@sveltejs/kit": "^2.50.2", - "@sveltejs/vite-plugin-svelte": "^6.2.4", - "svelte": "^5.49.2", - "svelte-check": "^4.3.6", - "typescript": "^5.9.3", - "vite": "^7.3.1", - "vite-plugin-top-level-await": "^1.5.0", - "vite-plugin-wasm": "^3.4.1" - } - }, - "../wasm-engine/pkg": { - "name": "wizalloc-engine", - "version": "0.1.0" - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@polka/url": { - "version": "1.0.0-next.29", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", - "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", - "license": "MIT" - }, - "node_modules/@rollup/plugin-virtual": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-virtual/-/plugin-virtual-3.0.2.tgz", - "integrity": "sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", - "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", - "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", - "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", - "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", - "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", - "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", - "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", - "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", - "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", - "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", - "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", - "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", - "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", - "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", - "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", - "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", - "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", - "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", - "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", - "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", - "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", - "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", - "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", - "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", - "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "license": "MIT" - }, - "node_modules/@sveltejs/acorn-typescript": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz", - "integrity": "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==", - "license": "MIT", - "peerDependencies": { - "acorn": "^8.9.0" - } - }, - "node_modules/@sveltejs/adapter-auto": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-7.0.1.tgz", - "integrity": "sha512-dvuPm1E7M9NI/+canIQ6KKQDU2AkEefEZ2Dp7cY6uKoPq9Z/PhOXABe526UdW2mN986gjVkuSLkOYIBnS/M2LQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@sveltejs/kit": "^2.0.0" - } - }, - "node_modules/@sveltejs/adapter-static": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.10.tgz", - "integrity": "sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew==", - "license": "MIT", - "peerDependencies": { - "@sveltejs/kit": "^2.0.0" - } - }, - "node_modules/@sveltejs/kit": { - "version": "2.52.0", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.52.0.tgz", - "integrity": "sha512-zG+HmJuSF7eC0e7xt2htlOcEMAdEtlVdb7+gAr+ef08EhtwUsjLxcAwBgUCJY3/5p08OVOxVZti91WfXeuLvsg==", - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.0.0", - "@sveltejs/acorn-typescript": "^1.0.5", - "@types/cookie": "^0.6.0", - "acorn": "^8.14.1", - "cookie": "^0.6.0", - "devalue": "^5.6.2", - "esm-env": "^1.2.2", - "kleur": "^4.1.5", - "magic-string": "^0.30.5", - "mrmime": "^2.0.0", - "sade": "^1.8.1", - "set-cookie-parser": "^3.0.0", - "sirv": "^3.0.0" - }, - "bin": { - "svelte-kit": "svelte-kit.js" - }, - "engines": { - "node": ">=18.13" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0", - "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", - "svelte": "^4.0.0 || ^5.0.0-next.0", - "typescript": "^5.3.3", - "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "6.2.4", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz", - "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==", - "license": "MIT", - "dependencies": { - "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", - "deepmerge": "^4.3.1", - "magic-string": "^0.30.21", - "obug": "^2.1.0", - "vitefu": "^1.1.1" - }, - "engines": { - "node": "^20.19 || ^22.12 || >=24" - }, - "peerDependencies": { - "svelte": "^5.0.0", - "vite": "^6.3.0 || ^7.0.0" - } - }, - "node_modules/@sveltejs/vite-plugin-svelte-inspector": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.2.tgz", - "integrity": "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==", - "license": "MIT", - "dependencies": { - "obug": "^2.1.0" - }, - "engines": { - "node": "^20.19 || ^22.12 || >=24" - }, - "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", - "svelte": "^5.0.0", - "vite": "^6.3.0 || ^7.0.0" - } - }, - "node_modules/@swc/core": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.11.tgz", - "integrity": "sha512-iLmLTodbYxU39HhMPaMUooPwO/zqJWvsqkrXv1ZI38rMb048p6N7qtAtTp37sw9NzSrvH6oli8EdDygo09IZ/w==", - "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@swc/counter": "^0.1.3", - "@swc/types": "^0.1.25" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/swc" - }, - "optionalDependencies": { - "@swc/core-darwin-arm64": "1.15.11", - "@swc/core-darwin-x64": "1.15.11", - "@swc/core-linux-arm-gnueabihf": "1.15.11", - "@swc/core-linux-arm64-gnu": "1.15.11", - "@swc/core-linux-arm64-musl": "1.15.11", - "@swc/core-linux-x64-gnu": "1.15.11", - "@swc/core-linux-x64-musl": "1.15.11", - "@swc/core-win32-arm64-msvc": "1.15.11", - "@swc/core-win32-ia32-msvc": "1.15.11", - "@swc/core-win32-x64-msvc": "1.15.11" - }, - "peerDependencies": { - "@swc/helpers": ">=0.5.17" - }, - "peerDependenciesMeta": { - "@swc/helpers": { - "optional": true - } - } - }, - "node_modules/@swc/core-darwin-arm64": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.11.tgz", - "integrity": "sha512-QoIupRWVH8AF1TgxYyeA5nS18dtqMuxNwchjBIwJo3RdwLEFiJq6onOx9JAxHtuPwUkIVuU2Xbp+jCJ7Vzmgtg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-darwin-x64": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.11.tgz", - "integrity": "sha512-S52Gu1QtPSfBYDiejlcfp9GlN+NjTZBRRNsz8PNwBgSE626/FUf2PcllVUix7jqkoMC+t0rS8t+2/aSWlMuQtA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.11.tgz", - "integrity": "sha512-lXJs8oXo6Z4yCpimpQ8vPeCjkgoHu5NoMvmJZ8qxDyU99KVdg6KwU9H79vzrmB+HfH+dCZ7JGMqMF//f8Cfvdg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.11.tgz", - "integrity": "sha512-chRsz1K52/vj8Mfq/QOugVphlKPWlMh10V99qfH41hbGvwAU6xSPd681upO4bKiOr9+mRIZZW+EfJqY42ZzRyA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.11.tgz", - "integrity": "sha512-PYftgsTaGnfDK4m6/dty9ryK1FbLk+LosDJ/RJR2nkXGc8rd+WenXIlvHjWULiBVnS1RsjHHOXmTS4nDhe0v0w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.11.tgz", - "integrity": "sha512-DKtnJKIHiZdARyTKiX7zdRjiDS1KihkQWatQiCHMv+zc2sfwb4Glrodx2VLOX4rsa92NLR0Sw8WLcPEMFY1szQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-x64-musl": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.11.tgz", - "integrity": "sha512-mUjjntHj4+8WBaiDe5UwRNHuEzLjIWBTSGTw0JT9+C9/Yyuh4KQqlcEQ3ro6GkHmBGXBFpGIj/o5VMyRWfVfWw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.11.tgz", - "integrity": "sha512-ZkNNG5zL49YpaFzfl6fskNOSxtcZ5uOYmWBkY4wVAvgbSAQzLRVBp+xArGWh2oXlY/WgL99zQSGTv7RI5E6nzA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.11.tgz", - "integrity": "sha512-6XnzORkZCQzvTQ6cPrU7iaT9+i145oLwnin8JrfsLG41wl26+5cNQ2XV3zcbrnFEV6esjOceom9YO1w9mGJByw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.11.tgz", - "integrity": "sha512-IQ2n6af7XKLL6P1gIeZACskSxK8jWtoKpJWLZmdXTDj1MGzktUy4i+FvpdtxFmJWNavRWH1VmTr6kAubRDHeKw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/@swc/types": { - "version": "0.1.25", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", - "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@swc/counter": "^0.1.3" - } - }, - "node_modules/@swc/wasm": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/wasm/-/wasm-1.15.11.tgz", - "integrity": "sha512-230rdYZf8ux3nIwISOQNCFrxzxpL/UFY4Khv/3UsvpEdo709j/+Tg80yXWW3DXETeZNPBV72QpvEBhXsl7Lc9g==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/@types/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT" - }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "license": "MIT" - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/devalue": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.2.tgz", - "integrity": "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==", - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" - } - }, - "node_modules/esm-env": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", - "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", - "license": "MIT" - }, - "node_modules/esrap": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.3.tgz", - "integrity": "sha512-8fOS+GIGCQZl/ZIlhl59htOlms6U8NvX6ZYgYHpRU/b6tVSh3uHkOHZikl3D4cMbYM0JlpBe+p/BkZEi8J9XIQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/is-reference": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", - "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.6" - } - }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/locate-character": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", - "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "license": "MIT" - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/mrmime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", - "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/obug": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/rollup": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", - "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.57.1", - "@rollup/rollup-android-arm64": "4.57.1", - "@rollup/rollup-darwin-arm64": "4.57.1", - "@rollup/rollup-darwin-x64": "4.57.1", - "@rollup/rollup-freebsd-arm64": "4.57.1", - "@rollup/rollup-freebsd-x64": "4.57.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", - "@rollup/rollup-linux-arm-musleabihf": "4.57.1", - "@rollup/rollup-linux-arm64-gnu": "4.57.1", - "@rollup/rollup-linux-arm64-musl": "4.57.1", - "@rollup/rollup-linux-loong64-gnu": "4.57.1", - "@rollup/rollup-linux-loong64-musl": "4.57.1", - "@rollup/rollup-linux-ppc64-gnu": "4.57.1", - "@rollup/rollup-linux-ppc64-musl": "4.57.1", - "@rollup/rollup-linux-riscv64-gnu": "4.57.1", - "@rollup/rollup-linux-riscv64-musl": "4.57.1", - "@rollup/rollup-linux-s390x-gnu": "4.57.1", - "@rollup/rollup-linux-x64-gnu": "4.57.1", - "@rollup/rollup-linux-x64-musl": "4.57.1", - "@rollup/rollup-openbsd-x64": "4.57.1", - "@rollup/rollup-openharmony-arm64": "4.57.1", - "@rollup/rollup-win32-arm64-msvc": "4.57.1", - "@rollup/rollup-win32-ia32-msvc": "4.57.1", - "@rollup/rollup-win32-x64-gnu": "4.57.1", - "@rollup/rollup-win32-x64-msvc": "4.57.1", - "fsevents": "~2.3.2" - } - }, - "node_modules/sade": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", - "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "license": "MIT", - "dependencies": { - "mri": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/set-cookie-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.0.1.tgz", - "integrity": "sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==", - "license": "MIT" - }, - "node_modules/sirv": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", - "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", - "license": "MIT", - "dependencies": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", - "totalist": "^3.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/svelte": { - "version": "5.51.3", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.51.3.tgz", - "integrity": "sha512-3+ni7BMjiEQeMCa1fDQzHy2ESAebgQDVOTuE4jlj2/QOAB2grRta8ew80p95miWE+ZmimpL7B3t9SSO4rv0aqQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "@jridgewell/sourcemap-codec": "^1.5.0", - "@sveltejs/acorn-typescript": "^1.0.5", - "@types/estree": "^1.0.5", - "@types/trusted-types": "^2.0.7", - "acorn": "^8.12.1", - "aria-query": "^5.3.1", - "axobject-query": "^4.1.0", - "clsx": "^2.1.1", - "devalue": "^5.6.2", - "esm-env": "^1.2.1", - "esrap": "^2.2.2", - "is-reference": "^3.0.3", - "locate-character": "^3.0.0", - "magic-string": "^0.30.11", - "zimmerframe": "^1.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/svelte-check": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.0.tgz", - "integrity": "sha512-gB3FdEPb8tPO3Y7Dzc6d/Pm/KrXAhK+0Fk+LkcysVtupvAh6Y/IrBCEZNupq57oh0hcwlxCUamu/rq7GtvfSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "chokidar": "^4.0.1", - "fdir": "^6.2.0", - "picocolors": "^1.0.0", - "sade": "^1.7.4" - }, - "bin": { - "svelte-check": "bin/svelte-check" - }, - "engines": { - "node": ">= 18.0.0" - }, - "peerDependencies": { - "svelte": "^4.0.0 || ^5.0.0-next.0", - "typescript": ">=5.0.0" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/totalist": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", - "license": "MIT", - "dependencies": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.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" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vite-plugin-top-level-await": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/vite-plugin-top-level-await/-/vite-plugin-top-level-await-1.6.0.tgz", - "integrity": "sha512-bNhUreLamTIkoulCR9aDXbTbhLk6n1YE8NJUTTxl5RYskNRtzOR0ASzSjBVRtNdjIfngDXo11qOsybGLNsrdww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/plugin-virtual": "^3.0.2", - "@swc/core": "^1.12.14", - "@swc/wasm": "^1.12.14", - "uuid": "10.0.0" - }, - "peerDependencies": { - "vite": ">=2.8" - } - }, - "node_modules/vite-plugin-wasm": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/vite-plugin-wasm/-/vite-plugin-wasm-3.5.0.tgz", - "integrity": "sha512-X5VWgCnqiQEGb+omhlBVsvTfxikKtoOgAzQ95+BZ8gQ+VfMHIjSHr0wyvXFQCa0eKQ0fKyaL0kWcEnYqBac4lQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "vite": "^2 || ^3 || ^4 || ^5 || ^6 || ^7" - } - }, - "node_modules/vitefu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", - "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", - "license": "MIT", - "workspaces": [ - "tests/deps/*", - "tests/projects/*", - "tests/projects/workspace/packages/*" - ], - "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, - "node_modules/wizalloc-engine": { - "resolved": "../wasm-engine/pkg", - "link": true - }, - "node_modules/zimmerframe": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", - "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", - "license": "MIT" - } - } + "name": "wizalloc", + "version": "0.2.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "wizalloc", + "version": "0.2.0", + "dependencies": { + "clsx": "^2.1.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "tailwind-merge": "^3.5.0", + "wizalloc-engine": "file:../wasm-engine/pkg", + "zustand": "^5.0.0" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.0.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^4.3.0", + "tailwindcss": "^4.0.0", + "typescript": "^5.7.0", + "vite": "^6.0.0", + "vite-plugin-top-level-await": "^1.5.0", + "vite-plugin-wasm": "^3.4.1" + } + }, + "../wasm-engine/pkg": { + "name": "wizalloc-engine", + "version": "0.1.0" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/plugin-virtual": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-virtual/-/plugin-virtual-3.0.2.tgz", + "integrity": "sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@swc/core": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.11.tgz", + "integrity": "sha512-iLmLTodbYxU39HhMPaMUooPwO/zqJWvsqkrXv1ZI38rMb048p6N7qtAtTp37sw9NzSrvH6oli8EdDygo09IZ/w==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.25" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.15.11", + "@swc/core-darwin-x64": "1.15.11", + "@swc/core-linux-arm-gnueabihf": "1.15.11", + "@swc/core-linux-arm64-gnu": "1.15.11", + "@swc/core-linux-arm64-musl": "1.15.11", + "@swc/core-linux-x64-gnu": "1.15.11", + "@swc/core-linux-x64-musl": "1.15.11", + "@swc/core-win32-arm64-msvc": "1.15.11", + "@swc/core-win32-ia32-msvc": "1.15.11", + "@swc/core-win32-x64-msvc": "1.15.11" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.11.tgz", + "integrity": "sha512-QoIupRWVH8AF1TgxYyeA5nS18dtqMuxNwchjBIwJo3RdwLEFiJq6onOx9JAxHtuPwUkIVuU2Xbp+jCJ7Vzmgtg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.11.tgz", + "integrity": "sha512-S52Gu1QtPSfBYDiejlcfp9GlN+NjTZBRRNsz8PNwBgSE626/FUf2PcllVUix7jqkoMC+t0rS8t+2/aSWlMuQtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.11.tgz", + "integrity": "sha512-lXJs8oXo6Z4yCpimpQ8vPeCjkgoHu5NoMvmJZ8qxDyU99KVdg6KwU9H79vzrmB+HfH+dCZ7JGMqMF//f8Cfvdg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.11.tgz", + "integrity": "sha512-chRsz1K52/vj8Mfq/QOugVphlKPWlMh10V99qfH41hbGvwAU6xSPd681upO4bKiOr9+mRIZZW+EfJqY42ZzRyA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.11.tgz", + "integrity": "sha512-PYftgsTaGnfDK4m6/dty9ryK1FbLk+LosDJ/RJR2nkXGc8rd+WenXIlvHjWULiBVnS1RsjHHOXmTS4nDhe0v0w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.11.tgz", + "integrity": "sha512-DKtnJKIHiZdARyTKiX7zdRjiDS1KihkQWatQiCHMv+zc2sfwb4Glrodx2VLOX4rsa92NLR0Sw8WLcPEMFY1szQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.11.tgz", + "integrity": "sha512-mUjjntHj4+8WBaiDe5UwRNHuEzLjIWBTSGTw0JT9+C9/Yyuh4KQqlcEQ3ro6GkHmBGXBFpGIj/o5VMyRWfVfWw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.11.tgz", + "integrity": "sha512-ZkNNG5zL49YpaFzfl6fskNOSxtcZ5uOYmWBkY4wVAvgbSAQzLRVBp+xArGWh2oXlY/WgL99zQSGTv7RI5E6nzA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.11.tgz", + "integrity": "sha512-6XnzORkZCQzvTQ6cPrU7iaT9+i145oLwnin8JrfsLG41wl26+5cNQ2XV3zcbrnFEV6esjOceom9YO1w9mGJByw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.11.tgz", + "integrity": "sha512-IQ2n6af7XKLL6P1gIeZACskSxK8jWtoKpJWLZmdXTDj1MGzktUy4i+FvpdtxFmJWNavRWH1VmTr6kAubRDHeKw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@swc/types": { + "version": "0.1.25", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", + "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@swc/wasm": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/wasm/-/wasm-1.15.11.tgz", + "integrity": "sha512-230rdYZf8ux3nIwISOQNCFrxzxpL/UFY4Khv/3UsvpEdo709j/+Tg80yXWW3DXETeZNPBV72QpvEBhXsl7Lc9g==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@tailwindcss/node": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.0.tgz", + "integrity": "sha512-Yv+fn/o2OmL5fh/Ir62VXItdShnUxfpkMA4Y7jdeC8O81WPB8Kf6TT6GSHvnqgSwDzlB5iT7kDpeXxLsUS0T6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", + "jiti": "^2.6.1", + "lightningcss": "1.31.1", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.2.0" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.0.tgz", + "integrity": "sha512-AZqQzADaj742oqn2xjl5JbIOzZB/DGCYF/7bpvhA8KvjUj9HJkag6bBuwZvH1ps6dfgxNHyuJVlzSr2VpMgdTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.2.0", + "@tailwindcss/oxide-darwin-arm64": "4.2.0", + "@tailwindcss/oxide-darwin-x64": "4.2.0", + "@tailwindcss/oxide-freebsd-x64": "4.2.0", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.0", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.0", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.0", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.0", + "@tailwindcss/oxide-linux-x64-musl": "4.2.0", + "@tailwindcss/oxide-wasm32-wasi": "4.2.0", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.0", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.0" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.0.tgz", + "integrity": "sha512-F0QkHAVaW/JNBWl4CEKWdZ9PMb0khw5DCELAOnu+RtjAfx5Zgw+gqCHFvqg3AirU1IAd181fwOtJQ5I8Yx5wtw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.0.tgz", + "integrity": "sha512-I0QylkXsBsJMZ4nkUNSR04p6+UptjcwhcVo3Zu828ikiEqHjVmQL9RuQ6uT/cVIiKpvtVA25msu/eRV97JeNSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.0.tgz", + "integrity": "sha512-6TmQIn4p09PBrmnkvbYQ0wbZhLtbaksCDx7Y7R3FYYx0yxNA7xg5KP7dowmQ3d2JVdabIHvs3Hx4K3d5uCf8xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.0.tgz", + "integrity": "sha512-qBudxDvAa2QwGlq9y7VIzhTvp2mLJ6nD/G8/tI70DCDoneaUeLWBJaPcbfzqRIWraj+o969aDQKvKW9dvkUizw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.0.tgz", + "integrity": "sha512-7XKkitpy5NIjFZNUQPeUyNJNJn1CJeV7rmMR+exHfTuOsg8rxIO9eNV5TSEnqRcaOK77zQpsyUkBWmPy8FgdSg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.0.tgz", + "integrity": "sha512-Mff5a5Q3WoQR01pGU1gr29hHM1N93xYrKkGXfPw/aRtK4bOc331Ho4Tgfsm5WDGvpevqMpdlkCojT3qlCQbCpA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.0.tgz", + "integrity": "sha512-XKcSStleEVnbH6W/9DHzZv1YhjE4eSS6zOu2eRtYAIh7aV4o3vIBs+t/B15xlqoxt6ef/0uiqJVB6hkHjWD/0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.0.tgz", + "integrity": "sha512-/hlXCBqn9K6fi7eAM0RsobHwJYa5V/xzWspVTzxnX+Ft9v6n+30Pz8+RxCn7sQL/vRHHLS30iQPrHQunu6/vJA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.0.tgz", + "integrity": "sha512-lKUaygq4G7sWkhQbfdRRBkaq4LY39IriqBQ+Gk6l5nKq6Ay2M2ZZb1tlIyRNgZKS8cbErTwuYSor0IIULC0SHw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.0.tgz", + "integrity": "sha512-xuDjhAsFdUuFP5W9Ze4k/o4AskUtI8bcAGU4puTYprr89QaYFmhYOPfP+d1pH+k9ets6RoE23BXZM1X1jJqoyw==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.8.1", + "@emnapi/runtime": "^1.8.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.1", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.0.tgz", + "integrity": "sha512-2UU/15y1sWDEDNJXxEIrfWKC2Yb4YgIW5Xz2fKFqGzFWfoMHWFlfa1EJlGO2Xzjkq/tvSarh9ZTjvbxqWvLLXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.0.tgz", + "integrity": "sha512-CrFadmFoc+z76EV6LPG1jx6XceDsaCG3lFhyLNo/bV9ByPrE+FnBPckXQVP4XRkN76h3Fjt/a+5Er/oA/nCBvQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.0.tgz", + "integrity": "sha512-da9mFCaHpoOgtQiWtDGIikTrSpUFBtIZCG3jy/u2BGV+l/X1/pbxzmIUxNt6JWm19N3WtGi4KlJdSH/Si83WOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.2.0", + "@tailwindcss/oxide": "4.2.0", + "tailwindcss": "4.2.0" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001770", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz", + "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "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" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", + "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.31.1", + "lightningcss-darwin-arm64": "1.31.1", + "lightningcss-darwin-x64": "1.31.1", + "lightningcss-freebsd-x64": "1.31.1", + "lightningcss-linux-arm-gnueabihf": "1.31.1", + "lightningcss-linux-arm64-gnu": "1.31.1", + "lightningcss-linux-arm64-musl": "1.31.1", + "lightningcss-linux-x64-gnu": "1.31.1", + "lightningcss-linux-x64-musl": "1.31.1", + "lightningcss-win32-arm64-msvc": "1.31.1", + "lightningcss-win32-x64-msvc": "1.31.1" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", + "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", + "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", + "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", + "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", + "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", + "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", + "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", + "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", + "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", + "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", + "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tailwind-merge": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz", + "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.0.tgz", + "integrity": "sha512-yYzTZ4++b7fNYxFfpnberEEKu43w44aqDMNM9MHMmcKuCH7lL8jJ4yJ7LGHv7rSwiqM0nkiobF9I6cLlpS2P7Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-plugin-top-level-await": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/vite-plugin-top-level-await/-/vite-plugin-top-level-await-1.6.0.tgz", + "integrity": "sha512-bNhUreLamTIkoulCR9aDXbTbhLk6n1YE8NJUTTxl5RYskNRtzOR0ASzSjBVRtNdjIfngDXo11qOsybGLNsrdww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/plugin-virtual": "^3.0.2", + "@swc/core": "^1.12.14", + "@swc/wasm": "^1.12.14", + "uuid": "10.0.0" + }, + "peerDependencies": { + "vite": ">=2.8" + } + }, + "node_modules/vite-plugin-wasm": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/vite-plugin-wasm/-/vite-plugin-wasm-3.5.0.tgz", + "integrity": "sha512-X5VWgCnqiQEGb+omhlBVsvTfxikKtoOgAzQ95+BZ8gQ+VfMHIjSHr0wyvXFQCa0eKQ0fKyaL0kWcEnYqBac4lQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "vite": "^2 || ^3 || ^4 || ^5 || ^6 || ^7" + } + }, + "node_modules/wizalloc-engine": { + "resolved": "../wasm-engine/pkg", + "link": true + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/zustand": { + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.11.tgz", + "integrity": "sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } } diff --git a/frontend/package.json b/frontend/package.json index fa12a56..743eed3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,30 +1,31 @@ { - "name": "wizalloc", - "private": true, - "version": "0.1.0", - "type": "module", - "scripts": { - "dev": "vite dev", - "build": "vite build", - "preview": "vite preview", - "prepare": "svelte-kit sync || echo ''", - "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "wasm:build": "cd ../wasm-engine && wasm-pack build --target web --release" - }, - "devDependencies": { - "@sveltejs/adapter-auto": "^7.0.0", - "@sveltejs/kit": "^2.50.2", - "@sveltejs/vite-plugin-svelte": "^6.2.4", - "svelte": "^5.49.2", - "svelte-check": "^4.3.6", - "typescript": "^5.9.3", - "vite": "^7.3.1", - "vite-plugin-top-level-await": "^1.5.0", - "vite-plugin-wasm": "^3.4.1" - }, - "dependencies": { - "@sveltejs/adapter-static": "^3.0.10", - "wizalloc-engine": "file:../wasm-engine/pkg" - } + "name": "wizalloc", + "private": true, + "version": "0.2.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview", + "wasm:build": "cd ../wasm-engine && wasm-pack build --target web --release" + }, + "dependencies": { + "clsx": "^2.1.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "tailwind-merge": "^3.5.0", + "wizalloc-engine": "file:../wasm-engine/pkg", + "zustand": "^5.0.0" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.0.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^4.3.0", + "tailwindcss": "^4.0.0", + "typescript": "^5.7.0", + "vite": "^6.0.0", + "vite-plugin-top-level-await": "^1.5.0", + "vite-plugin-wasm": "^3.4.1" + } } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..cf3bf26 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,273 @@ +import { useEffect, useState, useRef, useCallback } from 'react'; +import { useStorageStore } from '@/stores/storage'; +import { useLayoutStore } from '@/stores/layout'; +import { useMediaQuery } from '@/hooks/useMediaQuery'; +import StorageControlPanel from '@/components/panels/StorageControlPanel'; +import BufferPoolCanvas from '@/components/canvas/BufferPoolCanvas'; +import DiskCanvas from '@/components/canvas/DiskCanvas'; +import PageInspectorCanvas from '@/components/canvas/PageInspectorCanvas'; +import PaneDivider from '@/components/layout/PaneDivider'; +import PaneHeader from '@/components/layout/PaneHeader'; + +export default function App() { + const initWasm = useStorageStore((s) => s.initWasm); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const isMobile = useMediaQuery('(max-width: 768px)'); + + const vizRef = useRef(null); + const topRowRef = useRef(null); + const [vizHeight, setVizHeight] = useState(0); + const [topRowWidth, setTopRowWidth] = useState(0); + + const layout = useLayoutStore(); + + useEffect(() => { + initWasm() + .then(() => setLoading(false)) + .catch((e) => setError(String(e))); + }, [initWasm]); + + useEffect(() => { + const el = vizRef.current; + if (!el) return; + const ro = new ResizeObserver((entries) => { + setVizHeight(entries[0].contentRect.height); + }); + ro.observe(el); + return () => ro.disconnect(); + }, [loading]); + + useEffect(() => { + const el = topRowRef.current; + if (!el) return; + const ro = new ResizeObserver((entries) => { + setTopRowWidth(entries[0].contentRect.width); + }); + ro.observe(el); + return () => ro.disconnect(); + }, [loading, isMobile]); + + const handleHorizontalResize = useCallback( + (delta: number) => { + if (vizHeight === 0) return; + layout.setTopBottomRatio(layout.topBottomRatio + delta / vizHeight); + }, + [vizHeight, layout], + ); + + const handleVerticalResize = useCallback( + (delta: number) => { + if (topRowWidth === 0) return; + layout.setLeftRightRatio(layout.leftRightRatio + delta / topRowWidth); + }, + [topRowWidth, layout], + ); + + const topRowFlex = layout.inspectorCollapsed + ? '1 1 0%' + : `${layout.topBottomRatio} 1 0%`; + const bottomRowFlex = layout.inspectorCollapsed + ? '0 0 24px' + : `${1 - layout.topBottomRatio} 1 0%`; + const bpFlex = layout.diskCollapsed + ? '1 1 0%' + : `${layout.leftRightRatio} 1 0%`; + const diskFlex = layout.bufferPoolCollapsed + ? '1 1 0%' + : `${1 - layout.leftRightRatio} 1 0%`; + + if (error) { + return ( +
+ Failed to load WASM: {error} +
+ ); + } + + if (loading) { + return ( +
+
+
+
+ Loading WASM... +
+
+ ); + } + + return ( +
+
+
+
+ {isMobile ? ( + + ) : ( + + )} +
+
+
+ ); +} + +function Header() { + return ( +
+ + wizalloc + +
+ ); +} + +function MobileLayout({ vizRef }: { vizRef: React.RefObject }) { + const layout = useLayoutStore(); + + return ( + <> + +
+ + + + + + + + + +
+ + ); +} + +function MobilePane({ + title, + collapsed, + onToggle, + children, +}: { + title: string; + collapsed: boolean; + onToggle: () => void; + children: React.ReactNode; +}) { + return ( +
+ + {!collapsed &&
{children}
} +
+ ); +} + +function DesktopLayout({ + vizRef, + topRowRef, + topRowFlex, + bottomRowFlex, + bpFlex, + diskFlex, + handleHorizontalResize, + handleVerticalResize, +}: { + vizRef: React.RefObject; + topRowRef: React.RefObject; + topRowFlex: string; + bottomRowFlex: string; + bpFlex: string; + diskFlex: string; + handleHorizontalResize: (delta: number) => void; + handleVerticalResize: (delta: number) => void; +}) { + const layout = useLayoutStore(); + + return ( + <> + {/* Sidebar */} + {layout.sidebarCollapsed ? ( + + ) : ( + + )} + + {/* Viz area */} +
+ {/* Top row: Buffer Pool + Disk */} +
+ {layout.bufferPoolCollapsed ? ( +
+ +
+ ) : ( +
+ +
+ +
+
+ )} + + {!layout.bufferPoolCollapsed && !layout.diskCollapsed && ( + + )} + + {layout.diskCollapsed ? ( +
+ +
+ ) : ( +
+ +
+ +
+
+ )} +
+ + {/* Divider */} + {!layout.inspectorCollapsed && ( + + )} + + {/* Bottom row: Page Inspector */} +
+
+ + {!layout.inspectorCollapsed && ( +
+ +
+ )} +
+
+
+ + ); +} diff --git a/frontend/src/app.d.ts b/frontend/src/app.d.ts deleted file mode 100644 index da08e6d..0000000 --- a/frontend/src/app.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -// See https://svelte.dev/docs/kit/types#app.d.ts -// for information about these interfaces -declare global { - namespace App { - // interface Error {} - // interface Locals {} - // interface PageData {} - // interface PageState {} - // interface Platform {} - } -} - -export {}; diff --git a/frontend/src/app.html b/frontend/src/app.html deleted file mode 100644 index 12096b1..0000000 --- a/frontend/src/app.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - %sveltekit.head% - - -
%sveltekit.body%
- - diff --git a/frontend/src/components/canvas/BufferPoolCanvas.tsx b/frontend/src/components/canvas/BufferPoolCanvas.tsx new file mode 100644 index 0000000..d60e77e --- /dev/null +++ b/frontend/src/components/canvas/BufferPoolCanvas.tsx @@ -0,0 +1,183 @@ +import { useCallback } from 'react'; +import { useStorageStore } from '@/stores/storage'; +import { useCanvas, MONO, PAD } from '@/hooks/useCanvas'; + +const FRAME_W = 90; +const FRAME_H = 72; +const FRAME_GAP = 6; +const HEADER_H = 24; +const STATS_H = 36; + +export default function BufferPoolCanvas() { + const bpSnapshot = useStorageStore((s) => s.bpSnapshot); + const selectedPageId = useStorageStore((s) => s.selectedPageId); + const selectPage = useStorageStore((s) => s.selectPage); + + const computeHeight = useCallback( + (width: number, viewportHeight: number) => { + if (!bpSnapshot || width === 0) return viewportHeight; + const cols = Math.max(1, Math.floor((width - PAD * 2 + FRAME_GAP) / (FRAME_W + FRAME_GAP))); + const rows = Math.ceil(bpSnapshot.frames.length / cols); + const contentH = HEADER_H + PAD + rows * (FRAME_H + FRAME_GAP) + STATS_H + PAD; + return Math.max(viewportHeight, contentH); + }, + [bpSnapshot], + ); + + const render = useCallback( + (ctx: CanvasRenderingContext2D, width: number, h: number) => { + if (!bpSnapshot) { + ctx.fillStyle = 'rgba(255,255,255,0.3)'; + ctx.font = `12px ${MONO}`; + ctx.textAlign = 'center'; + ctx.fillText('Initialize engine to see buffer pool', width / 2, h / 2); + return; + } + + // Header + ctx.fillStyle = 'rgba(255,255,255,0.6)'; + ctx.font = `bold 11px ${MONO}`; + ctx.textAlign = 'left'; + ctx.fillText('BUFFER POOL', PAD, PAD + 10); + + // Frames + const startY = HEADER_H + PAD; + const cols = Math.max(1, Math.floor((width - PAD * 2 + FRAME_GAP) / (FRAME_W + FRAME_GAP))); + + for (let i = 0; i < bpSnapshot.frames.length; i++) { + const frame = bpSnapshot.frames[i]; + const col = i % cols; + const row = Math.floor(i / cols); + const x = PAD + col * (FRAME_W + FRAME_GAP); + const y = startY + row * (FRAME_H + FRAME_GAP); + + let bg = 'rgba(255,255,255,0.03)'; + let borderColor = 'rgba(255,255,255,0.1)'; + + if (frame.isOccupied) { + if (frame.isDirty) { + bg = 'rgba(239,68,68,0.1)'; + borderColor = 'rgba(239,68,68,0.3)'; + } else { + bg = 'rgba(74,222,128,0.08)'; + borderColor = 'rgba(74,222,128,0.2)'; + } + if (frame.pinCount > 0) { + borderColor = 'rgba(96,165,250,0.5)'; + } + } + + if (frame.pageId !== null && frame.pageId === selectedPageId) { + borderColor = '#c084fc'; + bg = 'rgba(192,132,252,0.12)'; + } + + ctx.fillStyle = bg; + ctx.strokeStyle = borderColor; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.roundRect(x, y, FRAME_W, FRAME_H, 4); + ctx.fill(); + ctx.stroke(); + + // Frame label + ctx.fillStyle = 'rgba(255,255,255,0.4)'; + ctx.font = `9px ${MONO}`; + ctx.textAlign = 'left'; + ctx.fillText(`Fr ${i}`, x + 4, y + 11); + + if (frame.isOccupied) { + ctx.fillStyle = '#fff'; + ctx.font = `bold 12px ${MONO}`; + ctx.textAlign = 'center'; + ctx.fillText(`Pg ${frame.pageId}`, x + FRAME_W / 2, y + 30); + + ctx.font = `9px ${MONO}`; + ctx.fillStyle = frame.pinCount > 0 ? '#60a5fa' : 'rgba(255,255,255,0.4)'; + ctx.textAlign = 'left'; + ctx.fillText(`pin:${frame.pinCount}`, x + 4, y + 46); + + ctx.textAlign = 'right'; + ctx.fillStyle = frame.isDirty ? '#f87171' : 'rgba(255,255,255,0.3)'; + ctx.fillText(frame.isDirty ? 'dirty' : 'clean', x + FRAME_W - 4, y + 46); + + const lruPos = bpSnapshot.lruOrder.indexOf(i); + if (lruPos >= 0) { + ctx.fillStyle = 'rgba(255,255,255,0.25)'; + ctx.font = `8px ${MONO}`; + ctx.textAlign = 'center'; + ctx.fillText(`LRU:${lruPos}`, x + FRAME_W / 2, y + 62); + } + } else { + ctx.fillStyle = 'rgba(255,255,255,0.2)'; + ctx.font = `11px ${MONO}`; + ctx.textAlign = 'center'; + ctx.fillText('empty', x + FRAME_W / 2, y + 35); + } + } + + // Stats + const statsY = h - STATS_H; + ctx.fillStyle = 'rgba(255,255,255,0.04)'; + ctx.fillRect(0, statsY, width, STATS_H); + + ctx.fillStyle = 'rgba(255,255,255,0.5)'; + ctx.font = `10px ${MONO}`; + ctx.textAlign = 'left'; + + const total = bpSnapshot.hitCount + bpSnapshot.missCount; + const hitRate = total > 0 ? ((bpSnapshot.hitCount / total) * 100).toFixed(1) : '0.0'; + + ctx.fillText(`Hits: ${bpSnapshot.hitCount} Misses: ${bpSnapshot.missCount} Rate: ${hitRate}%`, PAD, statsY + 13); + ctx.fillText(`Disk I/O R: ${bpSnapshot.diskReadCount} W: ${bpSnapshot.diskWriteCount}`, PAD, statsY + 26); + }, + [bpSnapshot, selectedPageId], + ); + + const handleClick = useCallback( + (e: React.MouseEvent, width: number) => { + if (!bpSnapshot) return; + const canvas = e.currentTarget; + const rect = canvas.getBoundingClientRect(); + const mx = e.clientX - rect.left; + const my = e.clientY - rect.top; + + const startY = HEADER_H + PAD; + const cols = Math.max(1, Math.floor((width - PAD * 2 + FRAME_GAP) / (FRAME_W + FRAME_GAP))); + + for (let i = 0; i < bpSnapshot.frames.length; i++) { + const frame = bpSnapshot.frames[i]; + if (!frame.isOccupied || frame.pageId === null) continue; + + const col = i % cols; + const row = Math.floor(i / cols); + const x = PAD + col * (FRAME_W + FRAME_GAP); + const y = startY + row * (FRAME_H + FRAME_GAP); + + if (mx >= x && mx <= x + FRAME_W && my >= y && my <= y + FRAME_H) { + selectPage(selectedPageId === frame.pageId ? null : frame.pageId); + return; + } + } + }, + [bpSnapshot, selectedPageId, selectPage], + ); + + const { containerRef, canvasRef, width, canvasHeight, handleClick: onCanvasClick } = useCanvas({ + render, + computeHeight, + deps: [bpSnapshot, selectedPageId], + onClick: handleClick, + }); + + return ( +
+ +
+ ); +} diff --git a/frontend/src/components/canvas/DiskCanvas.tsx b/frontend/src/components/canvas/DiskCanvas.tsx new file mode 100644 index 0000000..f0cd409 --- /dev/null +++ b/frontend/src/components/canvas/DiskCanvas.tsx @@ -0,0 +1,167 @@ +import { useCallback } from 'react'; +import { useStorageStore } from '@/stores/storage'; +import { useCanvas, MONO, PAD } from '@/hooks/useCanvas'; + +const CELL_W = 44; +const CELL_H = 36; +const GAP = 3; +const HEADER_H = 24; + +export default function DiskCanvas() { + const diskSnapshot = useStorageStore((s) => s.diskSnapshot); + const bpSnapshot = useStorageStore((s) => s.bpSnapshot); + const selectedPageId = useStorageStore((s) => s.selectedPageId); + const selectPage = useStorageStore((s) => s.selectPage); + + const computeHeight = useCallback( + (width: number, viewportHeight: number) => { + if (!diskSnapshot || width === 0) return viewportHeight; + const cols = Math.max(1, Math.floor((width - PAD * 2 + GAP) / (CELL_W + GAP))); + const rows = Math.ceil(diskSnapshot.maxPages / cols); + const contentH = HEADER_H + PAD + rows * (CELL_H + GAP) + PAD; + return Math.max(viewportHeight, contentH); + }, + [diskSnapshot], + ); + + const render = useCallback( + (ctx: CanvasRenderingContext2D, width: number, h: number) => { + if (!diskSnapshot) { + ctx.fillStyle = 'rgba(255,255,255,0.3)'; + ctx.font = `12px ${MONO}`; + ctx.textAlign = 'center'; + ctx.fillText('Initialize engine to see disk', width / 2, h / 2); + return; + } + + ctx.fillStyle = 'rgba(255,255,255,0.6)'; + ctx.font = `bold 11px ${MONO}`; + ctx.textAlign = 'left'; + ctx.fillText(`DISK ${diskSnapshot.numAllocated}/${diskSnapshot.maxPages} pages`, PAD, PAD + 10); + + const startY = HEADER_H + PAD; + const cols = Math.max(1, Math.floor((width - PAD * 2 + GAP) / (CELL_W + GAP))); + + const inPool = new Set(); + if (bpSnapshot) { + for (const [pid] of bpSnapshot.pageTable) { + inPool.add(pid); + } + } + + for (let i = 0; i < diskSnapshot.maxPages; i++) { + const pg = diskSnapshot.pages[i]; + const col = i % cols; + const row = Math.floor(i / cols); + const x = PAD + col * (CELL_W + GAP); + const y = startY + row * (CELL_H + GAP); + + let bg = 'rgba(255,255,255,0.02)'; + let border = 'rgba(255,255,255,0.06)'; + let label = ''; + let labelColor = 'rgba(255,255,255,0.25)'; + + if (pg.isAllocated) { + switch (pg.pageType) { + case 0: + bg = 'rgba(96,165,250,0.1)'; + border = 'rgba(96,165,250,0.25)'; + label = 'data'; + labelColor = 'rgba(96,165,250,0.6)'; + break; + case 1: + bg = 'rgba(251,191,36,0.1)'; + border = 'rgba(251,191,36,0.25)'; + label = 'ovfl'; + labelColor = 'rgba(251,191,36,0.6)'; + break; + default: + label = 'free'; + } + } + + if (inPool.has(i)) { + border = 'rgba(74,222,128,0.5)'; + } + + if (i === selectedPageId) { + border = '#c084fc'; + bg = 'rgba(192,132,252,0.15)'; + } + + ctx.fillStyle = bg; + ctx.strokeStyle = border; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.roundRect(x, y, CELL_W, CELL_H, 3); + ctx.fill(); + ctx.stroke(); + + ctx.fillStyle = pg.isAllocated ? 'rgba(255,255,255,0.8)' : 'rgba(255,255,255,0.2)'; + ctx.font = `bold 10px ${MONO}`; + ctx.textAlign = 'center'; + ctx.fillText(`${i}`, x + CELL_W / 2, y + 14); + + if (label) { + ctx.fillStyle = labelColor; + ctx.font = `8px ${MONO}`; + ctx.fillText(label, x + CELL_W / 2, y + 26); + } + + if (inPool.has(i)) { + ctx.beginPath(); + ctx.arc(x + CELL_W - 5, y + 5, 2.5, 0, Math.PI * 2); + ctx.fillStyle = '#4ade80'; + ctx.fill(); + } + } + }, + [diskSnapshot, bpSnapshot, selectedPageId], + ); + + const handleClick = useCallback( + (e: React.MouseEvent, width: number) => { + if (!diskSnapshot) return; + const canvas = e.currentTarget; + const rect = canvas.getBoundingClientRect(); + const mx = e.clientX - rect.left; + const my = e.clientY - rect.top; + + const startY = HEADER_H + PAD; + const cols = Math.max(1, Math.floor((width - PAD * 2 + GAP) / (CELL_W + GAP))); + + for (let i = 0; i < diskSnapshot.maxPages; i++) { + if (!diskSnapshot.pages[i].isAllocated) continue; + + const col = i % cols; + const row = Math.floor(i / cols); + const x = PAD + col * (CELL_W + GAP); + const y = startY + row * (CELL_H + GAP); + + if (mx >= x && mx <= x + CELL_W && my >= y && my <= y + CELL_H) { + selectPage(selectedPageId === i ? null : i); + return; + } + } + }, + [diskSnapshot, selectedPageId, selectPage], + ); + + const { containerRef, canvasRef, width, canvasHeight, handleClick: onCanvasClick } = useCanvas({ + render, + computeHeight, + deps: [diskSnapshot, bpSnapshot, selectedPageId], + onClick: handleClick, + }); + + return ( +
+ +
+ ); +} diff --git a/frontend/src/components/canvas/PageInspectorCanvas.tsx b/frontend/src/components/canvas/PageInspectorCanvas.tsx new file mode 100644 index 0000000..8c7c96e --- /dev/null +++ b/frontend/src/components/canvas/PageInspectorCanvas.tsx @@ -0,0 +1,328 @@ +import { useCallback, useState } from 'react'; +import { useStorageStore } from '@/stores/storage'; +import { useCanvas, MONO, PAD } from '@/hooks/useCanvas'; +import { pageTypeName, INVALID_PAGE } from '@/wasm/storage-types'; +import type { PageSnapshot } from '@/wasm/storage-types'; + +const BYTE_W = 18; +const BYTE_H = 16; +const BYTE_GAP = 1; + +function computeSlotsHeight(snap: PageSnapshot, width: number): number { + const sw = 70; + const sh = 16; + let slotX = PAD; + let rows = 1; + for (let i = 0; i < snap.slots.length; i++) { + slotX += sw + 3; + if (slotX + sw > width - PAD && i < snap.slots.length - 1) { + slotX = PAD; + rows++; + } + } + return rows * (sh + 3); +} + +function renderBarView( + ctx: CanvasRenderingContext2D, + snap: PageSnapshot, + slotsEndY: number, + width: number, + selectedSlotId: number | null, +) { + const barY = slotsEndY + 12; + const barH = 28; + const barW = width - PAD * 2; + const ps = snap.pageSize; + + ctx.fillStyle = 'rgba(255,255,255,0.03)'; + ctx.strokeStyle = 'rgba(255,255,255,0.1)'; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.roundRect(PAD, barY, barW, barH, 4); + ctx.fill(); + ctx.stroke(); + + const scale = barW / ps; + + const headerW = 16 * scale; + ctx.fillStyle = 'rgba(96,165,250,0.3)'; + ctx.fillRect(PAD, barY, headerW, barH); + + const slotW = (snap.freeStart - 16) * scale; + if (slotW > 0) { + ctx.fillStyle = 'rgba(74,222,128,0.25)'; + ctx.fillRect(PAD + headerW, barY, slotW, barH); + } + + const tupleStart = snap.freeEnd * scale; + const tupleW = (ps - snap.freeEnd) * scale; + if (tupleW > 0) { + ctx.fillStyle = 'rgba(251,191,36,0.25)'; + ctx.fillRect(PAD + tupleStart, barY, tupleW, barH); + } + + ctx.font = `8px ${MONO}`; + ctx.textAlign = 'center'; + ctx.fillStyle = 'rgba(96,165,250,0.8)'; + if (headerW > 20) ctx.fillText('HDR', PAD + headerW / 2, barY + barH / 2 + 3); + + ctx.fillStyle = 'rgba(74,222,128,0.8)'; + if (slotW > 24) ctx.fillText('SLOTS', PAD + headerW + slotW / 2, barY + barH / 2 + 3); + + ctx.fillStyle = 'rgba(255,255,255,0.2)'; + const freeW = (snap.freeEnd - snap.freeStart) * scale; + if (freeW > 30) ctx.fillText(`FREE ${snap.freeSpace}B`, PAD + snap.freeStart * scale + freeW / 2, barY + barH / 2 + 3); + + ctx.fillStyle = 'rgba(251,191,36,0.8)'; + if (tupleW > 30) ctx.fillText('TUPLES', PAD + tupleStart + tupleW / 2, barY + barH / 2 + 3); + + ctx.fillStyle = 'rgba(255,255,255,0.25)'; + ctx.font = `7px ${MONO}`; + ctx.textAlign = 'left'; + ctx.fillText('0', PAD, barY + barH + 10); + ctx.textAlign = 'center'; + ctx.fillText(String(snap.freeStart), PAD + snap.freeStart * scale, barY + barH + 10); + ctx.fillText(String(snap.freeEnd), PAD + snap.freeEnd * scale, barY + barH + 10); + ctx.textAlign = 'right'; + ctx.fillText(String(ps), PAD + barW, barY + barH + 10); + + const legendY = barY + barH + 22; + const legends = [ + { color: 'rgba(96,165,250,0.5)', label: 'Header' }, + { color: 'rgba(74,222,128,0.5)', label: 'Slots' }, + { color: 'rgba(255,255,255,0.1)', label: 'Free' }, + { color: 'rgba(251,191,36,0.5)', label: 'Tuples' }, + ]; + let lx = PAD; + for (const l of legends) { + ctx.fillStyle = l.color; + ctx.fillRect(lx, legendY, 8, 8); + ctx.fillStyle = 'rgba(255,255,255,0.4)'; + ctx.font = `8px ${MONO}`; + ctx.textAlign = 'left'; + ctx.fillText(l.label, lx + 11, legendY + 7); + lx += ctx.measureText(l.label).width + 22; + } + + if (selectedSlotId !== null && selectedSlotId < snap.slots.length) { + const slot = snap.slots[selectedSlotId]; + if (slot.length > 0) { + const selX = PAD + slot.offset * scale; + const selW = slot.length * scale; + ctx.strokeStyle = '#00d4ff'; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.roundRect(selX, barY, Math.max(selW, 2), barH, 2); + ctx.stroke(); + + const infoY = legendY + 16; + const pct = ((slot.length / ps) * 100).toFixed(1); + ctx.fillStyle = '#00d4ff'; + ctx.font = `9px ${MONO}`; + ctx.textAlign = 'left'; + ctx.fillText( + `Row ${snap.pageId}:${selectedSlotId} → Page ${snap.pageId}, Slot [${selectedSlotId}], offset ${slot.offset}, ${slot.length} bytes (${pct}% of page)`, + PAD, + infoY, + ); + } + } +} + +function renderHexView( + ctx: CanvasRenderingContext2D, + snap: PageSnapshot, + slotsEndY: number, + width: number, +) { + const hexY = slotsEndY + 12; + const bytes = snap.rawBytes; + const bytesPerRow = Math.max(1, Math.floor((width - PAD * 2 - 50) / (BYTE_W + BYTE_GAP))); + const totalRows = Math.ceil(bytes.length / bytesPerRow); + + for (let row = 0; row < totalRows; row++) { + const baseOffset = row * bytesPerRow; + if (baseOffset >= bytes.length) break; + + ctx.fillStyle = 'rgba(255,255,255,0.3)'; + ctx.font = `9px ${MONO}`; + ctx.textAlign = 'right'; + ctx.fillText( + baseOffset.toString(16).padStart(4, '0'), + PAD + 34, + hexY + row * (BYTE_H + BYTE_GAP) + 11, + ); + + for (let col = 0; col < bytesPerRow; col++) { + const byteIdx = baseOffset + col; + if (byteIdx >= bytes.length) break; + + const bx = PAD + 40 + col * (BYTE_W + BYTE_GAP); + const by = hexY + row * (BYTE_H + BYTE_GAP); + + let bg: string; + if (byteIdx < 16) { + bg = 'rgba(96,165,250,0.15)'; + } else if (byteIdx < snap.freeStart) { + bg = 'rgba(74,222,128,0.12)'; + } else if (byteIdx >= snap.freeEnd) { + bg = 'rgba(251,191,36,0.12)'; + } else { + bg = 'rgba(255,255,255,0.02)'; + } + + ctx.fillStyle = bg; + ctx.fillRect(bx, by, BYTE_W, BYTE_H); + + const val = bytes[byteIdx]; + ctx.fillStyle = val === 0 ? 'rgba(255,255,255,0.15)' : 'rgba(255,255,255,0.7)'; + ctx.font = `9px ${MONO}`; + ctx.textAlign = 'center'; + ctx.fillText(val.toString(16).padStart(2, '0'), bx + BYTE_W / 2, by + 11); + } + } +} + +export default function PageInspectorCanvas() { + const pageSnapshot = useStorageStore((s) => s.pageSnapshot); + const selectedSlotId = useStorageStore((s) => s.selectedSlotId); + const selectedPageId = useStorageStore((s) => s.selectedPageId); + const flushPage = useStorageStore((s) => s.flushPage); + const [showHex, setShowHex] = useState(false); + + const computeHeight = useCallback( + (width: number, viewportHeight: number) => { + if (!pageSnapshot || width === 0) return viewportHeight; + const slotsH = computeSlotsHeight(pageSnapshot, width); + const baseY = PAD + 48 + slotsH + 12; + if (showHex) { + const bytesPerRow = Math.max(1, Math.floor((width - PAD * 2 - 50) / (BYTE_W + BYTE_GAP))); + const totalRows = Math.ceil(pageSnapshot.rawBytes.length / bytesPerRow); + const contentH = baseY + totalRows * (BYTE_H + BYTE_GAP) + PAD; + return Math.max(viewportHeight, contentH); + } + const contentH = baseY + 28 + 22 + 30 + PAD; + return Math.max(viewportHeight, contentH); + }, + [pageSnapshot, showHex], + ); + + const render = useCallback( + (ctx: CanvasRenderingContext2D, width: number, h: number) => { + if (!pageSnapshot) { + ctx.fillStyle = 'rgba(255,255,255,0.3)'; + ctx.font = `12px ${MONO}`; + ctx.textAlign = 'center'; + ctx.fillText('Select a page from the Buffer Pool or Disk', width / 2, h / 2 - 8); + ctx.fillText('to inspect its internal layout', width / 2, h / 2 + 8); + return; + } + + const snap = pageSnapshot; + + // Header info + ctx.fillStyle = 'rgba(255,255,255,0.7)'; + ctx.font = `bold 11px ${MONO}`; + ctx.textAlign = 'left'; + ctx.fillText(`PAGE ${snap.pageId} (${pageTypeName(snap.pageType)})`, PAD, PAD + 10); + + ctx.fillStyle = 'rgba(255,255,255,0.45)'; + ctx.font = `10px ${MONO}`; + ctx.fillText( + `Slots: ${snap.slotCount} Free: ${snap.freeSpace}B Next: ${snap.nextPageId === INVALID_PAGE ? 'NULL' : 'Pg ' + snap.nextPageId}`, + PAD, + PAD + 24, + ); + + // Slot table + ctx.fillText('Slot Array:', PAD, PAD + 40); + let slotX = PAD; + let slotY = PAD + 48; + const sw = 70; + const sh = 16; + for (let i = 0; i < snap.slots.length; i++) { + const s = snap.slots[i]; + const isDead = s.length === 0; + const isSelected = selectedSlotId === i; + + if (isSelected) { + ctx.fillStyle = 'rgba(0,200,255,0.15)'; + ctx.strokeStyle = '#00d4ff'; + ctx.lineWidth = 2; + } else { + ctx.fillStyle = isDead ? 'rgba(239,68,68,0.1)' : 'rgba(96,165,250,0.1)'; + ctx.strokeStyle = isDead ? 'rgba(239,68,68,0.2)' : 'rgba(96,165,250,0.2)'; + ctx.lineWidth = 1; + } + ctx.beginPath(); + ctx.roundRect(slotX, slotY, sw, sh, 2); + ctx.fill(); + ctx.stroke(); + + ctx.fillStyle = isSelected ? '#00d4ff' : isDead ? 'rgba(239,68,68,0.6)' : 'rgba(255,255,255,0.6)'; + ctx.font = `9px ${MONO}`; + ctx.textAlign = 'center'; + ctx.fillText( + isDead ? `[${i}] DEAD` : `[${i}] @${s.offset} ${s.length}B`, + slotX + sw / 2, + slotY + 11, + ); + + slotX += sw + 3; + if (slotX + sw > width - PAD) { + slotX = PAD; + slotY += sh + 3; + } + } + const slotsEndY = slotY + sh; + + if (showHex) { + renderHexView(ctx, snap, slotsEndY, width); + } else { + renderBarView(ctx, snap, slotsEndY, width, selectedSlotId); + } + }, + [pageSnapshot, selectedSlotId, showHex], + ); + + const { containerRef, canvasRef, width, canvasHeight } = useCanvas({ + render, + computeHeight, + deps: [pageSnapshot, selectedSlotId, showHex], + }); + + return ( +
+
+ + + {pageSnapshot && ( + + )} +
+
+ +
+
+ ); +} diff --git a/frontend/src/components/layout/CollapsibleSection.tsx b/frontend/src/components/layout/CollapsibleSection.tsx new file mode 100644 index 0000000..14f27b6 --- /dev/null +++ b/frontend/src/components/layout/CollapsibleSection.tsx @@ -0,0 +1,51 @@ +import type { ReactNode } from 'react'; + +interface CollapsibleSectionProps { + title: string; + badge?: string; + description?: string; + open: boolean; + onToggle: () => void; + children: ReactNode; +} + +export default function CollapsibleSection({ + title, + badge, + description, + open, + onToggle, + children, +}: CollapsibleSectionProps) { + return ( +
+ + {description && open && ( +

+ {description} +

+ )} + {open && ( +
+ {children} +
+ )} +
+ ); +} diff --git a/frontend/src/components/layout/PaneDivider.tsx b/frontend/src/components/layout/PaneDivider.tsx new file mode 100644 index 0000000..a6e72c3 --- /dev/null +++ b/frontend/src/components/layout/PaneDivider.tsx @@ -0,0 +1,48 @@ +import { useRef, useCallback } from 'react'; + +interface PaneDividerProps { + orientation: 'horizontal' | 'vertical'; + onResize: (deltaPixels: number) => void; +} + +export default function PaneDivider({ orientation, onResize }: PaneDividerProps) { + const dragging = useRef(false); + + const onPointerDown = useCallback( + (e: React.PointerEvent) => { + dragging.current = true; + e.currentTarget.setPointerCapture(e.pointerId); + }, + [], + ); + + const onPointerMove = useCallback( + (e: React.PointerEvent) => { + if (!dragging.current) return; + const delta = orientation === 'horizontal' ? e.movementY : e.movementX; + onResize(delta); + }, + [orientation, onResize], + ); + + const stopDrag = useCallback(() => { + dragging.current = false; + }, []); + + const isHorizontal = orientation === 'horizontal'; + + return ( +
+ ); +} diff --git a/frontend/src/components/layout/PaneHeader.tsx b/frontend/src/components/layout/PaneHeader.tsx new file mode 100644 index 0000000..9580421 --- /dev/null +++ b/frontend/src/components/layout/PaneHeader.tsx @@ -0,0 +1,38 @@ +interface PaneHeaderProps { + title: string; + collapsed: boolean; + onToggle: () => void; + vertical?: boolean; +} + +export default function PaneHeader({ title, collapsed, onToggle, vertical = false }: PaneHeaderProps) { + if (vertical) { + return ( + + ); + } + + return ( + + ); +} diff --git a/frontend/src/components/panels/ColumnEditor.tsx b/frontend/src/components/panels/ColumnEditor.tsx new file mode 100644 index 0000000..5989e5c --- /dev/null +++ b/frontend/src/components/panels/ColumnEditor.tsx @@ -0,0 +1,94 @@ +interface Column { + name: string; + type: string; + maxLen: number; + nullable: boolean; +} + +interface ColumnEditorProps { + columns: Column[]; + onChange: (columns: Column[]) => void; +} + +export default function ColumnEditor({ columns, onChange }: ColumnEditorProps) { + function update(index: number, field: keyof Column, value: string | number | boolean) { + const next = columns.map((c, i) => + i === index ? { ...c, [field]: value } : c, + ); + onChange(next); + } + + function add() { + onChange([...columns, { name: '', type: 'Int32', nullable: false, maxLen: 255 }]); + } + + function remove(index: number) { + onChange(columns.filter((_, i) => i !== index)); + } + + return ( + <> +
+ {columns.map((col, i) => ( +
+ update(i, 'name', e.target.value)} + placeholder="col name" + className="w-20 bg-white/[0.06] border border-white/12 rounded px-1.5 py-1 text-white font-mono text-[11px] outline-none focus:border-[#007acc]" + /> + + {(col.type === 'VarChar' || col.type === 'Blob') && ( + update(i, 'maxLen', Number(e.target.value))} + min={1} + max={65535} + className="w-12 bg-white/[0.06] border border-white/12 rounded px-1.5 py-1 text-white font-mono text-[11px] outline-none focus:border-[#007acc]" + /> + )} + + +
+ ))} +
+
+ +
+ + ); +} + +export type { Column }; diff --git a/frontend/src/components/panels/CreateTablePanel.tsx b/frontend/src/components/panels/CreateTablePanel.tsx new file mode 100644 index 0000000..f213c84 --- /dev/null +++ b/frontend/src/components/panels/CreateTablePanel.tsx @@ -0,0 +1,47 @@ +import { useState } from 'react'; +import { useStorageStore } from '@/stores/storage'; +import CollapsibleSection from '@/components/layout/CollapsibleSection'; +import ColumnEditor, { type Column } from './ColumnEditor'; +import { toColumnDefs } from './QuickFillPanel'; + +interface CreateTablePanelProps { + open: boolean; + onToggle: () => void; +} + +export default function CreateTablePanel({ open, onToggle }: CreateTablePanelProps) { + const createTable = useStorageStore((s) => s.createTable); + const [tableName, setTableName] = useState(''); + const [columns, setColumns] = useState([ + { name: 'id', type: 'Int32', nullable: false, maxLen: 255 }, + { name: 'name', type: 'VarChar', nullable: false, maxLen: 255 }, + ]); + + function handleCreate() { + if (!tableName.trim()) return; + createTable(tableName.trim(), toColumnDefs(columns)); + setTableName(''); + } + + return ( + + setTableName(e.target.value)} + placeholder="Table name" + className="w-full bg-white/[0.06] border border-white/12 rounded px-1.5 py-1 text-white font-mono text-[11px] outline-none focus:border-[#007acc]" + /> + +
+ +
+
+ ); +} diff --git a/frontend/src/components/panels/EngineConfigPanel.tsx b/frontend/src/components/panels/EngineConfigPanel.tsx new file mode 100644 index 0000000..d56da84 --- /dev/null +++ b/frontend/src/components/panels/EngineConfigPanel.tsx @@ -0,0 +1,98 @@ +import { useState } from 'react'; +import { useStorageStore } from '@/stores/storage'; +import type { EngineConfig } from '@/wasm/storage-types'; + +const PAGE_PRESETS = [64, 128, 256, 512, 1024, 4096]; + +export default function EngineConfigPanel() { + const initEngine = useStorageStore((s) => s.initEngine); + const [pageSize, setPageSize] = useState(128); + const [poolSize, setPoolSize] = useState(8); + const [diskCapacity, setDiskCapacity] = useState(64); + const [overflowThreshold, setOverflowThreshold] = useState(64); + + function handleInit() { + const config: EngineConfig = { + page_size: pageSize, + pool_size: poolSize, + disk_capacity: diskCapacity, + overflow_threshold: overflowThreshold, + }; + initEngine(config); + } + + return ( +
+

+ Engine Configuration +

+

+ Configure the storage engine's page size, buffer pool capacity, and disk size +

+ + Page Size +
+ {PAGE_PRESETS.map((preset) => ( + + ))} +
+ +
+ + +
+ + + + +
+ ); +} diff --git a/frontend/src/components/panels/LoadDataPanel.tsx b/frontend/src/components/panels/LoadDataPanel.tsx new file mode 100644 index 0000000..c2e9884 --- /dev/null +++ b/frontend/src/components/panels/LoadDataPanel.tsx @@ -0,0 +1,120 @@ +import { useState, useCallback } from 'react'; +import { useStorageStore } from '@/stores/storage'; +import { parseCSV, inferColumnTypes } from '@/stores/storage'; +import CollapsibleSection from '@/components/layout/CollapsibleSection'; +import { formatColType } from '@/wasm/storage-types'; +import type { ColumnDef } from '@/wasm/storage-types'; + +interface LoadDataPanelProps { + open: boolean; + onToggle: () => void; +} + +export default function LoadDataPanel({ open, onToggle }: LoadDataPanelProps) { + const loadFromCSV = useStorageStore((s) => s.loadFromCSV); + const [tableName, setTableName] = useState(''); + const [headers, setHeaders] = useState([]); + const [rows, setRows] = useState([]); + const [columns, setColumns] = useState([]); + const [parsed, setParsed] = useState(false); + const [error, setError] = useState(''); + + const handleFileSelect = useCallback((event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (!file) return; + + setError(''); + setParsed(false); + + const reader = new FileReader(); + reader.onload = () => { + try { + const text = reader.result as string; + const { headers: h, rows: r } = parseCSV(text); + if (h.length === 0) { + setError('No columns found in CSV'); + return; + } + setHeaders(h); + setRows(r); + setColumns(inferColumnTypes(h, r)); + setParsed(true); + + if (!tableName.trim()) { + setTableName(file.name.replace(/\.csv$/i, '').replace(/[^a-zA-Z0-9_]/g, '_')); + } + } catch (e) { + setError(String(e)); + } + }; + reader.readAsText(file); + }, [tableName]); + + function handleLoad() { + if (!tableName.trim() || !parsed) return; + loadFromCSV(tableName.trim(), headers, rows, columns); + setTableName(''); + setHeaders([]); + setRows([]); + setColumns([]); + setParsed(false); + setError(''); + } + + return ( + + setTableName(e.target.value)} + placeholder="Table name" + className="w-full bg-white/[0.06] border border-white/12 rounded px-1.5 py-1 text-white font-mono text-[11px] outline-none focus:border-[#007acc]" + /> +
+ + +
+ + {error && {error}} + + {parsed && ( + <> + {rows.length} rows, {columns.length} columns +
+ {columns.map((col, i) => ( + + {col.name}: {formatColType(col.type)}{col.nullable ? '?' : ''} + + ))} +
+ + + )} +
+ ); +} diff --git a/frontend/src/components/panels/QuickFillPanel.tsx b/frontend/src/components/panels/QuickFillPanel.tsx new file mode 100644 index 0000000..6f62b0a --- /dev/null +++ b/frontend/src/components/panels/QuickFillPanel.tsx @@ -0,0 +1,84 @@ +import { useState } from 'react'; +import { useStorageStore } from '@/stores/storage'; +import CollapsibleSection from '@/components/layout/CollapsibleSection'; +import ColumnEditor, { type Column } from './ColumnEditor'; +import type { ColumnDef } from '@/wasm/storage-types'; + +const ROW_PRESETS = [10, 50, 100, 500]; + +function toColumnDefs(columns: Column[]): ColumnDef[] { + return columns.map((c) => { + let type_: string | { VarChar: number } | { Blob: number } = c.type; + if (c.type === 'VarChar') type_ = { VarChar: c.maxLen }; + if (c.type === 'Blob') type_ = { Blob: c.maxLen }; + return { name: c.name, type: type_, nullable: c.nullable }; + }); +} + +interface QuickFillPanelProps { + open: boolean; + onToggle: () => void; +} + +export default function QuickFillPanel({ open, onToggle }: QuickFillPanelProps) { + const bootstrapTable = useStorageStore((s) => s.bootstrapTable); + const [tableName, setTableName] = useState('users'); + const [rowCount, setRowCount] = useState(100); + const [columns, setColumns] = useState([ + { name: 'id', type: 'Int32', nullable: false, maxLen: 255 }, + { name: 'name', type: 'VarChar', nullable: false, maxLen: 32 }, + { name: 'email', type: 'VarChar', nullable: false, maxLen: 64 }, + { name: 'age', type: 'UInt32', nullable: false, maxLen: 255 }, + { name: 'active', type: 'Bool', nullable: false, maxLen: 255 }, + ]); + + function handleFill() { + if (!tableName.trim()) return; + bootstrapTable(tableName.trim(), toColumnDefs(columns), rowCount); + } + + return ( + + setTableName(e.target.value)} + placeholder="Table name" + className="w-full bg-white/[0.06] border border-white/12 rounded px-1.5 py-1 text-white font-mono text-[11px] outline-none focus:border-[#007acc]" + /> + + + Row Count +
+ {ROW_PRESETS.map((preset) => ( + + ))} +
+ + +
+ ); +} + +export { toColumnDefs }; diff --git a/frontend/src/components/panels/ScanResultsPanel.tsx b/frontend/src/components/panels/ScanResultsPanel.tsx new file mode 100644 index 0000000..6bec962 --- /dev/null +++ b/frontend/src/components/panels/ScanResultsPanel.tsx @@ -0,0 +1,142 @@ +import { useState, useMemo } from 'react'; +import { useStorageStore } from '@/stores/storage'; +import CollapsibleSection from '@/components/layout/CollapsibleSection'; +import { CellInfoView } from './TableOperationsPanel'; + +interface ScanResultsPanelProps { + open: boolean; + onToggle: () => void; +} + +export default function ScanResultsPanel({ open, onToggle }: ScanResultsPanelProps) { + const scanResults = useStorageStore((s) => s.scanResults); + const scanColumns = useStorageStore((s) => s.scanColumns); + const selectedSlotId = useStorageStore((s) => s.selectedSlotId); + const selectedPageId = useStorageStore((s) => s.selectedPageId); + const selectedCell = useStorageStore((s) => s.selectedCell); + const selectCell = useStorageStore((s) => s.selectCell); + const clearCellSelection = useStorageStore((s) => s.clearCellSelection); + const selectRowFromScan = useStorageStore((s) => s.selectRowFromScan); + const tableInfo = useStorageStore((s) => s.tableInfo); + const pageSnapshot = useStorageStore((s) => s.pageSnapshot); + + const [filterText, setFilterText] = useState(''); + + const filteredResults = useMemo(() => { + if (!filterText.trim()) return scanResults; + const q = filterText.trim().toLowerCase(); + return scanResults.filter((row) => { + if (row.row_id.toLowerCase().includes(q)) return true; + return row.values.some((v) => String(v ?? 'NULL').toLowerCase().includes(q)); + }); + }, [scanResults, filterText]); + + if (scanResults.length === 0) return null; + + const columns = scanColumns.length > 0 ? scanColumns : (scanResults[0]?.values ?? []).map((_, i) => `col${i}`); + + function handleRowClick(rowId: string) { + clearCellSelection(); + selectRowFromScan(rowId); + } + + function handleCellClick(e: React.MouseEvent, rowId: string, colIndex: number, value: unknown) { + e.stopPropagation(); + selectCell(rowId, colIndex, value); + } + + function isRowSelected(rowId: string) { + if (selectedSlotId === null) return false; + const parts = rowId.split(':'); + return parseInt(parts[0], 10) === selectedPageId && parseInt(parts[1], 10) === selectedSlotId; + } + + const badge = filteredResults.length === scanResults.length + ? String(scanResults.length) + : `${filteredResults.length}/${scanResults.length}`; + + return ( + + {/* Filter */} +
+ setFilterText(e.target.value)} + placeholder="Filter rows..." + className="w-full bg-white/[0.06] border border-white/12 rounded px-1.5 py-1 text-white font-mono text-[10px] outline-none focus:border-[#007acc]" + /> + {filterText.trim() && ( + + Showing {filteredResults.length} of {scanResults.length} rows + + )} +
+ + {/* Results table */} +
+ + + + + {columns.map((colName) => ( + + ))} + + + + {filteredResults.map((row) => { + const rowSelected = isRowSelected(row.row_id); + return ( + handleRowClick(row.row_id)} + > + + {row.values.map((val, colIdx) => ( + + ))} + + ); + })} + +
+ RowID + + {colName} +
+ {row.row_id} + handleCellClick(e, row.row_id, colIdx, val)} + > + {val === null ? 'NULL' : String(val)} +
+
+ + {/* Cell info */} + {selectedCell && tableInfo && ( + + )} +
+ ); +} diff --git a/frontend/src/components/panels/StorageControlPanel.tsx b/frontend/src/components/panels/StorageControlPanel.tsx new file mode 100644 index 0000000..28541bd --- /dev/null +++ b/frontend/src/components/panels/StorageControlPanel.tsx @@ -0,0 +1,78 @@ +import { useState, useEffect } from 'react'; +import { useStorageStore } from '@/stores/storage'; +import EngineConfigPanel from './EngineConfigPanel'; +import QuickFillPanel from './QuickFillPanel'; +import LoadDataPanel from './LoadDataPanel'; +import CreateTablePanel from './CreateTablePanel'; +import TableOperationsPanel from './TableOperationsPanel'; +import ScanResultsPanel from './ScanResultsPanel'; + +export default function StorageControlPanel() { + const engineReady = useStorageStore((s) => s.engineReady); + const engineConfig = useStorageStore((s) => s.engineConfig); + const resetEngine = useStorageStore((s) => s.resetEngine); + const tables = useStorageStore((s) => s.tables); + const statusMsg = useStorageStore((s) => s.statusMsg); + const statusType = useStorageStore((s) => s.statusType); + + const [quickFillOpen, setQuickFillOpen] = useState(true); + const [loadDataOpen, setLoadDataOpen] = useState(true); + const [createTableOpen, setCreateTableOpen] = useState(true); + const [tableOpsOpen, setTableOpsOpen] = useState(true); + const [scanResultsOpen, setScanResultsOpen] = useState(true); + + // Auto-collapse creation sections once a table exists + const [hasAutoCollapsed, setHasAutoCollapsed] = useState(false); + useEffect(() => { + if (tables.length > 0 && !hasAutoCollapsed) { + setQuickFillOpen(false); + setCreateTableOpen(false); + setLoadDataOpen(false); + setHasAutoCollapsed(true); + } + }, [tables, hasAutoCollapsed]); + + return ( +
+ {!engineReady ? ( + + ) : ( + <> + {/* Engine summary bar */} +
+ Page: {engineConfig?.page_size}B + Pool: {engineConfig?.pool_size} frames + Disk: {engineConfig?.disk_capacity} pages + +
+ + setQuickFillOpen((v) => !v)} /> + setLoadDataOpen((v) => !v)} /> + setCreateTableOpen((v) => !v)} /> + setTableOpsOpen((v) => !v)} /> + setScanResultsOpen((v) => !v)} /> + + )} + + {/* Status */} + {statusMsg && ( +
+ {statusMsg} +
+ )} +
+ ); +} diff --git a/frontend/src/components/panels/TableOperationsPanel.tsx b/frontend/src/components/panels/TableOperationsPanel.tsx new file mode 100644 index 0000000..9db4634 --- /dev/null +++ b/frontend/src/components/panels/TableOperationsPanel.tsx @@ -0,0 +1,367 @@ +import { useState } from 'react'; +import { useStorageStore } from '@/stores/storage'; +import CollapsibleSection from '@/components/layout/CollapsibleSection'; +import { formatColType, formatBytes } from '@/wasm/storage-types'; + +interface TableOperationsPanelProps { + open: boolean; + onToggle: () => void; +} + +export default function TableOperationsPanel({ open, onToggle }: TableOperationsPanelProps) { + const tables = useStorageStore((s) => s.tables); + const selectedTable = useStorageStore((s) => s.selectedTable); + const selectTable = useStorageStore((s) => s.selectTable); + const insert = useStorageStore((s) => s.insert); + const deleteRow = useStorageStore((s) => s.deleteRow); + const getRow = useStorageStore((s) => s.getRow); + const scan = useStorageStore((s) => s.scan); + const flushAll = useStorageStore((s) => s.flushAll); + const dropTable = useStorageStore((s) => s.dropTable); + const selectPage = useStorageStore((s) => s.selectPage); + const selectedPageId = useStorageStore((s) => s.selectedPageId); + const tableInfo = useStorageStore((s) => s.tableInfo); + const engineConfig = useStorageStore((s) => s.engineConfig); + const getRowResult = useStorageStore((s) => s.getRowResult); + const clearGetResult = useStorageStore((s) => s.clearGetResult); + const scanColumns = useStorageStore((s) => s.scanColumns); + const selectedCell = useStorageStore((s) => s.selectedCell); + const selectCell = useStorageStore((s) => s.selectCell); + const clearCellSelection = useStorageStore((s) => s.clearCellSelection); + const pageSnapshot = useStorageStore((s) => s.pageSnapshot); + + const [insertValues, setInsertValues] = useState(''); + const [rowIdInput, setRowIdInput] = useState(''); + + function doInsert() { + if (!selectedTable || !insertValues.trim()) return; + try { + const vals = JSON.parse(`[${insertValues}]`); + insert(selectedTable, vals); + setInsertValues(''); + } catch { + insert(selectedTable, []); + } + } + + function doGet() { + if (!selectedTable || !rowIdInput.trim()) return; + getRow(selectedTable, rowIdInput.trim()); + } + + function doDelete() { + if (!selectedTable || !rowIdInput.trim()) return; + deleteRow(selectedTable, rowIdInput.trim()); + } + + function doScan() { + if (!selectedTable) return; + scan(selectedTable); + } + + if (tables.length === 0) return null; + + return ( + + {/* Table tabs */} +
+ {tables.map((t) => ( + + ))} +
+ + {selectedTable && ( + <> + {/* Table Info */} + {tableInfo && ( +
+
+ {tableInfo.columns.map((col, i) => ( + + {col.name}: {formatColType(col.type)} + + ))} +
+
+ {tableInfo.rowCount} rows + · + {tableInfo.pageIds.length} pages + · + {formatBytes(tableInfo.pageIds.length * (engineConfig?.page_size ?? 0))} +
+ {tableInfo.pageIds.length > 0 && ( +
+ {tableInfo.pageIds.map((pgId, i) => ( + + + {i < tableInfo.pageIds.length - 1 && ( + + )} + + ))} +
+ )} +
+ )} + + {/* Insert */} + Insert Row +
+ setInsertValues(e.target.value)} + placeholder='42, "Alice", 3.14, true' + className="flex-1 bg-white/[0.06] border border-white/12 rounded px-1.5 py-1 text-white font-mono text-[11px] outline-none focus:border-[#007acc]" + onKeyDown={(e) => e.key === 'Enter' && doInsert()} + /> + +
+ + {/* Get / Delete */} + Row ID +
+ setRowIdInput(e.target.value)} + placeholder="0:0" + className="bg-white/[0.06] border border-white/12 rounded px-1.5 py-1 text-white font-mono text-[11px] outline-none focus:border-[#007acc]" + onKeyDown={(e) => e.key === 'Enter' && doGet()} + /> + + +
+ + {/* Get Row Result */} + {getRowResult && ( + + )} + + {/* Actions */} +
+ + + +
+ + )} +
+ ); +} + +// ── Get Row Result sub-component ── + +function GetRowResultView({ + result, + scanColumns, + selectedCell, + selectCell, + clearCellSelection, + clearGetResult, + tableInfo, + pageSnapshot, +}: { + result: { rowId: string; values: unknown[] }; + scanColumns: string[]; + selectedCell: { rowId: string; colIndex: number; value: unknown } | null; + selectCell: (rowId: string, colIndex: number, value: unknown) => void; + clearCellSelection: () => void; + clearGetResult: () => void; + tableInfo: import('@/wasm/storage-types').TableInfo | null; + pageSnapshot: import('@/wasm/storage-types').PageSnapshot | null; +}) { + const columns = scanColumns.length > 0 ? scanColumns : result.values.map((_, i) => `col${i}`); + + return ( +
+
+ Row {result.rowId} + +
+
+ + + + + {columns.map((colName) => ( + + ))} + + + + + + {result.values.map((val, colIdx) => ( + + ))} + + +
+ RowID + + {colName} +
+ {result.rowId} + { + e.stopPropagation(); + selectCell(result.rowId, colIdx, val); + }} + > + {val === null ? 'NULL' : String(val)} +
+
+ {selectedCell && selectedCell.rowId === result.rowId && tableInfo && ( + + )} +
+ ); +} + +// ── Cell Info sub-component (shared by scan results and get result) ── + +import { estimateByteSize, colTypeName } from '@/wasm/storage-types'; +import type { TableInfo, PageSnapshot } from '@/wasm/storage-types'; + +export function CellInfoView({ + cell, + tableInfo, + pageSnapshot, + onClose, +}: { + cell: { rowId: string; colIndex: number; value: unknown }; + tableInfo: TableInfo; + pageSnapshot: PageSnapshot | null; + onClose: () => void; +}) { + const col = tableInfo.columns[cell.colIndex]; + const parts = cell.rowId.split(':'); + const pgId = parseInt(parts[0], 10); + const slotIdx = parseInt(parts[1], 10); + const byteSize = col ? estimateByteSize(col.type, cell.value) : 0; + const slot = pageSnapshot && slotIdx < pageSnapshot.slots.length ? pageSnapshot.slots[slotIdx] : null; + + return ( +
+ +
+ + "{cell.value === null ? 'NULL' : String(cell.value)}" + +
+
+ {col && ( + <> + + Column: {col.name} ({formatColType(col.type)}) + + + Encoded size: ~{byteSize}B as {colTypeName(col.type)} + {typeof col.type === 'object' && 'VarChar' in col.type ? ` (2B len + ${byteSize - 2}B UTF-8)` : ''} + + + )} + + Location: Page {pgId}, Slot [{slotIdx}] + {slot && slot.length > 0 ? `, tuple offset ${slot.offset}, ${slot.length}B total` : ''} + + {col && ( + + Field offset in tuple: ~byte{' '} + {tableInfo.columns + .slice(0, cell.colIndex) + .reduce((acc, c) => acc + estimateByteSize(c.type, null), 0)} + + )} +
+
+ ); +} diff --git a/frontend/src/hooks/useCanvas.ts b/frontend/src/hooks/useCanvas.ts new file mode 100644 index 0000000..84d6f42 --- /dev/null +++ b/frontend/src/hooks/useCanvas.ts @@ -0,0 +1,74 @@ +import { useRef, useEffect, useCallback, useState } from 'react'; + +export const MONO = "'SF Mono','Cascadia Code','Fira Code',Consolas,monospace"; +export const PAD = 12; + +interface UseCanvasOptions { + render: (ctx: CanvasRenderingContext2D, width: number, height: number) => void; + computeHeight: (width: number, viewportHeight: number) => number; + deps: unknown[]; + onClick?: (e: React.MouseEvent, width: number) => void; +} + +export function useCanvas({ render, computeHeight, deps, onClick }: UseCanvasOptions) { + const containerRef = useRef(null); + const canvasRef = useRef(null); + const [width, setWidth] = useState(0); + const [viewportHeight, setViewportHeight] = useState(0); + const rafId = useRef(0); + + // ResizeObserver + useEffect(() => { + const el = containerRef.current; + if (!el) return; + const ro = new ResizeObserver((entries) => { + const r = entries[0].contentRect; + setWidth(r.width); + setViewportHeight(r.height); + }); + ro.observe(el); + return () => ro.disconnect(); + }, []); + + const canvasHeight = width > 0 ? computeHeight(width, viewportHeight) : viewportHeight; + + // Schedule render via RAF + const scheduleRender = useCallback(() => { + if (rafId.current) return; + rafId.current = requestAnimationFrame(() => { + rafId.current = 0; + const canvas = canvasRef.current; + if (!canvas || width === 0) return; + const dpr = window.devicePixelRatio || 1; + const h = canvasHeight; + canvas.width = width * dpr; + canvas.height = h * dpr; + const ctx = canvas.getContext('2d'); + if (!ctx) return; + ctx.scale(dpr, dpr); + ctx.clearRect(0, 0, width, h); + render(ctx, width, h); + }); + }, [render, width, canvasHeight]); + + // Trigger render on deps change + useEffect(() => { + scheduleRender(); + }, [scheduleRender, ...deps]); + + const handleClick = useCallback( + (e: React.MouseEvent) => { + onClick?.(e, width); + }, + [onClick, width], + ); + + return { + containerRef, + canvasRef, + width, + viewportHeight, + canvasHeight, + handleClick, + }; +} diff --git a/frontend/src/hooks/useMediaQuery.ts b/frontend/src/hooks/useMediaQuery.ts new file mode 100644 index 0000000..d3b6dbb --- /dev/null +++ b/frontend/src/hooks/useMediaQuery.ts @@ -0,0 +1,17 @@ +import { useState, useEffect } from 'react'; + +export function useMediaQuery(query: string): boolean { + const [matches, setMatches] = useState(() => + typeof window !== 'undefined' ? window.matchMedia(query).matches : false, + ); + + useEffect(() => { + const mql = window.matchMedia(query); + setMatches(mql.matches); + const handler = (e: MediaQueryListEvent) => setMatches(e.matches); + mql.addEventListener('change', handler); + return () => mql.removeEventListener('change', handler); + }, [query]); + + return matches; +} diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 0000000..8a4be0b --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,48 @@ +@import "tailwindcss"; + +@theme { + --font-mono: 'SF Mono', 'Cascadia Code', 'Fira Code', Consolas, monospace; + --font-sans: Inter, system-ui, -apple-system, sans-serif; + + --color-surface: #1e1e1e; + --color-surface-hover: rgba(255, 255, 255, 0.06); + --color-surface-active: rgba(255, 255, 255, 0.1); + --color-border: rgba(255, 255, 255, 0.06); + --color-border-strong: rgba(255, 255, 255, 0.12); + + --color-text: rgba(255, 255, 255, 0.87); + --color-text-muted: rgba(255, 255, 255, 0.5); + --color-text-dim: rgba(255, 255, 255, 0.35); + --color-text-faint: rgba(255, 255, 255, 0.2); + + --color-accent: #007acc; + --color-accent-hover: #005f99; + --color-success: #4ade80; + --color-danger: #f87171; + --color-warning: #fbbf24; + --color-info: #60a5fa; + --color-purple: #c084fc; +} + +@layer base { + body { + background: var(--color-surface); + color: var(--color-text); + font-family: var(--font-sans); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + overflow: hidden; + overscroll-behavior: none; + } + + input[type='number'] { + -moz-appearance: textfield; + } + input[type='number']::-webkit-inner-spin-button, + input[type='number']::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; + } +} diff --git a/frontend/src/lib/assets/favicon.svg b/frontend/src/lib/assets/favicon.svg deleted file mode 100644 index cc5dc66..0000000 --- a/frontend/src/lib/assets/favicon.svg +++ /dev/null @@ -1 +0,0 @@ -svelte-logo \ No newline at end of file diff --git a/frontend/src/lib/components/CollapsibleSection.svelte b/frontend/src/lib/components/CollapsibleSection.svelte deleted file mode 100644 index c8bf1d8..0000000 --- a/frontend/src/lib/components/CollapsibleSection.svelte +++ /dev/null @@ -1,96 +0,0 @@ - - -
- - {#if description && open} -

{description}

- {/if} - {#if open} -
- {@render children()} -
- {/if} -
- - diff --git a/frontend/src/lib/components/PaneDivider.svelte b/frontend/src/lib/components/PaneDivider.svelte deleted file mode 100644 index e2ada66..0000000 --- a/frontend/src/lib/components/PaneDivider.svelte +++ /dev/null @@ -1,57 +0,0 @@ - - - -
- - diff --git a/frontend/src/lib/components/PaneHeader.svelte b/frontend/src/lib/components/PaneHeader.svelte deleted file mode 100644 index 1501f2c..0000000 --- a/frontend/src/lib/components/PaneHeader.svelte +++ /dev/null @@ -1,91 +0,0 @@ - - -{#if vertical} - -{:else} - -{/if} - - diff --git a/frontend/src/lib/components/storage/BufferPoolCanvas.svelte b/frontend/src/lib/components/storage/BufferPoolCanvas.svelte deleted file mode 100644 index ff15c95..0000000 --- a/frontend/src/lib/components/storage/BufferPoolCanvas.svelte +++ /dev/null @@ -1,220 +0,0 @@ - - -
- -
- - diff --git a/frontend/src/lib/components/storage/DiskCanvas.svelte b/frontend/src/lib/components/storage/DiskCanvas.svelte deleted file mode 100644 index 339e809..0000000 --- a/frontend/src/lib/components/storage/DiskCanvas.svelte +++ /dev/null @@ -1,203 +0,0 @@ - - -
- -
- - diff --git a/frontend/src/lib/components/storage/PageInspectorCanvas.svelte b/frontend/src/lib/components/storage/PageInspectorCanvas.svelte deleted file mode 100644 index 60de4ea..0000000 --- a/frontend/src/lib/components/storage/PageInspectorCanvas.svelte +++ /dev/null @@ -1,386 +0,0 @@ - - -
-
- - - {#if storageState.pageSnapshot} - - {/if} -
-
- -
-
- - diff --git a/frontend/src/lib/components/storage/StorageControlPanel.svelte b/frontend/src/lib/components/storage/StorageControlPanel.svelte deleted file mode 100644 index abb2d3b..0000000 --- a/frontend/src/lib/components/storage/StorageControlPanel.svelte +++ /dev/null @@ -1,1043 +0,0 @@ - - -
- {#if !storageState.engineReady} - -
-

Engine Configuration

-

Configure the storage engine's page size, buffer pool capacity, and disk size

- - Page Size -
- {#each PAGE_PRESETS as preset} - - {/each} -
- -
- - -
- - - - -
- {:else} - -
- Page: {storageState.config?.page_size}B - Pool: {storageState.config?.pool_size} frames - Disk: {storageState.config?.disk_capacity} pages - -
- - - - -
- {#each qfColumns as col, i} -
- - - {#if col.type === 'VarChar' || col.type === 'Blob'} - - {/if} - - -
- {/each} -
-
- -
- - Row Count -
- {#each ROW_PRESETS as preset} - - {/each} -
- - -
- - - - -
- - -
- - {#if ldError} - {ldError} - {/if} - - {#if ldParsed} - {ldRows.length} rows, {ldColumns.length} columns -
- {#each ldColumns as col} - - {col.name}: {formatColType(col.type)}{col.nullable ? '?' : ''} - - {/each} -
- - {/if} -
- - - - -
- {#each newColumns as col, i} -
- - - {#if col.type === 'VarChar' || col.type === 'Blob'} - - {/if} - - -
- {/each} -
-
- - -
-
- - - {#if storageState.tables.length > 0} - -
- {#each storageState.tables as t} - - {/each} -
- - {#if storageState.selectedTable} - - {#if storageState.tableInfo} - {@const info = storageState.tableInfo} -
-
- {#each info.columns as col} - - {col.name}: {formatColType(col.type)} - - {/each} -
-
- {info.rowCount} rows - - {info.pageIds.length} pages - - {formatBytes(info.pageIds.length * (storageState.config?.page_size ?? 0))} -
- {#if info.pageIds.length > 0} -
- {#each info.pageIds as pgId, i} - - {#if i < info.pageIds.length - 1} - - {/if} - {/each} -
- {/if} -
- {/if} - - - Insert Row -
- e.key === 'Enter' && doInsert()} - /> - -
- - - Row ID -
- e.key === 'Enter' && doGet()} - /> - - -
- - - {#if storageState.getRowResult} - {@const result = storageState.getRowResult} -
-
- Row {result.rowId} - -
-
- - - - - {#each storageState.scanColumns as colName} - - {:else} - {#each result.values as _, i} - - {/each} - {/each} - - - - - - {#each result.values as val, colIdx} - - {/each} - - -
RowID{colName}col{i}
{result.rowId} handleCellClick(e, result.rowId, colIdx, val)} - >{val === null ? 'NULL' : String(val)}
-
- - {#if storageState.selectedCell && storageState.selectedCell.rowId === result.rowId && storageState.tableInfo} - {@const cell = storageState.selectedCell} - {@const col = storageState.tableInfo.columns[cell.colIndex]} - {@const parts = cell.rowId.split(':')} - {@const pgId = parseInt(parts[0], 10)} - {@const slotIdx = parseInt(parts[1], 10)} - {@const byteSize = col ? estimateByteSize(col.type, cell.value) : 0} - {@const snap = storageState.pageSnapshot} - {@const slot = snap && slotIdx < snap.slots.length ? snap.slots[slotIdx] : null} -
- -
- "{cell.value === null ? 'NULL' : String(cell.value)}" -
-
- {#if col} - Column: {col.name} ({formatColType(col.type)}) - Encoded size: ~{byteSize}B as {colTypeName(col.type)}{typeof col.type === 'object' && 'VarChar' in col.type ? ` (2B len + ${byteSize - 2}B UTF-8)` : ''} - {/if} - Location: Page {pgId}, Slot [{slotIdx}]{#if slot && slot.length > 0}, tuple offset {slot.offset}, {slot.length}B total{/if} - {#if col} - {@const offsetInTuple = storageState.tableInfo.columns.slice(0, cell.colIndex).reduce((acc, c) => acc + estimateByteSize(c.type, null), 0)} - Field offset in tuple: ~byte {offsetInTuple} - {/if} -
-
- {/if} -
- {/if} - - -
- - - -
- {/if} -
- {/if} - - - {#if storageState.scanResults.length > 0} - - -
- - {#if filterText.trim()} - - Showing {filteredResults.length} of {storageState.scanResults.length} rows - - {/if} -
- -
- - - - - {#each storageState.scanColumns as colName} - - {:else} - {#each storageState.scanResults[0]?.values ?? [] as _, i} - - {/each} - {/each} - - - - {#each filteredResults as row} - {@const isRowSelected = storageState.selectedSlotId !== null && (() => { - const parts = row.row_id.split(':'); - return parseInt(parts[0], 10) === storageState.selectedPageId && parseInt(parts[1], 10) === storageState.selectedSlotId; - })()} - handleRowClick(row.row_id)} - > - - {#each row.values as val, colIdx} - - {/each} - - {/each} - -
RowID{colName}col{i}
{row.row_id} handleCellClick(e, row.row_id, colIdx, val)} - >{val === null ? 'NULL' : String(val)}
-
- - - {#if storageState.selectedCell && storageState.tableInfo} - {@const cell = storageState.selectedCell} - {@const col = storageState.tableInfo.columns[cell.colIndex]} - {@const parts = cell.rowId.split(':')} - {@const pgId = parseInt(parts[0], 10)} - {@const slotIdx = parseInt(parts[1], 10)} - {@const byteSize = col ? estimateByteSize(col.type, cell.value) : 0} - {@const snap = storageState.pageSnapshot} - {@const slot = snap && slotIdx < snap.slots.length ? snap.slots[slotIdx] : null} -
- -
- "{cell.value === null ? 'NULL' : String(cell.value)}" -
-
- {#if col} - Column: {col.name} ({formatColType(col.type)}) - Encoded size: ~{byteSize}B as {colTypeName(col.type)}{typeof col.type === 'object' && 'VarChar' in col.type ? ` (2B len + ${byteSize - 2}B UTF-8)` : ''} - {/if} - Location: Page {pgId}, Slot [{slotIdx}]{#if slot && slot.length > 0}, tuple offset {slot.offset}, {slot.length}B total{/if} - {#if col} - {@const offsetInTuple = storageState.tableInfo.columns.slice(0, cell.colIndex).reduce((acc, c) => acc + estimateByteSize(c.type, null), 0)} - Field offset in tuple: ~byte {offsetInTuple} - {/if} -
-
- {/if} -
- {/if} - {/if} - - - {#if storageState.statusMsg} -
- {storageState.statusMsg} -
- {/if} -
- - diff --git a/frontend/src/lib/index.ts b/frontend/src/lib/index.ts deleted file mode 100644 index 856f2b6..0000000 --- a/frontend/src/lib/index.ts +++ /dev/null @@ -1 +0,0 @@ -// place files you want to import through the `$lib` alias in this folder. diff --git a/frontend/src/lib/stores/layout.svelte.ts b/frontend/src/lib/stores/layout.svelte.ts deleted file mode 100644 index 431d432..0000000 --- a/frontend/src/lib/stores/layout.svelte.ts +++ /dev/null @@ -1,32 +0,0 @@ -// Pane layout state: ratios and collapse toggles - -let topBottomRatio = $state(0.5); -let leftRightRatio = $state(0.5); -let sidebarCollapsed = $state(false); -let bufferPoolCollapsed = $state(false); -let diskCollapsed = $state(false); -let inspectorCollapsed = $state(false); - -const MIN_RATIO = 0.15; -const MAX_RATIO = 0.85; - -function clampRatio(v: number): number { - return Math.min(MAX_RATIO, Math.max(MIN_RATIO, v)); -} - -export const layoutState = { - get topBottomRatio() { return topBottomRatio; }, - get leftRightRatio() { return leftRightRatio; }, - get sidebarCollapsed() { return sidebarCollapsed; }, - get bufferPoolCollapsed() { return bufferPoolCollapsed; }, - get diskCollapsed() { return diskCollapsed; }, - get inspectorCollapsed() { return inspectorCollapsed; }, - - setTopBottomRatio(v: number) { topBottomRatio = clampRatio(v); }, - setLeftRightRatio(v: number) { leftRightRatio = clampRatio(v); }, - - toggleSidebar() { sidebarCollapsed = !sidebarCollapsed; }, - toggleBufferPool() { bufferPoolCollapsed = !bufferPoolCollapsed; }, - toggleDisk() { diskCollapsed = !diskCollapsed; }, - toggleInspector() { inspectorCollapsed = !inspectorCollapsed; }, -}; diff --git a/frontend/src/lib/stores/storage.svelte.ts b/frontend/src/lib/stores/storage.svelte.ts deleted file mode 100644 index 98d453c..0000000 --- a/frontend/src/lib/stores/storage.svelte.ts +++ /dev/null @@ -1,410 +0,0 @@ -import { StorageEngineWrapper, initStorageWasm } from '$lib/wasm/storage-engine.js'; -import type { - EngineConfig, - BufferPoolSnapshot, - DiskSnapshot, - PageSnapshot, - TableInfo, - ColumnDef, - ScanRow, -} from '$lib/wasm/storage-types.js'; -import { formatBytes, parseCSV, inferColumnTypes } from '$lib/wasm/storage-types.js'; - -// ── Reactive state using Svelte 5 runes ──────────────────────────── - -let engine: StorageEngineWrapper | null = $state(null); -let engineConfig: EngineConfig | null = $state(null); -let bpSnapshot: BufferPoolSnapshot | null = $state(null); -let diskSnap: DiskSnapshot | null = $state(null); -let selectedPageId: number | null = $state(null); -let pageSnap: PageSnapshot | null = $state(null); -let tables: string[] = $state([]); -let selectedTable: string | null = $state(null); -let scanResults: ScanRow[] = $state([]); -let scanColumns: string[] = $state([]); -let tableInfo: TableInfo | null = $state(null); -let selectedSlotId: number | null = $state(null); -let selectedCell: { rowId: string; colIndex: number; value: unknown } | null = $state(null); -let getRowResult: { rowId: string; values: unknown[] } | null = $state(null); -let statusMsg: string = $state(''); -let statusType: 'info' | 'success' | 'error' = $state('info'); -let wasmReady = $state(false); -let engineReady = $state(false); - -function refreshSnapshots() { - if (!engine) return; - bpSnapshot = engine.snapshotBufferPool(); - diskSnap = engine.snapshotDisk(); - if (selectedPageId !== null) { - pageSnap = engine.snapshotPage(selectedPageId); - } - if (selectedTable) { - tableInfo = engine.snapshotTable(selectedTable); - } -} - -function setStatus(msg: string, type: 'info' | 'success' | 'error' = 'info') { - statusMsg = msg; - statusType = type; -} - -// ── Random data generation helpers ───────────────────────────────── - -const SAMPLE_NAMES = [ - 'Alice', 'Bob', 'Charlie', 'Diana', 'Eve', 'Frank', 'Grace', 'Hank', - 'Ivy', 'Jack', 'Karen', 'Leo', 'Mona', 'Nate', 'Olivia', 'Pete', - 'Quinn', 'Rose', 'Sam', 'Tina', 'Uma', 'Vince', 'Wendy', 'Xander', -]; - -const SAMPLE_EMAILS = [ - 'alice@example.com', 'bob@test.org', 'charlie@mail.co', 'diana@work.net', - 'eve@inbox.io', 'frank@demo.dev', 'grace@site.com', 'hank@corp.biz', - 'ivy@web.app', 'jack@hello.org', 'karen@data.io', 'leo@tech.co', -]; - -const SAMPLE_CITIES = [ - 'New York', 'London', 'Tokyo', 'Paris', 'Berlin', 'Sydney', - 'Toronto', 'Mumbai', 'Seoul', 'Dubai', 'Oslo', 'Lima', -]; - -const SAMPLE_STRINGS = [...SAMPLE_NAMES, ...SAMPLE_EMAILS, ...SAMPLE_CITIES]; - -function randomValue(colType: string | { VarChar: number } | { Blob: number }, nullable: boolean): unknown { - if (nullable && Math.random() < 0.1) return null; - - if (typeof colType === 'object') { - if ('VarChar' in colType) { - const s = SAMPLE_STRINGS[Math.floor(Math.random() * SAMPLE_STRINGS.length)]; - return s.length > colType.VarChar ? s.slice(0, colType.VarChar) : s; - } - // Blob — small random hex string - const len = Math.min(8, ('Blob' in colType ? colType.Blob : 8)); - const bytes = Array.from({ length: len }, () => Math.floor(Math.random() * 256)); - return bytes.map(b => b.toString(16).padStart(2, '0')).join(''); - } - - switch (colType) { - case 'Int32': return Math.floor(Math.random() * 2001) - 1000; - case 'UInt32': return Math.floor(Math.random() * 10001); - case 'Float64': return Math.round(Math.random() * 10000) / 100; - case 'Bool': return Math.random() > 0.5; - default: return 0; - } -} - -function coerceValue(raw: string, colType: string | { VarChar: number } | { Blob: number }): unknown { - if (raw === '') return null; - if (typeof colType === 'object') return raw; // VarChar / Blob → string as-is - switch (colType) { - case 'Int32': case 'UInt32': return parseInt(raw, 10); - case 'Float64': return parseFloat(raw); - case 'Bool': { - const lower = raw.toLowerCase(); - return lower === 'true' || lower === '1'; - } - default: return raw; - } -} - -// ── Public API ───────────────────────────────────────────────────── - -export const storageState = { - get engine() { return engine; }, - get config() { return engineConfig; }, - get bpSnapshot() { return bpSnapshot; }, - get diskSnapshot() { return diskSnap; }, - get selectedPageId() { return selectedPageId; }, - get pageSnapshot() { return pageSnap; }, - get tables() { return tables; }, - get selectedTable() { return selectedTable; }, - get scanResults() { return scanResults; }, - get scanColumns() { return scanColumns; }, - get tableInfo() { return tableInfo; }, - get selectedSlotId() { return selectedSlotId; }, - get selectedCell() { return selectedCell; }, - get getRowResult() { return getRowResult; }, - get statusMsg() { return statusMsg; }, - get statusType() { return statusType; }, - get wasmReady() { return wasmReady; }, - get engineReady() { return engineReady; }, - - async initWasm() { - await initStorageWasm(); - wasmReady = true; - }, - - initEngine(config: EngineConfig) { - if (engine) { - engine.destroy(); - } - engine = new StorageEngineWrapper(config); - engineConfig = engine.config(); - engineReady = true; - tables = []; - selectedTable = null; - scanResults = []; - scanColumns = []; - tableInfo = null; - selectedSlotId = null; - selectedCell = null; - getRowResult = null; - selectedPageId = null; - pageSnap = null; - refreshSnapshots(); - setStatus('Engine initialized', 'success'); - }, - - resetEngine() { - if (engine) { - engine.destroy(); - engine = null; - } - engineConfig = null; - engineReady = false; - bpSnapshot = null; - diskSnap = null; - pageSnap = null; - selectedPageId = null; - tables = []; - selectedTable = null; - scanResults = []; - scanColumns = []; - tableInfo = null; - selectedSlotId = null; - selectedCell = null; - getRowResult = null; - setStatus('Engine reset', 'info'); - }, - - createTable(name: string, columns: ColumnDef[]) { - if (!engine) return; - try { - engine.createTable(name, columns); - tables = engine.listTables(); - if (!selectedTable) selectedTable = name; - refreshSnapshots(); - setStatus(`Table '${name}' created`, 'success'); - } catch (e: unknown) { - setStatus(String(e), 'error'); - } - }, - - dropTable(name: string) { - if (!engine) return; - engine.dropTable(name); - tables = engine.listTables(); - if (selectedTable === name) { - selectedTable = tables.length > 0 ? tables[0] : null; - } - scanResults = []; - scanColumns = []; - tableInfo = selectedTable && engine ? engine.snapshotTable(selectedTable) : null; - selectedSlotId = null; - selectedCell = null; - getRowResult = null; - refreshSnapshots(); - setStatus(`Table '${name}' dropped`, 'success'); - }, - - selectTable(name: string) { - selectedTable = name; - scanResults = []; - scanColumns = []; - selectedSlotId = null; - selectedCell = null; - getRowResult = null; - if (engine) { - tableInfo = engine.snapshotTable(name); - } - }, - - insert(tableName: string, values: unknown[]) { - if (!engine) return; - try { - const rowId = engine.insert(tableName, values); - refreshSnapshots(); - // Richer status: show page info if available - const parts = rowId.split(':'); - const pgId = parseInt(parts[0], 10); - const snap = engine.snapshotPage(pgId); - if (snap) { - setStatus(`Inserted row ${rowId} on Page ${pgId} (${snap.freeSpace}B free remaining)`, 'success'); - } else { - setStatus(`Inserted row ${rowId}`, 'success'); - } - } catch (e: unknown) { - setStatus(String(e), 'error'); - } - }, - - deleteRow(tableName: string, rowId: string) { - if (!engine) return; - try { - const ok = engine.delete(tableName, rowId); - refreshSnapshots(); - setStatus( - ok ? `Deleted row ${rowId} — slot tombstoned (space not yet reclaimed)` : 'Row not found', - ok ? 'success' : 'error' - ); - } catch (e: unknown) { - setStatus(String(e), 'error'); - } - }, - - getRow(tableName: string, rowId: string) { - if (!engine) return; - try { - const values = engine.get(tableName, rowId); - getRowResult = { rowId, values }; - // Ensure column names are available - if (scanColumns.length === 0) { - const schema = engine.tableSchema(tableName); - scanColumns = schema ? schema.columns.map(c => c.name) : []; - } - // Also navigate to its page/slot - const parts = rowId.split(':'); - if (parts.length === 2) { - const pgId = parseInt(parts[0], 10); - const slotIdx = parseInt(parts[1], 10); - if (!isNaN(pgId) && !isNaN(slotIdx)) { - selectedPageId = pgId; - pageSnap = engine.snapshotPage(pgId); - selectedSlotId = slotIdx; - } - } - refreshSnapshots(); - setStatus(`Got row ${rowId}`, 'success'); - } catch (e: unknown) { - getRowResult = null; - setStatus(String(e), 'error'); - } - }, - - selectCell(rowId: string, colIndex: number, value: unknown) { - selectedCell = { rowId, colIndex, value }; - // Also navigate to the row's page/slot - const parts = rowId.split(':'); - if (parts.length === 2) { - const pgId = parseInt(parts[0], 10); - const slotIdx = parseInt(parts[1], 10); - if (!isNaN(pgId) && !isNaN(slotIdx)) { - selectedPageId = pgId; - if (engine) { - pageSnap = engine.snapshotPage(pgId); - } - selectedSlotId = slotIdx; - } - } - }, - - clearCellSelection() { - selectedCell = null; - }, - - clearGetResult() { - getRowResult = null; - selectedCell = null; - }, - - scan(tableName: string) { - if (!engine) return; - try { - scanResults = engine.scan(tableName); - const schema = engine.tableSchema(tableName); - scanColumns = schema ? schema.columns.map(c => c.name) : []; - refreshSnapshots(); - setStatus(`Scanned ${scanResults.length} rows`, 'success'); - } catch (e: unknown) { - setStatus(String(e), 'error'); - } - }, - - selectPage(pageId: number | null) { - selectedPageId = pageId; - if (engine && pageId !== null) { - pageSnap = engine.snapshotPage(pageId); - } else { - pageSnap = null; - } - }, - - flushPage(pageId: number) { - if (!engine) return; - engine.flushPage(pageId); - refreshSnapshots(); - setStatus(`Flushed page ${pageId}`, 'success'); - }, - - selectSlot(slotId: number | null) { - selectedSlotId = slotId; - }, - - selectRowFromScan(rowId: string) { - // rowId is in "pageId:slotId" format - const parts = rowId.split(':'); - if (parts.length !== 2) return; - const pgId = parseInt(parts[0], 10); - const slotIdx = parseInt(parts[1], 10); - if (isNaN(pgId) || isNaN(slotIdx)) return; - - // Select the page and slot - selectedPageId = pgId; - if (engine) { - pageSnap = engine.snapshotPage(pgId); - } - selectedSlotId = slotIdx; - }, - - flushAll() { - if (!engine) return; - engine.flushAll(); - refreshSnapshots(); - setStatus('Flushed all dirty pages', 'success'); - }, - - bootstrapTable(name: string, columns: ColumnDef[], rowCount: number) { - if (!engine) return; - try { - // Build typed column defs for the engine - engine.createTable(name, columns); - tables = engine.listTables(); - if (!selectedTable) selectedTable = name; - - for (let i = 0; i < rowCount; i++) { - const row = columns.map(c => randomValue(c.type, c.nullable)); - engine.insert(name, row); - } - - refreshSnapshots(); - setStatus(`Created '${name}' with ${rowCount} rows`, 'success'); - } catch (e: unknown) { - setStatus(String(e), 'error'); - } - }, - - loadFromCSV(tableName: string, headers: string[], rows: string[][], columns: ColumnDef[]) { - if (!engine) return; - try { - engine.createTable(tableName, columns); - tables = engine.listTables(); - if (!selectedTable) selectedTable = tableName; - - for (const row of rows) { - const values = columns.map((col, i) => { - const raw = i < row.length ? row[i] : ''; - return raw === '' && col.nullable ? null : coerceValue(raw, col.type); - }); - engine.insert(tableName, values); - } - - refreshSnapshots(); - setStatus(`Loaded '${tableName}' with ${rows.length} rows from CSV`, 'success'); - } catch (e: unknown) { - setStatus(String(e), 'error'); - } - }, - - parseCSV, - inferColumnTypes, - refreshSnapshots, -}; diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts new file mode 100644 index 0000000..9ad0df4 --- /dev/null +++ b/frontend/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/frontend/src/lib/wasm/storage-engine.ts b/frontend/src/lib/wasm/storage-engine.ts deleted file mode 100644 index 5d1cd5f..0000000 --- a/frontend/src/lib/wasm/storage-engine.ts +++ /dev/null @@ -1,102 +0,0 @@ -import init, { StorageEngine as WasmStorageEngine } from 'wizalloc-engine'; -import wasmUrl from 'wizalloc-engine/wizalloc_engine_bg.wasm?url'; -import { - type EngineConfig, - type BufferPoolSnapshot, - type DiskSnapshot, - type PageSnapshot, - type TableInfo, - type ScanRow, - type ColumnDef, - decodeBufferPool, - decodeDisk, - decodePage, - decodeTable, -} from './storage-types.js'; - -let initialized = false; - -export async function initStorageWasm(): Promise { - if (initialized) return; - await init({ module_or_path: wasmUrl }); - initialized = true; -} - -/** Thin wrapper around the WASM StorageEngine. */ -export class StorageEngineWrapper { - private inner: WasmStorageEngine; - - constructor(config: EngineConfig) { - this.inner = new WasmStorageEngine(JSON.stringify(config)); - } - - config(): EngineConfig { - return JSON.parse(this.inner.config()); - } - - createTable(name: string, columns: ColumnDef[]): void { - const schema = JSON.stringify({ columns }); - this.inner.create_table(name, schema); - } - - dropTable(name: string): boolean { - return this.inner.drop_table(name); - } - - listTables(): string[] { - return JSON.parse(this.inner.list_tables()); - } - - insert(tableName: string, values: unknown[]): string { - return this.inner.insert(tableName, JSON.stringify(values)); - } - - get(tableName: string, rowId: string): unknown[] { - return JSON.parse(this.inner.get(tableName, rowId)); - } - - delete(tableName: string, rowId: string): boolean { - return this.inner.delete(tableName, rowId); - } - - scan(tableName: string): ScanRow[] { - return JSON.parse(this.inner.scan(tableName)); - } - - flushAll(): void { - this.inner.flush_all(); - } - - flushPage(pageId: number): boolean { - return this.inner.flush_page(pageId); - } - - tableSchema(tableName: string): { columns: ColumnDef[] } | null { - const json = this.inner.table_schema(tableName); - return json ? JSON.parse(json) : null; - } - - // ── Snapshots ────────────────────────────────────────────── - - snapshotBufferPool(): BufferPoolSnapshot { - return decodeBufferPool(this.inner.snapshot_buffer_pool()); - } - - snapshotDisk(): DiskSnapshot { - return decodeDisk(this.inner.snapshot_disk()); - } - - snapshotPage(pageId: number): PageSnapshot | null { - const data = this.inner.snapshot_page(pageId); - return data ? decodePage(data) : null; - } - - snapshotTable(tableName: string): TableInfo | null { - const data = this.inner.snapshot_table(tableName); - return data ? decodeTable(data) : null; - } - - destroy(): void { - this.inner.free(); - } -} diff --git a/frontend/src/lib/wasm/storage-types.ts b/frontend/src/lib/wasm/storage-types.ts deleted file mode 100644 index 59673be..0000000 --- a/frontend/src/lib/wasm/storage-types.ts +++ /dev/null @@ -1,356 +0,0 @@ -// ── Types for Storage Engine visualization ───────────────────────── - -export const INVALID_PAGE = 0xFFFFFFFF; - -export interface EngineConfig { - page_size: number; - pool_size: number; - disk_capacity: number; - overflow_threshold: number; -} - -export interface FrameInfo { - pageId: number | null; // null if empty - pinCount: number; - isDirty: boolean; - isOccupied: boolean; -} - -export interface BufferPoolSnapshot { - poolSize: number; - pageSize: number; - frames: FrameInfo[]; - pageTable: Map; // pageId → frameId - lruOrder: number[]; // frameIds, front=LRU - hitCount: number; - missCount: number; - diskReadCount: number; - diskWriteCount: number; - diskNumAllocated: number; - diskMaxPages: number; - diskBasePtr: number; -} - -export interface DiskPageInfo { - isAllocated: boolean; - pageType: number; // 0=Data, 1=Overflow, 2=Free -} - -export interface DiskSnapshot { - maxPages: number; - pageSize: number; - numAllocated: number; - diskBasePtr: number; - pages: DiskPageInfo[]; -} - -export interface SlotInfo { - offset: number; - length: number; -} - -export interface PageSnapshot { - pageSize: number; - pageId: number; - pageType: number; - slotCount: number; - freeStart: number; - freeEnd: number; - nextPageId: number; - freeSpace: number; - slots: SlotInfo[]; - rawBytes: Uint8Array; -} - -export interface ColumnDef { - name: string; - type: string | { VarChar: number } | { Blob: number }; - nullable: boolean; -} - -export interface TableInfo { - name: string; - rowCount: number; - firstPageId: number; - columns: ColumnDef[]; - pageIds: number[]; -} - -export interface ScanRow { - row_id: string; - values: unknown[]; -} - -// ── Binary decoders ──────────────────────────────────────────────── - -class BinaryReader { - private view: DataView; - private offset = 0; - - constructor(data: Uint8Array) { - this.view = new DataView(data.buffer, data.byteOffset, data.byteLength); - } - - u8(): number { const v = this.view.getUint8(this.offset); this.offset += 1; return v; } - u16(): number { const v = this.view.getUint16(this.offset, true); this.offset += 2; return v; } - u32(): number { const v = this.view.getUint32(this.offset, true); this.offset += 4; return v; } - u64(): bigint { - const lo = this.view.getUint32(this.offset, true); - const hi = this.view.getUint32(this.offset + 4, true); - this.offset += 8; - return BigInt(hi) * BigInt(0x100000000) + BigInt(lo); - } - bytes(n: number): Uint8Array { - const slice = new Uint8Array(this.view.buffer, this.view.byteOffset + this.offset, n); - this.offset += n; - return new Uint8Array(slice); // copy - } -} - -export function decodeBufferPool(data: Uint8Array): BufferPoolSnapshot { - const r = new BinaryReader(data); - const poolSize = r.u32(); - const pageSize = r.u32(); - - const frames: FrameInfo[] = []; - for (let i = 0; i < poolSize; i++) { - const pid = r.u32(); - frames.push({ - pageId: pid === INVALID_PAGE ? null : pid, - pinCount: r.u32(), - isDirty: r.u8() !== 0, - isOccupied: r.u8() !== 0, - }); - } - - const ptLen = r.u32(); - const pageTable = new Map(); - for (let i = 0; i < ptLen; i++) { - pageTable.set(r.u32(), r.u32()); - } - - const lruLen = r.u32(); - const lruOrder: number[] = []; - for (let i = 0; i < lruLen; i++) { - lruOrder.push(r.u32()); - } - - return { - poolSize, pageSize, frames, pageTable, lruOrder, - hitCount: Number(r.u64()), - missCount: Number(r.u64()), - diskReadCount: Number(r.u64()), - diskWriteCount: Number(r.u64()), - diskNumAllocated: r.u32(), - diskMaxPages: r.u32(), - diskBasePtr: r.u32(), - }; -} - -export function decodeDisk(data: Uint8Array): DiskSnapshot { - const r = new BinaryReader(data); - const maxPages = r.u32(); - const pageSize = r.u32(); - const numAllocated = r.u32(); - const diskBasePtr = r.u32(); - - const pages: DiskPageInfo[] = []; - for (let i = 0; i < maxPages; i++) { - pages.push({ - isAllocated: r.u8() !== 0, - pageType: r.u8(), - }); - } - - return { maxPages, pageSize, numAllocated, diskBasePtr, pages }; -} - -export function decodePage(data: Uint8Array): PageSnapshot { - const r = new BinaryReader(data); - const pageSize = r.u32(); - const pageId = r.u32(); - const pageType = r.u8(); - const slotCount = r.u16(); - const freeStart = r.u16(); - const freeEnd = r.u16(); - const nextPageId = r.u32(); - const freeSpace = r.u16(); - - const numSlots = r.u16(); - const slots: SlotInfo[] = []; - for (let i = 0; i < numSlots; i++) { - slots.push({ offset: r.u16(), length: r.u16() }); - } - - const rawBytes = r.bytes(pageSize); - - return { pageSize, pageId, pageType, slotCount, freeStart, freeEnd, nextPageId, freeSpace, slots, rawBytes }; -} - -export function decodeTable(data: Uint8Array): TableInfo { - const r = new BinaryReader(data); - - const nameLen = r.u16(); - const nameBytes = r.bytes(nameLen); - const name = new TextDecoder().decode(nameBytes); - - const rowCount = r.u32(); - const firstPageId = r.u32(); - - const numCols = r.u16(); - const columns: ColumnDef[] = []; - for (let i = 0; i < numCols; i++) { - const cnLen = r.u16(); - const cnBytes = r.bytes(cnLen); - const colName = new TextDecoder().decode(cnBytes); - const typeTag = r.u8(); - const nullable = r.u8() !== 0; - const maxLen = r.u16(); - - let type_: string | { VarChar: number } | { Blob: number }; - switch (typeTag) { - case 0: type_ = 'Int32'; break; - case 1: type_ = 'UInt32'; break; - case 2: type_ = 'Float64'; break; - case 3: type_ = 'Bool'; break; - case 4: type_ = { VarChar: maxLen }; break; - case 5: type_ = { Blob: maxLen }; break; - default: type_ = 'Int32'; - } - columns.push({ name: colName, type: type_, nullable }); - } - - const pageCount = r.u32(); - const pageIds: number[] = []; - for (let i = 0; i < pageCount; i++) { - pageIds.push(r.u32()); - } - - return { name, rowCount, firstPageId, columns, pageIds }; -} - -// ── Utility ──────────────────────────────────────────────────────── - -export function pageTypeName(t: number): string { - switch (t) { - case 0: return 'Data'; - case 1: return 'Overflow'; - case 2: return 'Free'; - default: return '?'; - } -} - -export function formatHex(n: number): string { - return '0x' + n.toString(16).toUpperCase().padStart(4, '0'); -} - -export function formatBytes(n: number): string { - if (n < 1024) return `${n}B`; - return `${(n / 1024).toFixed(1)}KB`; -} - -// ── CSV utilities ────────────────────────────────────────────────── - -export function parseCSV(text: string): { headers: string[]; rows: string[][] } { - // Strip BOM, normalize line endings - let src = text.replace(/^\uFEFF/, '').replace(/\r\n/g, '\n').replace(/\r/g, '\n'); - // Trim trailing empty lines - src = src.replace(/\n+$/, ''); - - const lines: string[][] = []; - let i = 0; - - while (i < src.length) { - const row: string[] = []; - while (i < src.length) { - if (src[i] === '"') { - // Quoted field - i++; // skip opening quote - let field = ''; - while (i < src.length) { - if (src[i] === '"') { - if (i + 1 < src.length && src[i + 1] === '"') { - field += '"'; - i += 2; - } else { - i++; // skip closing quote - break; - } - } else { - field += src[i]; - i++; - } - } - row.push(field); - } else { - // Unquoted field - let field = ''; - while (i < src.length && src[i] !== ',' && src[i] !== '\n') { - field += src[i]; - i++; - } - row.push(field.trim()); - } - - if (i < src.length && src[i] === ',') { - i++; // skip comma - } else { - break; - } - } - if (i < src.length && src[i] === '\n') i++; // skip newline - lines.push(row); - } - - const headers = lines[0] ?? []; - const rows = lines.slice(1); - return { headers, rows }; -} - -export function inferColumnTypes(headers: string[], rows: string[][]): ColumnDef[] { - return headers.map((rawName, colIdx) => { - const name = rawName.trim() || `col_${colIdx}`; - const values = rows.map(r => (colIdx < r.length ? r[colIdx] : '')); - const nonEmpty = values.filter(v => v !== ''); - const nullable = nonEmpty.length < values.length; - - if (nonEmpty.length === 0) { - return { name, type: { VarChar: 32 } as { VarChar: number }, nullable: true }; - } - - // Bool check - const boolSet = new Set(['true', 'false', '0', '1']); - if (nonEmpty.every(v => boolSet.has(v.toLowerCase()))) { - return { name, type: 'Bool', nullable }; - } - - // UInt32 check - const uintRe = /^\d+$/; - if (nonEmpty.every(v => uintRe.test(v))) { - const nums = nonEmpty.map(Number); - if (nums.every(n => n >= 0 && n <= 4294967295)) { - return { name, type: 'UInt32', nullable }; - } - } - - // Int32 check - const intRe = /^-?\d+$/; - if (nonEmpty.every(v => intRe.test(v))) { - const nums = nonEmpty.map(Number); - if (nums.every(n => n >= -2147483648 && n <= 2147483647)) { - return { name, type: 'Int32', nullable }; - } - } - - // Float64 check - const floatRe = /^-?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/; - if (nonEmpty.every(v => floatRe.test(v))) { - return { name, type: 'Float64', nullable }; - } - - // VarChar fallback - const maxLen = Math.max(...nonEmpty.map(v => v.length)); - const rounded = maxLen <= 32 ? 32 : maxLen <= 64 ? 64 : maxLen <= 128 ? 128 : 255; - return { name, type: { VarChar: rounded } as { VarChar: number }, nullable }; - }); -} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..cc14d83 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import './index.css'; +import App from './App'; + +createRoot(document.getElementById('root')!).render( + + + , +); diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte deleted file mode 100644 index 08aaf12..0000000 --- a/frontend/src/routes/+layout.svelte +++ /dev/null @@ -1,93 +0,0 @@ - - - - wizalloc - - - -
-
-
- wizalloc -
-
- -
- {@render children()} -
- -
- - diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte deleted file mode 100644 index 45746fe..0000000 --- a/frontend/src/routes/+page.svelte +++ /dev/null @@ -1,307 +0,0 @@ - - - - wizalloc - Storage Engine - - -{#if loading} -
-
- Loading WASM... -
-{:else} -
- {#if isMobile} - - -
-
- - {#if !layoutState.bufferPoolCollapsed} -
- -
- {/if} -
-
- - {#if !layoutState.diskCollapsed} -
- -
- {/if} -
-
- - {#if !layoutState.inspectorCollapsed} -
- -
- {/if} -
-
- {:else} - - {#if layoutState.sidebarCollapsed} - - {:else} - - {/if} -
-
- {#if layoutState.bufferPoolCollapsed} -
- -
- {:else} -
- -
- -
-
- {/if} - {#if !layoutState.bufferPoolCollapsed && !layoutState.diskCollapsed} - - {/if} - {#if layoutState.diskCollapsed} -
- -
- {:else} -
- -
- -
-
- {/if} -
- {#if !layoutState.inspectorCollapsed} - - {/if} -
-
- - {#if !layoutState.inspectorCollapsed} -
- -
- {/if} -
-
-
- {/if} -
-{/if} - - diff --git a/frontend/src/stores/layout.ts b/frontend/src/stores/layout.ts new file mode 100644 index 0000000..ed287d9 --- /dev/null +++ b/frontend/src/stores/layout.ts @@ -0,0 +1,40 @@ +import { create } from 'zustand'; + +const MIN_RATIO = 0.15; +const MAX_RATIO = 0.85; + +function clampRatio(v: number): number { + return Math.min(MAX_RATIO, Math.max(MIN_RATIO, v)); +} + +interface LayoutState { + topBottomRatio: number; + leftRightRatio: number; + sidebarCollapsed: boolean; + bufferPoolCollapsed: boolean; + diskCollapsed: boolean; + inspectorCollapsed: boolean; + + setTopBottomRatio: (v: number) => void; + setLeftRightRatio: (v: number) => void; + toggleSidebar: () => void; + toggleBufferPool: () => void; + toggleDisk: () => void; + toggleInspector: () => void; +} + +export const useLayoutStore = create((set) => ({ + topBottomRatio: 0.5, + leftRightRatio: 0.5, + sidebarCollapsed: false, + bufferPoolCollapsed: false, + diskCollapsed: false, + inspectorCollapsed: false, + + setTopBottomRatio: (v) => set({ topBottomRatio: clampRatio(v) }), + setLeftRightRatio: (v) => set({ leftRightRatio: clampRatio(v) }), + toggleSidebar: () => set((s) => ({ sidebarCollapsed: !s.sidebarCollapsed })), + toggleBufferPool: () => set((s) => ({ bufferPoolCollapsed: !s.bufferPoolCollapsed })), + toggleDisk: () => set((s) => ({ diskCollapsed: !s.diskCollapsed })), + toggleInspector: () => set((s) => ({ inspectorCollapsed: !s.inspectorCollapsed })), +})); diff --git a/frontend/src/stores/storage.ts b/frontend/src/stores/storage.ts new file mode 100644 index 0000000..63b2401 --- /dev/null +++ b/frontend/src/stores/storage.ts @@ -0,0 +1,480 @@ +import { create } from 'zustand'; +import { StorageEngineWrapper, initStorageWasm } from '@/wasm/storage-engine'; +import type { + EngineConfig, + BufferPoolSnapshot, + DiskSnapshot, + PageSnapshot, + TableInfo, + ColumnDef, + ScanRow, +} from '@/wasm/storage-types'; +import { parseCSV, inferColumnTypes } from '@/wasm/storage-types'; + +// ── Random data generation helpers ───────────────────────────────── + +const SAMPLE_NAMES = [ + 'Alice', 'Bob', 'Charlie', 'Diana', 'Eve', 'Frank', 'Grace', 'Hank', + 'Ivy', 'Jack', 'Karen', 'Leo', 'Mona', 'Nate', 'Olivia', 'Pete', + 'Quinn', 'Rose', 'Sam', 'Tina', 'Uma', 'Vince', 'Wendy', 'Xander', +]; + +const SAMPLE_EMAILS = [ + 'alice@example.com', 'bob@test.org', 'charlie@mail.co', 'diana@work.net', + 'eve@inbox.io', 'frank@demo.dev', 'grace@site.com', 'hank@corp.biz', + 'ivy@web.app', 'jack@hello.org', 'karen@data.io', 'leo@tech.co', +]; + +const SAMPLE_CITIES = [ + 'New York', 'London', 'Tokyo', 'Paris', 'Berlin', 'Sydney', + 'Toronto', 'Mumbai', 'Seoul', 'Dubai', 'Oslo', 'Lima', +]; + +const SAMPLE_STRINGS = [...SAMPLE_NAMES, ...SAMPLE_EMAILS, ...SAMPLE_CITIES]; + +function randomValue( + colType: string | { VarChar: number } | { Blob: number }, + nullable: boolean, +): unknown { + if (nullable && Math.random() < 0.1) return null; + + if (typeof colType === 'object') { + if ('VarChar' in colType) { + const s = SAMPLE_STRINGS[Math.floor(Math.random() * SAMPLE_STRINGS.length)]; + return s.length > colType.VarChar ? s.slice(0, colType.VarChar) : s; + } + const len = Math.min(8, 'Blob' in colType ? colType.Blob : 8); + const bytes = Array.from({ length: len }, () => + Math.floor(Math.random() * 256), + ); + return bytes.map((b) => b.toString(16).padStart(2, '0')).join(''); + } + + switch (colType) { + case 'Int32': + return Math.floor(Math.random() * 2001) - 1000; + case 'UInt32': + return Math.floor(Math.random() * 10001); + case 'Float64': + return Math.round(Math.random() * 10000) / 100; + case 'Bool': + return Math.random() > 0.5; + default: + return 0; + } +} + +function coerceValue( + raw: string, + colType: string | { VarChar: number } | { Blob: number }, +): unknown { + if (raw === '') return null; + if (typeof colType === 'object') return raw; + switch (colType) { + case 'Int32': + case 'UInt32': + return parseInt(raw, 10); + case 'Float64': + return parseFloat(raw); + case 'Bool': { + const lower = raw.toLowerCase(); + return lower === 'true' || lower === '1'; + } + default: + return raw; + } +} + +// ── Store types ──────────────────────────────────────────────────── + +interface StorageState { + // Engine + engine: StorageEngineWrapper | null; + engineConfig: EngineConfig | null; + wasmReady: boolean; + engineReady: boolean; + + // Snapshots + bpSnapshot: BufferPoolSnapshot | null; + diskSnapshot: DiskSnapshot | null; + pageSnapshot: PageSnapshot | null; + selectedPageId: number | null; + + // Tables + tables: string[]; + selectedTable: string | null; + tableInfo: TableInfo | null; + + // Scan + scanResults: ScanRow[]; + scanColumns: string[]; + + // Selection + selectedSlotId: number | null; + selectedCell: { rowId: string; colIndex: number; value: unknown } | null; + getRowResult: { rowId: string; values: unknown[] } | null; + + // Status + statusMsg: string; + statusType: 'info' | 'success' | 'error'; + + // Actions + initWasm: () => Promise; + initEngine: (config: EngineConfig) => void; + resetEngine: () => void; + createTable: (name: string, columns: ColumnDef[]) => void; + dropTable: (name: string) => void; + selectTable: (name: string) => void; + insert: (tableName: string, values: unknown[]) => void; + deleteRow: (tableName: string, rowId: string) => void; + getRow: (tableName: string, rowId: string) => void; + scan: (tableName: string) => void; + selectPage: (pageId: number | null) => void; + flushPage: (pageId: number) => void; + flushAll: () => void; + selectSlot: (slotId: number | null) => void; + selectCell: (rowId: string, colIndex: number, value: unknown) => void; + clearCellSelection: () => void; + clearGetResult: () => void; + selectRowFromScan: (rowId: string) => void; + bootstrapTable: (name: string, columns: ColumnDef[], rowCount: number) => void; + loadFromCSV: (tableName: string, headers: string[], rows: string[][], columns: ColumnDef[]) => void; + refreshSnapshots: () => void; +} + +// ── Store ────────────────────────────────────────────────────────── + +export const useStorageStore = create((set, get) => { + function refreshSnapshots() { + const { engine, selectedPageId, selectedTable } = get(); + if (!engine) return; + const bpSnapshot = engine.snapshotBufferPool(); + const diskSnapshot = engine.snapshotDisk(); + const pageSnapshot = selectedPageId !== null ? engine.snapshotPage(selectedPageId) : null; + const tableInfo = selectedTable ? engine.snapshotTable(selectedTable) : null; + set({ bpSnapshot, diskSnapshot, pageSnapshot, tableInfo }); + } + + function setStatus(statusMsg: string, statusType: 'info' | 'success' | 'error' = 'info') { + set({ statusMsg, statusType }); + } + + return { + engine: null, + engineConfig: null, + wasmReady: false, + engineReady: false, + bpSnapshot: null, + diskSnapshot: null, + pageSnapshot: null, + selectedPageId: null, + tables: [], + selectedTable: null, + tableInfo: null, + scanResults: [], + scanColumns: [], + selectedSlotId: null, + selectedCell: null, + getRowResult: null, + statusMsg: '', + statusType: 'info', + + async initWasm() { + await initStorageWasm(); + set({ wasmReady: true }); + }, + + initEngine(config: EngineConfig) { + const { engine: old } = get(); + if (old) old.destroy(); + const engine = new StorageEngineWrapper(config); + set({ + engine, + engineConfig: engine.config(), + engineReady: true, + tables: [], + selectedTable: null, + scanResults: [], + scanColumns: [], + tableInfo: null, + selectedSlotId: null, + selectedCell: null, + getRowResult: null, + selectedPageId: null, + pageSnapshot: null, + }); + refreshSnapshots(); + setStatus('Engine initialized', 'success'); + }, + + resetEngine() { + const { engine } = get(); + if (engine) engine.destroy(); + set({ + engine: null, + engineConfig: null, + engineReady: false, + bpSnapshot: null, + diskSnapshot: null, + pageSnapshot: null, + selectedPageId: null, + tables: [], + selectedTable: null, + scanResults: [], + scanColumns: [], + tableInfo: null, + selectedSlotId: null, + selectedCell: null, + getRowResult: null, + }); + setStatus('Engine reset', 'info'); + }, + + createTable(name: string, columns: ColumnDef[]) { + const { engine, selectedTable } = get(); + if (!engine) return; + try { + engine.createTable(name, columns); + const tables = engine.listTables(); + set({ tables, selectedTable: selectedTable ?? name }); + refreshSnapshots(); + setStatus(`Table '${name}' created`, 'success'); + } catch (e: unknown) { + setStatus(String(e), 'error'); + } + }, + + dropTable(name: string) { + const { engine, selectedTable } = get(); + if (!engine) return; + engine.dropTable(name); + const tables = engine.listTables(); + const newSelected = selectedTable === name + ? (tables.length > 0 ? tables[0] : null) + : selectedTable; + set({ + tables, + selectedTable: newSelected, + scanResults: [], + scanColumns: [], + selectedSlotId: null, + selectedCell: null, + getRowResult: null, + tableInfo: newSelected && engine ? engine.snapshotTable(newSelected) : null, + }); + refreshSnapshots(); + setStatus(`Table '${name}' dropped`, 'success'); + }, + + selectTable(name: string) { + const { engine } = get(); + set({ + selectedTable: name, + scanResults: [], + scanColumns: [], + selectedSlotId: null, + selectedCell: null, + getRowResult: null, + tableInfo: engine ? engine.snapshotTable(name) : null, + }); + }, + + insert(tableName: string, values: unknown[]) { + const { engine } = get(); + if (!engine) return; + try { + const rowId = engine.insert(tableName, values); + refreshSnapshots(); + const parts = rowId.split(':'); + const pgId = parseInt(parts[0], 10); + const snap = engine.snapshotPage(pgId); + if (snap) { + setStatus(`Inserted row ${rowId} on Page ${pgId} (${snap.freeSpace}B free remaining)`, 'success'); + } else { + setStatus(`Inserted row ${rowId}`, 'success'); + } + } catch (e: unknown) { + setStatus(String(e), 'error'); + } + }, + + deleteRow(tableName: string, rowId: string) { + const { engine } = get(); + if (!engine) return; + try { + const ok = engine.delete(tableName, rowId); + refreshSnapshots(); + setStatus( + ok ? `Deleted row ${rowId} — slot tombstoned (space not yet reclaimed)` : 'Row not found', + ok ? 'success' : 'error', + ); + } catch (e: unknown) { + setStatus(String(e), 'error'); + } + }, + + getRow(tableName: string, rowId: string) { + const { engine, scanColumns: existingCols } = get(); + if (!engine) return; + try { + const values = engine.get(tableName, rowId); + let scanColumns = existingCols; + if (scanColumns.length === 0) { + const schema = engine.tableSchema(tableName); + scanColumns = schema ? schema.columns.map((c) => c.name) : []; + } + const parts = rowId.split(':'); + const updates: Partial = { + getRowResult: { rowId, values }, + scanColumns, + }; + if (parts.length === 2) { + const pgId = parseInt(parts[0], 10); + const slotIdx = parseInt(parts[1], 10); + if (!isNaN(pgId) && !isNaN(slotIdx)) { + updates.selectedPageId = pgId; + updates.pageSnapshot = engine.snapshotPage(pgId); + updates.selectedSlotId = slotIdx; + } + } + set(updates); + refreshSnapshots(); + setStatus(`Got row ${rowId}`, 'success'); + } catch (e: unknown) { + set({ getRowResult: null }); + setStatus(String(e), 'error'); + } + }, + + scan(tableName: string) { + const { engine } = get(); + if (!engine) return; + try { + const scanResults = engine.scan(tableName); + const schema = engine.tableSchema(tableName); + const scanColumns = schema ? schema.columns.map((c) => c.name) : []; + set({ scanResults, scanColumns }); + refreshSnapshots(); + setStatus(`Scanned ${scanResults.length} rows`, 'success'); + } catch (e: unknown) { + setStatus(String(e), 'error'); + } + }, + + selectPage(pageId: number | null) { + const { engine } = get(); + set({ + selectedPageId: pageId, + pageSnapshot: engine && pageId !== null ? engine.snapshotPage(pageId) : null, + }); + }, + + flushPage(pageId: number) { + const { engine } = get(); + if (!engine) return; + engine.flushPage(pageId); + refreshSnapshots(); + setStatus(`Flushed page ${pageId}`, 'success'); + }, + + flushAll() { + const { engine } = get(); + if (!engine) return; + engine.flushAll(); + refreshSnapshots(); + setStatus('Flushed all dirty pages', 'success'); + }, + + selectSlot(slotId: number | null) { + set({ selectedSlotId: slotId }); + }, + + selectCell(rowId: string, colIndex: number, value: unknown) { + const { engine } = get(); + const parts = rowId.split(':'); + const updates: Partial = { + selectedCell: { rowId, colIndex, value }, + }; + if (parts.length === 2) { + const pgId = parseInt(parts[0], 10); + const slotIdx = parseInt(parts[1], 10); + if (!isNaN(pgId) && !isNaN(slotIdx)) { + updates.selectedPageId = pgId; + updates.pageSnapshot = engine ? engine.snapshotPage(pgId) : null; + updates.selectedSlotId = slotIdx; + } + } + set(updates); + }, + + clearCellSelection() { + set({ selectedCell: null }); + }, + + clearGetResult() { + set({ getRowResult: null, selectedCell: null }); + }, + + selectRowFromScan(rowId: string) { + const { engine } = get(); + const parts = rowId.split(':'); + if (parts.length !== 2) return; + const pgId = parseInt(parts[0], 10); + const slotIdx = parseInt(parts[1], 10); + if (isNaN(pgId) || isNaN(slotIdx)) return; + set({ + selectedPageId: pgId, + pageSnapshot: engine ? engine.snapshotPage(pgId) : null, + selectedSlotId: slotIdx, + }); + }, + + bootstrapTable(name: string, columns: ColumnDef[], rowCount: number) { + const { engine, selectedTable } = get(); + if (!engine) return; + try { + engine.createTable(name, columns); + const tables = engine.listTables(); + set({ tables, selectedTable: selectedTable ?? name }); + + for (let i = 0; i < rowCount; i++) { + const row = columns.map((c) => randomValue(c.type, c.nullable)); + engine.insert(name, row); + } + + refreshSnapshots(); + setStatus(`Created '${name}' with ${rowCount} rows`, 'success'); + } catch (e: unknown) { + setStatus(String(e), 'error'); + } + }, + + loadFromCSV(tableName: string, _headers: string[], rows: string[][], columns: ColumnDef[]) { + const { engine, selectedTable } = get(); + if (!engine) return; + try { + engine.createTable(tableName, columns); + const tables = engine.listTables(); + set({ tables, selectedTable: selectedTable ?? tableName }); + + for (const row of rows) { + const values = columns.map((col, i) => { + const raw = i < row.length ? row[i] : ''; + return raw === '' && col.nullable ? null : coerceValue(raw, col.type); + }); + engine.insert(tableName, values); + } + + refreshSnapshots(); + setStatus(`Loaded '${tableName}' with ${rows.length} rows from CSV`, 'success'); + } catch (e: unknown) { + setStatus(String(e), 'error'); + } + }, + + refreshSnapshots, + }; +}); + +// Re-export utilities for convenience +export { parseCSV, inferColumnTypes }; diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..26090b1 --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1,6 @@ +/// + +declare module 'wizalloc-engine/wizalloc_engine_bg.wasm?url' { + const url: string; + export default url; +} diff --git a/frontend/src/wasm/storage-engine.ts b/frontend/src/wasm/storage-engine.ts new file mode 100644 index 0000000..26a0843 --- /dev/null +++ b/frontend/src/wasm/storage-engine.ts @@ -0,0 +1,104 @@ +import init, { StorageEngine as WasmStorageEngine } from 'wizalloc-engine'; +import wasmUrl from 'wizalloc-engine/wizalloc_engine_bg.wasm?url'; +import type { + EngineConfig, + BufferPoolSnapshot, + DiskSnapshot, + PageSnapshot, + TableInfo, + ScanRow, + ColumnDef, +} from './storage-types'; +import { + decodeBufferPool, + decodeDisk, + decodePage, + decodeTable, +} from './storage-types'; + +let initialized = false; + +export async function initStorageWasm(): Promise { + if (initialized) return; + await init({ module_or_path: wasmUrl }); + initialized = true; +} + +/** Thin wrapper around the WASM StorageEngine. */ +export class StorageEngineWrapper { + private inner: WasmStorageEngine; + + constructor(config: EngineConfig) { + this.inner = new WasmStorageEngine(JSON.stringify(config)); + } + + config(): EngineConfig { + return JSON.parse(this.inner.config()); + } + + createTable(name: string, columns: ColumnDef[]): void { + const schema = JSON.stringify({ columns }); + this.inner.create_table(name, schema); + } + + dropTable(name: string): boolean { + return this.inner.drop_table(name); + } + + listTables(): string[] { + return JSON.parse(this.inner.list_tables()); + } + + insert(tableName: string, values: unknown[]): string { + return this.inner.insert(tableName, JSON.stringify(values)); + } + + get(tableName: string, rowId: string): unknown[] { + return JSON.parse(this.inner.get(tableName, rowId)); + } + + delete(tableName: string, rowId: string): boolean { + return this.inner.delete(tableName, rowId); + } + + scan(tableName: string): ScanRow[] { + return JSON.parse(this.inner.scan(tableName)); + } + + flushAll(): void { + this.inner.flush_all(); + } + + flushPage(pageId: number): boolean { + return this.inner.flush_page(pageId); + } + + tableSchema(tableName: string): { columns: ColumnDef[] } | null { + const json = this.inner.table_schema(tableName); + return json ? JSON.parse(json) : null; + } + + // ── Snapshots ────────────────────────────────────────────── + + snapshotBufferPool(): BufferPoolSnapshot { + return decodeBufferPool(this.inner.snapshot_buffer_pool()); + } + + snapshotDisk(): DiskSnapshot { + return decodeDisk(this.inner.snapshot_disk()); + } + + snapshotPage(pageId: number): PageSnapshot | null { + const data = this.inner.snapshot_page(pageId); + return data ? decodePage(data) : null; + } + + snapshotTable(tableName: string): TableInfo | null { + const data = this.inner.snapshot_table(tableName); + return data ? decodeTable(data) : null; + } + + destroy(): void { + this.inner.free(); + } +} diff --git a/frontend/src/wasm/storage-types.ts b/frontend/src/wasm/storage-types.ts new file mode 100644 index 0000000..5584473 --- /dev/null +++ b/frontend/src/wasm/storage-types.ts @@ -0,0 +1,449 @@ +// ── Types for Storage Engine visualization ───────────────────────── + +export const INVALID_PAGE = 0xffffffff; + +export interface EngineConfig { + page_size: number; + pool_size: number; + disk_capacity: number; + overflow_threshold: number; +} + +export interface FrameInfo { + pageId: number | null; // null if empty + pinCount: number; + isDirty: boolean; + isOccupied: boolean; +} + +export interface BufferPoolSnapshot { + poolSize: number; + pageSize: number; + frames: FrameInfo[]; + pageTable: Map; // pageId → frameId + lruOrder: number[]; // frameIds, front=LRU + hitCount: number; + missCount: number; + diskReadCount: number; + diskWriteCount: number; + diskNumAllocated: number; + diskMaxPages: number; + diskBasePtr: number; +} + +export interface DiskPageInfo { + isAllocated: boolean; + pageType: number; // 0=Data, 1=Overflow, 2=Free +} + +export interface DiskSnapshot { + maxPages: number; + pageSize: number; + numAllocated: number; + diskBasePtr: number; + pages: DiskPageInfo[]; +} + +export interface SlotInfo { + offset: number; + length: number; +} + +export interface PageSnapshot { + pageSize: number; + pageId: number; + pageType: number; + slotCount: number; + freeStart: number; + freeEnd: number; + nextPageId: number; + freeSpace: number; + slots: SlotInfo[]; + rawBytes: Uint8Array; +} + +export interface ColumnDef { + name: string; + type: string | { VarChar: number } | { Blob: number }; + nullable: boolean; +} + +export interface TableInfo { + name: string; + rowCount: number; + firstPageId: number; + columns: ColumnDef[]; + pageIds: number[]; +} + +export interface ScanRow { + row_id: string; + values: unknown[]; +} + +// ── Binary decoders ──────────────────────────────────────────────── + +class BinaryReader { + private view: DataView; + private offset = 0; + + constructor(data: Uint8Array) { + this.view = new DataView(data.buffer, data.byteOffset, data.byteLength); + } + + u8(): number { + const v = this.view.getUint8(this.offset); + this.offset += 1; + return v; + } + u16(): number { + const v = this.view.getUint16(this.offset, true); + this.offset += 2; + return v; + } + u32(): number { + const v = this.view.getUint32(this.offset, true); + this.offset += 4; + return v; + } + u64(): bigint { + const lo = this.view.getUint32(this.offset, true); + const hi = this.view.getUint32(this.offset + 4, true); + this.offset += 8; + return BigInt(hi) * BigInt(0x100000000) + BigInt(lo); + } + bytes(n: number): Uint8Array { + const slice = new Uint8Array( + this.view.buffer, + this.view.byteOffset + this.offset, + n, + ); + this.offset += n; + return new Uint8Array(slice); // copy + } +} + +export function decodeBufferPool(data: Uint8Array): BufferPoolSnapshot { + const r = new BinaryReader(data); + const poolSize = r.u32(); + const pageSize = r.u32(); + + const frames: FrameInfo[] = []; + for (let i = 0; i < poolSize; i++) { + const pid = r.u32(); + frames.push({ + pageId: pid === INVALID_PAGE ? null : pid, + pinCount: r.u32(), + isDirty: r.u8() !== 0, + isOccupied: r.u8() !== 0, + }); + } + + const ptLen = r.u32(); + const pageTable = new Map(); + for (let i = 0; i < ptLen; i++) { + pageTable.set(r.u32(), r.u32()); + } + + const lruLen = r.u32(); + const lruOrder: number[] = []; + for (let i = 0; i < lruLen; i++) { + lruOrder.push(r.u32()); + } + + return { + poolSize, + pageSize, + frames, + pageTable, + lruOrder, + hitCount: Number(r.u64()), + missCount: Number(r.u64()), + diskReadCount: Number(r.u64()), + diskWriteCount: Number(r.u64()), + diskNumAllocated: r.u32(), + diskMaxPages: r.u32(), + diskBasePtr: r.u32(), + }; +} + +export function decodeDisk(data: Uint8Array): DiskSnapshot { + const r = new BinaryReader(data); + const maxPages = r.u32(); + const pageSize = r.u32(); + const numAllocated = r.u32(); + const diskBasePtr = r.u32(); + + const pages: DiskPageInfo[] = []; + for (let i = 0; i < maxPages; i++) { + pages.push({ + isAllocated: r.u8() !== 0, + pageType: r.u8(), + }); + } + + return { maxPages, pageSize, numAllocated, diskBasePtr, pages }; +} + +export function decodePage(data: Uint8Array): PageSnapshot { + const r = new BinaryReader(data); + const pageSize = r.u32(); + const pageId = r.u32(); + const pageType = r.u8(); + const slotCount = r.u16(); + const freeStart = r.u16(); + const freeEnd = r.u16(); + const nextPageId = r.u32(); + const freeSpace = r.u16(); + + const numSlots = r.u16(); + const slots: SlotInfo[] = []; + for (let i = 0; i < numSlots; i++) { + slots.push({ offset: r.u16(), length: r.u16() }); + } + + const rawBytes = r.bytes(pageSize); + + return { + pageSize, + pageId, + pageType, + slotCount, + freeStart, + freeEnd, + nextPageId, + freeSpace, + slots, + rawBytes, + }; +} + +export function decodeTable(data: Uint8Array): TableInfo { + const r = new BinaryReader(data); + + const nameLen = r.u16(); + const nameBytes = r.bytes(nameLen); + const name = new TextDecoder().decode(nameBytes); + + const rowCount = r.u32(); + const firstPageId = r.u32(); + + const numCols = r.u16(); + const columns: ColumnDef[] = []; + for (let i = 0; i < numCols; i++) { + const cnLen = r.u16(); + const cnBytes = r.bytes(cnLen); + const colName = new TextDecoder().decode(cnBytes); + const typeTag = r.u8(); + const nullable = r.u8() !== 0; + const maxLen = r.u16(); + + let type_: string | { VarChar: number } | { Blob: number }; + switch (typeTag) { + case 0: + type_ = 'Int32'; + break; + case 1: + type_ = 'UInt32'; + break; + case 2: + type_ = 'Float64'; + break; + case 3: + type_ = 'Bool'; + break; + case 4: + type_ = { VarChar: maxLen }; + break; + case 5: + type_ = { Blob: maxLen }; + break; + default: + type_ = 'Int32'; + } + columns.push({ name: colName, type: type_, nullable }); + } + + const pageCount = r.u32(); + const pageIds: number[] = []; + for (let i = 0; i < pageCount; i++) { + pageIds.push(r.u32()); + } + + return { name, rowCount, firstPageId, columns, pageIds }; +} + +// ── Utility ──────────────────────────────────────────────────────── + +export function pageTypeName(t: number): string { + switch (t) { + case 0: + return 'Data'; + case 1: + return 'Overflow'; + case 2: + return 'Free'; + default: + return '?'; + } +} + +export function formatHex(n: number): string { + return '0x' + n.toString(16).toUpperCase().padStart(4, '0'); +} + +export function formatBytes(n: number): string { + if (n < 1024) return `${n}B`; + return `${(n / 1024).toFixed(1)}KB`; +} + +export function formatColType( + type: string | { VarChar: number } | { Blob: number }, +): string { + if (typeof type === 'object') { + if ('VarChar' in type) return `VarChar(${type.VarChar})`; + if ('Blob' in type) return `Blob(${type.Blob})`; + } + return String(type); +} + +export function colTypeName( + colType: string | { VarChar: number } | { Blob: number }, +): string { + if (typeof colType === 'object') { + if ('VarChar' in colType) return 'VarChar'; + if ('Blob' in colType) return 'Blob'; + } + return String(colType); +} + +// ── CSV utilities ────────────────────────────────────────────────── + +export function parseCSV(text: string): { headers: string[]; rows: string[][] } { + let src = text + .replace(/^\uFEFF/, '') + .replace(/\r\n/g, '\n') + .replace(/\r/g, '\n'); + src = src.replace(/\n+$/, ''); + + const lines: string[][] = []; + let i = 0; + + while (i < src.length) { + const row: string[] = []; + while (i < src.length) { + if (src[i] === '"') { + i++; + let field = ''; + while (i < src.length) { + if (src[i] === '"') { + if (i + 1 < src.length && src[i + 1] === '"') { + field += '"'; + i += 2; + } else { + i++; + break; + } + } else { + field += src[i]; + i++; + } + } + row.push(field); + } else { + let field = ''; + while (i < src.length && src[i] !== ',' && src[i] !== '\n') { + field += src[i]; + i++; + } + row.push(field.trim()); + } + + if (i < src.length && src[i] === ',') { + i++; + } else { + break; + } + } + if (i < src.length && src[i] === '\n') i++; + lines.push(row); + } + + const headers = lines[0] ?? []; + const rows = lines.slice(1); + return { headers, rows }; +} + +export function inferColumnTypes( + headers: string[], + rows: string[][], +): ColumnDef[] { + return headers.map((rawName, colIdx) => { + const name = rawName.trim() || `col_${colIdx}`; + const values = rows.map((r) => (colIdx < r.length ? r[colIdx] : '')); + const nonEmpty = values.filter((v) => v !== ''); + const nullable = nonEmpty.length < values.length; + + if (nonEmpty.length === 0) { + return { + name, + type: { VarChar: 32 } as { VarChar: number }, + nullable: true, + }; + } + + const boolSet = new Set(['true', 'false', '0', '1']); + if (nonEmpty.every((v) => boolSet.has(v.toLowerCase()))) { + return { name, type: 'Bool', nullable }; + } + + const uintRe = /^\d+$/; + if (nonEmpty.every((v) => uintRe.test(v))) { + const nums = nonEmpty.map(Number); + if (nums.every((n) => n >= 0 && n <= 4294967295)) { + return { name, type: 'UInt32', nullable }; + } + } + + const intRe = /^-?\d+$/; + if (nonEmpty.every((v) => intRe.test(v))) { + const nums = nonEmpty.map(Number); + if (nums.every((n) => n >= -2147483648 && n <= 2147483647)) { + return { name, type: 'Int32', nullable }; + } + } + + const floatRe = /^-?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/; + if (nonEmpty.every((v) => floatRe.test(v))) { + return { name, type: 'Float64', nullable }; + } + + const maxLen = Math.max(...nonEmpty.map((v) => v.length)); + const rounded = + maxLen <= 32 ? 32 : maxLen <= 64 ? 64 : maxLen <= 128 ? 128 : 255; + return { name, type: { VarChar: rounded } as { VarChar: number }, nullable }; + }); +} + +export function estimateByteSize( + colType: string | { VarChar: number } | { Blob: number }, + value: unknown, +): number { + if (value === null) return 1; + if (typeof colType === 'object') { + if ('VarChar' in colType) + return 2 + new TextEncoder().encode(String(value)).length; + if ('Blob' in colType) return 2 + String(value).length / 2; + } + switch (colType) { + case 'Int32': + case 'UInt32': + return 4; + case 'Float64': + return 8; + case 'Bool': + return 1; + default: + return 0; + } +} diff --git a/frontend/static/robots.txt b/frontend/static/robots.txt deleted file mode 100644 index b6dd667..0000000 --- a/frontend/static/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -# allow crawling everything by default -User-agent: * -Disallow: diff --git a/frontend/svelte.config.js b/frontend/svelte.config.js deleted file mode 100644 index 0b663ed..0000000 --- a/frontend/svelte.config.js +++ /dev/null @@ -1,12 +0,0 @@ -import adapter from '@sveltejs/adapter-static'; - -/** @type {import('@sveltejs/kit').Config} */ -const config = { - kit: { - adapter: adapter({ - fallback: 'index.html' - }) - } -}; - -export default config; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 2c2ed3c..911f0ec 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -1,20 +1,25 @@ { - "extends": "./.svelte-kit/tsconfig.json", - "compilerOptions": { - "rewriteRelativeImportExtensions": true, - "allowJs": true, - "checkJs": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "sourceMap": true, - "strict": true, - "moduleResolution": "bundler" - } - // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias - // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files - // - // To make changes to top-level options such as include and exclude, we recommend extending - // the generated config; see https://svelte.dev/docs/kit/configuration#typescript + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src"] } diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 452fb6d..3617980 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,16 +1,23 @@ -import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; import wasm from 'vite-plugin-wasm'; import topLevelAwait from 'vite-plugin-top-level-await'; +import tailwindcss from '@tailwindcss/vite'; +import path from 'path'; export default defineConfig({ - plugins: [wasm(), topLevelAwait(), sveltekit()], - optimizeDeps: { - exclude: ['wizalloc-engine'] - }, - server: { - fs: { - allow: ['..'] - } - } + plugins: [wasm(), topLevelAwait(), react(), tailwindcss()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, + optimizeDeps: { + exclude: ['wizalloc-engine'], + }, + server: { + fs: { + allow: ['..'], + }, + }, }); diff --git a/netlify.toml b/netlify.toml index 47702ef..0c8304b 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,4 +1,4 @@ [build] base = "frontend" - publish = "build" + publish = "dist" command = "npm run build"