From a0bb9a41c5f75eef04be90acac46e403c97725df Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 00:32:22 +0530 Subject: [PATCH 01/42] chore(dashboard-next): scaffold vite + react + tanstack --- dashboard-next/.gitignore | 7 + dashboard-next/biome.json | 45 + dashboard-next/components.json | 21 + dashboard-next/index.html | 21 + dashboard-next/package.json | 42 + dashboard-next/pnpm-lock.yaml | 2360 ++++++++++++++++++++++++++++++ dashboard-next/src/globals.css | 174 +++ dashboard-next/src/main.tsx | 28 + dashboard-next/src/vite-env.d.ts | 1 + dashboard-next/tsconfig.json | 26 + dashboard-next/vite.config.ts | 39 + 11 files changed, 2764 insertions(+) create mode 100644 dashboard-next/.gitignore create mode 100644 dashboard-next/biome.json create mode 100644 dashboard-next/components.json create mode 100644 dashboard-next/index.html create mode 100644 dashboard-next/package.json create mode 100644 dashboard-next/pnpm-lock.yaml create mode 100644 dashboard-next/src/globals.css create mode 100644 dashboard-next/src/main.tsx create mode 100644 dashboard-next/src/vite-env.d.ts create mode 100644 dashboard-next/tsconfig.json create mode 100644 dashboard-next/vite.config.ts diff --git a/dashboard-next/.gitignore b/dashboard-next/.gitignore new file mode 100644 index 0000000..de44f9e --- /dev/null +++ b/dashboard-next/.gitignore @@ -0,0 +1,7 @@ +node_modules +dist +.vite +*.log +.env +.env.local +src/route-tree.gen.ts diff --git a/dashboard-next/biome.json b/dashboard-next/biome.json new file mode 100644 index 0000000..0a95199 --- /dev/null +++ b/dashboard-next/biome.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.4.8/schema.json", + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "a11y": { + "noStaticElementInteractions": "off", + "useKeyWithClickEvents": "off" + }, + "complexity": { + "noForEach": "off" + }, + "correctness": { + "useExhaustiveDependencies": "warn" + }, + "style": { + "noNonNullAssertion": "off" + } + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "double", + "semicolons": "always" + } + }, + "css": { + "linter": { + "enabled": false + }, + "formatter": { + "enabled": false + } + }, + "files": { + "includes": ["src/**/*.ts", "src/**/*.tsx", "!**/*.gen.ts"] + } +} diff --git a/dashboard-next/components.json b/dashboard-next/components.json new file mode 100644 index 0000000..ec60459 --- /dev/null +++ b/dashboard-next/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/cn", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/dashboard-next/index.html b/dashboard-next/index.html new file mode 100644 index 0000000..249a8f3 --- /dev/null +++ b/dashboard-next/index.html @@ -0,0 +1,21 @@ + + + + + + + Taskito + + + +
+ + + diff --git a/dashboard-next/package.json b/dashboard-next/package.json new file mode 100644 index 0000000..e8eaf28 --- /dev/null +++ b/dashboard-next/package.json @@ -0,0 +1,42 @@ +{ + "name": "taskito-dashboard-next", + "private": true, + "version": "0.11.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "typecheck": "tsc --noEmit", + "lint": "biome check src/", + "lint:fix": "biome check --fix src/", + "format": "biome format --write src/", + "format:check": "biome format src/", + "ci": "biome ci src/ && tsc --noEmit && vite build" + }, + "dependencies": { + "@tanstack/react-query": "^5.62.7", + "@tanstack/react-query-devtools": "^5.62.7", + "@tanstack/react-router": "^1.95.1", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.577.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "sonner": "^1.7.1", + "tailwind-merge": "^2.6.0" + }, + "devDependencies": { + "@biomejs/biome": "^2.4.8", + "@tailwindcss/vite": "^4.0.0", + "@tanstack/router-plugin": "^1.95.1", + "@types/node": "^22.10.5", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "@vitejs/plugin-react": "^4.3.4", + "tailwindcss": "^4.0.0", + "typescript": "^5.7.0", + "vite": "^6.4.2" + }, + "packageManager": "pnpm@10.30.3" +} diff --git a/dashboard-next/pnpm-lock.yaml b/dashboard-next/pnpm-lock.yaml new file mode 100644 index 0000000..cfa5f48 --- /dev/null +++ b/dashboard-next/pnpm-lock.yaml @@ -0,0 +1,2360 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@tanstack/react-query': + specifier: ^5.62.7 + version: 5.100.1(react@18.3.1) + '@tanstack/react-query-devtools': + specifier: ^5.62.7 + version: 5.100.1(@tanstack/react-query@5.100.1(react@18.3.1))(react@18.3.1) + '@tanstack/react-router': + specifier: ^1.95.1 + version: 1.168.23(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + lucide-react: + specifier: ^0.577.0 + version: 0.577.0(react@18.3.1) + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + sonner: + specifier: ^1.7.1 + version: 1.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + tailwind-merge: + specifier: ^2.6.0 + version: 2.6.1 + devDependencies: + '@biomejs/biome': + specifier: ^2.4.8 + version: 2.4.13 + '@tailwindcss/vite': + specifier: ^4.0.0 + version: 4.2.4(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)) + '@tanstack/router-plugin': + specifier: ^1.95.1 + version: 1.167.23(@tanstack/react-router@1.168.23(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)) + '@types/node': + specifier: ^22.10.5 + version: 22.19.17 + '@types/react': + specifier: ^18.3.18 + version: 18.3.28 + '@types/react-dom': + specifier: ^18.3.5 + version: 18.3.7(@types/react@18.3.28) + '@vitejs/plugin-react': + specifier: ^4.3.4 + version: 4.7.0(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)) + tailwindcss: + specifier: ^4.0.0 + version: 4.2.4 + typescript: + specifier: ^5.7.0 + version: 5.9.3 + vite: + specifier: ^6.4.2 + version: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0) + +packages: + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.2': + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-jsx@7.28.6': + resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@biomejs/biome@2.4.13': + resolution: {integrity: sha512-gLXOwkOBBg0tr7bDsqlkIh4uFeKuMjxvqsrb1Tukww1iDmHcfr4Uu8MoQxp0Rcte+69+osRNWXwHsu/zxT6XqA==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@2.4.13': + resolution: {integrity: sha512-2KImO1jhNFBa2oWConyr0x6flxbQpGKv6902uGXpYM62Xyem8U80j441SyUJ8KyngsmKbQjeIv1q2CQfDkNnYg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@2.4.13': + resolution: {integrity: sha512-BKrJklbaFN4p1Ts4kPBczo+PkbsHQg57kmJ+vON9u2t6uN5okYHaSr7h/MutPCWQgg2lglaWoSmm+zhYW+oOkg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@2.4.13': + resolution: {integrity: sha512-U5MsuBQW25dXaYtqWWSPM3P96H6Y+fHuja3TQpMNnylocHW0tEbtFTDlUj6oM+YJLntvEkQy4grBvQNUD4+RCg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@biomejs/cli-linux-arm64@2.4.13': + resolution: {integrity: sha512-NzkUDSqfvMBrPplKgVr3aXLHZ2NEELvvF4vZxXulEylKWIGqlvNEcwUcj9OLrn75TD3lJ/GIqCVlBwd1MZCuYQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@biomejs/cli-linux-x64-musl@2.4.13': + resolution: {integrity: sha512-Z601MienRgTBDza/+u2CH3RSrWoXo9rtr8NK6A4KJzqGgfxx+H3VlyLgTJ4sRo40T3pIsqpTmiOQEvYzQvBRvQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@biomejs/cli-linux-x64@2.4.13': + resolution: {integrity: sha512-Az3ZZedYRBo9EQzNnD9SxFcR1G5QsGo6VEc2hIyVPZ1rdKwee/7E9oeBBZFpE8Z44ekxsDQBqbiWGW5ShOhUSQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@biomejs/cli-win32-arm64@2.4.13': + resolution: {integrity: sha512-Px9PS2B5/Q183bUwy/5VHqp3J2lzdOCeVGzMpphYfl8oSa7VDCqenBdqWpy6DCy/en4Rbf/Y1RieZF6dJPcc9A==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@2.4.13': + resolution: {integrity: sha512-tTcMkXyBrmHi9BfrD2VNHs/5rYIUKETqsBlYOvSAABwBkJhSDVb5e7wPukftsQbO3WzQkXe6kaztC6WtUOXSoQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/rollup-android-arm-eabi@4.60.2': + resolution: {integrity: sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.60.2': + resolution: {integrity: sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.60.2': + resolution: {integrity: sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.60.2': + resolution: {integrity: sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.60.2': + resolution: {integrity: sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.60.2': + resolution: {integrity: sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.2': + resolution: {integrity: sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.60.2': + resolution: {integrity: sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.60.2': + resolution: {integrity: sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.60.2': + resolution: {integrity: sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.60.2': + resolution: {integrity: sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.60.2': + resolution: {integrity: sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.60.2': + resolution: {integrity: sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.60.2': + resolution: {integrity: sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.60.2': + resolution: {integrity: sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.60.2': + resolution: {integrity: sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.60.2': + resolution: {integrity: sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.60.2': + resolution: {integrity: sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.60.2': + resolution: {integrity: sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.60.2': + resolution: {integrity: sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.2': + resolution: {integrity: sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.2': + resolution: {integrity: sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.60.2': + resolution: {integrity: sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.60.2': + resolution: {integrity: sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.60.2': + resolution: {integrity: sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==} + cpu: [x64] + os: [win32] + + '@tailwindcss/node@4.2.4': + resolution: {integrity: sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==} + + '@tailwindcss/oxide-android-arm64@4.2.4': + resolution: {integrity: sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.2.4': + resolution: {integrity: sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.2.4': + resolution: {integrity: sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.2.4': + resolution: {integrity: sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': + resolution: {integrity: sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': + resolution: {integrity: sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.2.4': + resolution: {integrity: sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.2.4': + resolution: {integrity: sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-x64-musl@4.2.4': + resolution: {integrity: sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-wasm32-wasi@4.2.4': + resolution: {integrity: sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': + resolution: {integrity: sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.2.4': + resolution: {integrity: sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.2.4': + resolution: {integrity: sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==} + engines: {node: '>= 20'} + + '@tailwindcss/vite@4.2.4': + resolution: {integrity: sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 || ^8 + + '@tanstack/history@1.161.6': + resolution: {integrity: sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg==} + engines: {node: '>=20.19'} + + '@tanstack/query-core@5.100.1': + resolution: {integrity: sha512-awvQhOO/2TrSCHE5LKKsXcvvj6WSBncwEcMFCB/ez0Qs0b17iyyivoGArNV3HFfXryZwCpnb/olsaBBKrIbtSw==} + + '@tanstack/query-devtools@5.100.1': + resolution: {integrity: sha512-jZLV2l7XjYxXCrXHj9pj15gZuY8Te+idoSPS2hIh3+SxOd20Gn0rfUoqEw9vc+us/b16hi0/DWqpzx9O1ZsyIQ==} + + '@tanstack/react-query-devtools@5.100.1': + resolution: {integrity: sha512-JuLinBUl/BlZhm0WVX83fJgE2a3YSbuEdxf3fgP+THg92hX7YfwuH5DzT35a6sL/rifZsPr0yJ9itB6jDOcdRg==} + peerDependencies: + '@tanstack/react-query': ^5.100.1 + react: ^18 || ^19 + + '@tanstack/react-query@5.100.1': + resolution: {integrity: sha512-UgWRLhQKprC37SsO6y1zRabOqDmM2gsdTNPbqTT35yl7kOOhwXU4nyfOiGHXPwoEFJV1IpSk85hjIFjNFWVpzw==} + peerDependencies: + react: ^18 || ^19 + + '@tanstack/react-router@1.168.23': + resolution: {integrity: sha512-+GblieDnutG6oipJJPNtRJjrWF8QTZEG/l0532+BngFkVK48oHNOcvIkSoAFYftK1egAwM7KBxXsb0Ou+X6/MQ==} + engines: {node: '>=20.19'} + peerDependencies: + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + + '@tanstack/react-store@0.9.3': + resolution: {integrity: sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/router-core@1.168.15': + resolution: {integrity: sha512-Wr0424NDtD8fT/uALobMZ9DdcfsTyXtW5IPR++7zvW8/7RaIOeaqXpVDId8ywaGtqPWLWOfaUg2zUtYtukoXYA==} + engines: {node: '>=20.19'} + hasBin: true + + '@tanstack/router-generator@1.166.33': + resolution: {integrity: sha512-MXP1WrEaZ13tlO5iJoXC+ZIFHNj5CtcvWuqlAZ3zXY70Musuq+mfUcKWMVdcIstnNqrZl5M2hfqLh5Zf5t4NVw==} + engines: {node: '>=20.19'} + + '@tanstack/router-plugin@1.167.23': + resolution: {integrity: sha512-dqfCd8gsZThbVQ8bcYMO62/hW5GCkUoPLnnjOd3fCWoEi+Ei5oWa/GnlgHCpG7bdeGr/K8isnYUmI9Ysq5vLrg==} + engines: {node: '>=20.19'} + hasBin: true + peerDependencies: + '@rsbuild/core': '>=1.0.2 || ^2.0.0' + '@tanstack/react-router': ^1.168.23 + vite: '>=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0' + vite-plugin-solid: ^2.11.10 || ^3.0.0-0 + webpack: '>=5.92.0' + peerDependenciesMeta: + '@rsbuild/core': + optional: true + '@tanstack/react-router': + optional: true + vite: + optional: true + vite-plugin-solid: + optional: true + webpack: + optional: true + + '@tanstack/router-utils@1.161.7': + resolution: {integrity: sha512-VkY0u7ax/GD0qU6ZLLnfPC+UMxVzxRbvZp4yV4iUSXjgJZ/siAT5/QlLm9FEDJ9QDoC0VD9W7f00tKKreUI7Ng==} + engines: {node: '>=20.19'} + + '@tanstack/store@0.9.3': + resolution: {integrity: sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==} + + '@tanstack/virtual-file-routes@1.161.7': + resolution: {integrity: sha512-olW33+Cn+bsCsZKPwEGhlkqS6w3M2slFv11JIobdnCFKMLG97oAI2kWKdx5/zsywTL8flpnoIgaZZPlQTFYhdQ==} + engines: {node: '>=20.19'} + hasBin: true + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/node@22.19.17': + resolution: {integrity: sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react@18.3.28': + resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==} + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ansis@4.2.0: + resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} + engines: {node: '>=14'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + babel-dead-code-elimination@1.0.12: + resolution: {integrity: sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==} + + baseline-browser-mapping@2.10.21: + resolution: {integrity: sha512-Q+rUQ7Uz8AHM7DEaNdwvfFCTq7a43lNTzuS94eiWqwyxfV/wJv+oUivef51T91mmRY4d4A1u9rcSvkeufCVXlA==} + engines: {node: '>=6.0.0'} + hasBin: true + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + caniuse-lite@1.0.30001790: + resolution: {integrity: sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-es@3.1.1: + resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + diff@8.0.4: + resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} + engines: {node: '>=0.3.1'} + + electron-to-chromium@1.5.344: + resolution: {integrity: sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==} + + enhanced-resolve@5.21.0: + resolution: {integrity: sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==} + engines: {node: '>=10.13.0'} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + isbot@5.1.39: + resolution: {integrity: sha512-obH0yYahGXdzNxo+djmHhBYThUKDkz565cxkIlt2L9hXfv1NlaLKoDBHo6KxXsYrIXx2RK3x5vY36CfZcobxEw==} + engines: {node: '>=18'} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-react@0.577.0: + resolution: {integrity: sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-releases@2.0.38: + resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + postcss@8.5.10: + resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==} + engines: {node: ^10 || ^12 || >=14} + + prettier@3.8.3: + resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} + engines: {node: '>=14'} + hasBin: true + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + rollup@4.60.2: + resolution: {integrity: sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + seroval-plugins@1.5.2: + resolution: {integrity: sha512-qpY0Cl+fKYFn4GOf3cMiq6l72CpuVaawb6ILjubOQ+diJ54LfOWaSSPsaswN8DRPIPW4Yq+tE1k5aKd7ILyaFg==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + + seroval@1.5.2: + resolution: {integrity: sha512-xcRN39BdsnO9Tf+VzsE7b3JyTJASItIV1FVFewJKCFcW4s4haIKS3e6vj8PGB9qBwC7tnuOywQMdv5N4qkzi7Q==} + engines: {node: '>=10'} + + sonner@1.7.4: + resolution: {integrity: sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + tailwind-merge@2.6.1: + resolution: {integrity: sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==} + + tailwindcss@4.2.4: + resolution: {integrity: sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==} + + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + unplugin@2.3.11: + resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} + engines: {node: '>=18.12.0'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + vite@6.4.2: + resolution: {integrity: sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + 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 + + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + +snapshots: + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.29.2 + '@babel/parser': 7.29.2 + '@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.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.29.2': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.2': + dependencies: + '@babel/types': 7.29.0 + + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@biomejs/biome@2.4.13': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 2.4.13 + '@biomejs/cli-darwin-x64': 2.4.13 + '@biomejs/cli-linux-arm64': 2.4.13 + '@biomejs/cli-linux-arm64-musl': 2.4.13 + '@biomejs/cli-linux-x64': 2.4.13 + '@biomejs/cli-linux-x64-musl': 2.4.13 + '@biomejs/cli-win32-arm64': 2.4.13 + '@biomejs/cli-win32-x64': 2.4.13 + + '@biomejs/cli-darwin-arm64@2.4.13': + optional: true + + '@biomejs/cli-darwin-x64@2.4.13': + optional: true + + '@biomejs/cli-linux-arm64-musl@2.4.13': + optional: true + + '@biomejs/cli-linux-arm64@2.4.13': + optional: true + + '@biomejs/cli-linux-x64-musl@2.4.13': + optional: true + + '@biomejs/cli-linux-x64@2.4.13': + optional: true + + '@biomejs/cli-win32-arm64@2.4.13': + optional: true + + '@biomejs/cli-win32-x64@2.4.13': + optional: true + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/rollup-android-arm-eabi@4.60.2': + optional: true + + '@rollup/rollup-android-arm64@4.60.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.60.2': + optional: true + + '@rollup/rollup-darwin-x64@4.60.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.60.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.60.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.60.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.60.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.60.2': + optional: true + + '@rollup/rollup-openbsd-x64@4.60.2': + optional: true + + '@rollup/rollup-openharmony-arm64@4.60.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.60.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.60.2': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.2': + optional: true + + '@tailwindcss/node@4.2.4': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.21.0 + jiti: 2.6.1 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.2.4 + + '@tailwindcss/oxide-android-arm64@4.2.4': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.2.4': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.2.4': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.2.4': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.2.4': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.2.4': + optional: true + + '@tailwindcss/oxide@4.2.4': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.2.4 + '@tailwindcss/oxide-darwin-arm64': 4.2.4 + '@tailwindcss/oxide-darwin-x64': 4.2.4 + '@tailwindcss/oxide-freebsd-x64': 4.2.4 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.4 + '@tailwindcss/oxide-linux-arm64-gnu': 4.2.4 + '@tailwindcss/oxide-linux-arm64-musl': 4.2.4 + '@tailwindcss/oxide-linux-x64-gnu': 4.2.4 + '@tailwindcss/oxide-linux-x64-musl': 4.2.4 + '@tailwindcss/oxide-wasm32-wasi': 4.2.4 + '@tailwindcss/oxide-win32-arm64-msvc': 4.2.4 + '@tailwindcss/oxide-win32-x64-msvc': 4.2.4 + + '@tailwindcss/vite@4.2.4(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))': + dependencies: + '@tailwindcss/node': 4.2.4 + '@tailwindcss/oxide': 4.2.4 + tailwindcss: 4.2.4 + vite: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0) + + '@tanstack/history@1.161.6': {} + + '@tanstack/query-core@5.100.1': {} + + '@tanstack/query-devtools@5.100.1': {} + + '@tanstack/react-query-devtools@5.100.1(@tanstack/react-query@5.100.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/query-devtools': 5.100.1 + '@tanstack/react-query': 5.100.1(react@18.3.1) + react: 18.3.1 + + '@tanstack/react-query@5.100.1(react@18.3.1)': + dependencies: + '@tanstack/query-core': 5.100.1 + react: 18.3.1 + + '@tanstack/react-router@1.168.23(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/history': 1.161.6 + '@tanstack/react-store': 0.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tanstack/router-core': 1.168.15 + isbot: 5.1.39 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@tanstack/react-store@0.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/store': 0.9.3 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + use-sync-external-store: 1.6.0(react@18.3.1) + + '@tanstack/router-core@1.168.15': + dependencies: + '@tanstack/history': 1.161.6 + cookie-es: 3.1.1 + seroval: 1.5.2 + seroval-plugins: 1.5.2(seroval@1.5.2) + + '@tanstack/router-generator@1.166.33': + dependencies: + '@babel/types': 7.29.0 + '@tanstack/router-core': 1.168.15 + '@tanstack/router-utils': 1.161.7 + '@tanstack/virtual-file-routes': 1.161.7 + magic-string: 0.30.21 + prettier: 3.8.3 + tsx: 4.21.0 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + + '@tanstack/router-plugin@1.167.23(@tanstack/react-router@1.168.23(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@tanstack/router-core': 1.168.15 + '@tanstack/router-generator': 1.166.33 + '@tanstack/router-utils': 1.161.7 + '@tanstack/virtual-file-routes': 1.161.7 + chokidar: 3.6.0 + unplugin: 2.3.11 + zod: 3.25.76 + optionalDependencies: + '@tanstack/react-router': 1.168.23(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + vite: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0) + transitivePeerDependencies: + - supports-color + + '@tanstack/router-utils@1.161.7': + dependencies: + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + ansis: 4.2.0 + babel-dead-code-elimination: 1.0.12 + diff: 8.0.4 + pathe: 2.0.3 + tinyglobby: 0.2.16 + transitivePeerDependencies: + - supports-color + + '@tanstack/store@0.9.3': {} + + '@tanstack/virtual-file-routes@1.161.7': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/estree@1.0.8': {} + + '@types/node@22.19.17': + dependencies: + undici-types: 6.21.0 + + '@types/prop-types@15.7.15': {} + + '@types/react-dom@18.3.7(@types/react@18.3.28)': + dependencies: + '@types/react': 18.3.28 + + '@types/react@18.3.28': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.3 + + '@vitejs/plugin-react@4.7.0(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0) + transitivePeerDependencies: + - supports-color + + acorn@8.16.0: {} + + ansis@4.2.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.2 + + babel-dead-code-elimination@1.0.12: + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.2 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + baseline-browser-mapping@2.10.21: {} + + binary-extensions@2.3.0: {} + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.21 + caniuse-lite: 1.0.30001790 + electron-to-chromium: 1.5.344 + node-releases: 2.0.38 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + + caniuse-lite@1.0.30001790: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + clsx@2.1.1: {} + + convert-source-map@2.0.0: {} + + cookie-es@3.1.1: {} + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + detect-libc@2.1.2: {} + + diff@8.0.4: {} + + electron-to-chromium@1.5.344: {} + + enhanced-resolve@5.21.0: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + escalade@3.2.0: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + graceful-fs@4.2.11: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + isbot@5.1.39: {} + + jiti@2.6.1: {} + + js-tokens@4.0.0: {} + + jsesc@3.1.0: {} + + json5@2.2.3: {} + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@0.577.0(react@18.3.1): + dependencies: + react: 18.3.1 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + node-releases@2.0.38: {} + + normalize-path@3.0.0: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.4: {} + + postcss@8.5.10: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prettier@3.8.3: {} + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-refresh@0.17.0: {} + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.2 + + resolve-pkg-maps@1.0.0: {} + + rollup@4.60.2: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.60.2 + '@rollup/rollup-android-arm64': 4.60.2 + '@rollup/rollup-darwin-arm64': 4.60.2 + '@rollup/rollup-darwin-x64': 4.60.2 + '@rollup/rollup-freebsd-arm64': 4.60.2 + '@rollup/rollup-freebsd-x64': 4.60.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.2 + '@rollup/rollup-linux-arm-musleabihf': 4.60.2 + '@rollup/rollup-linux-arm64-gnu': 4.60.2 + '@rollup/rollup-linux-arm64-musl': 4.60.2 + '@rollup/rollup-linux-loong64-gnu': 4.60.2 + '@rollup/rollup-linux-loong64-musl': 4.60.2 + '@rollup/rollup-linux-ppc64-gnu': 4.60.2 + '@rollup/rollup-linux-ppc64-musl': 4.60.2 + '@rollup/rollup-linux-riscv64-gnu': 4.60.2 + '@rollup/rollup-linux-riscv64-musl': 4.60.2 + '@rollup/rollup-linux-s390x-gnu': 4.60.2 + '@rollup/rollup-linux-x64-gnu': 4.60.2 + '@rollup/rollup-linux-x64-musl': 4.60.2 + '@rollup/rollup-openbsd-x64': 4.60.2 + '@rollup/rollup-openharmony-arm64': 4.60.2 + '@rollup/rollup-win32-arm64-msvc': 4.60.2 + '@rollup/rollup-win32-ia32-msvc': 4.60.2 + '@rollup/rollup-win32-x64-gnu': 4.60.2 + '@rollup/rollup-win32-x64-msvc': 4.60.2 + fsevents: 2.3.3 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + semver@6.3.1: {} + + seroval-plugins@1.5.2(seroval@1.5.2): + dependencies: + seroval: 1.5.2 + + seroval@1.5.2: {} + + sonner@1.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + source-map-js@1.2.1: {} + + tailwind-merge@2.6.1: {} + + tailwindcss@4.2.4: {} + + tapable@2.3.3: {} + + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tsx@4.21.0: + dependencies: + esbuild: 0.27.7 + get-tsconfig: 4.14.0 + optionalDependencies: + fsevents: 2.3.3 + + typescript@5.9.3: {} + + undici-types@6.21.0: {} + + unplugin@2.3.11: + dependencies: + '@jridgewell/remapping': 2.3.5 + acorn: 8.16.0 + picomatch: 4.0.4 + webpack-virtual-modules: 0.6.2 + + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + use-sync-external-store@1.6.0(react@18.3.1): + dependencies: + react: 18.3.1 + + vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.10 + rollup: 4.60.2 + tinyglobby: 0.2.16 + optionalDependencies: + '@types/node': 22.19.17 + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.32.0 + tsx: 4.21.0 + + webpack-virtual-modules@0.6.2: {} + + yallist@3.1.1: {} + + zod@3.25.76: {} diff --git a/dashboard-next/src/globals.css b/dashboard-next/src/globals.css new file mode 100644 index 0000000..1c0cd86 --- /dev/null +++ b/dashboard-next/src/globals.css @@ -0,0 +1,174 @@ +@import "tailwindcss"; + +/* + Design tokens — Supabase/Planetscale-ish. + + - Airy, data-forward, soft dividers over hard borders + - Single emerald accent (soft enough for extended screen time) + - Inter for UI, JetBrains Mono for code/IDs + - Light + dark paired; dark is the daily driver +*/ + +@theme { + --font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + --font-mono: "JetBrains Mono", "Fira Code", "SF Mono", Menlo, monospace; + + --radius-sm: 0.375rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + + /* accent (emerald) */ + --color-accent: oklch(0.72 0.17 162); + --color-accent-fg: oklch(0.22 0.04 160); + --color-accent-dim: oklch(0.72 0.17 162 / 0.18); + + /* status semantics */ + --color-success: oklch(0.72 0.17 162); + --color-success-dim: oklch(0.72 0.17 162 / 0.18); + --color-warning: oklch(0.8 0.14 75); + --color-warning-dim: oklch(0.8 0.14 75 / 0.18); + --color-danger: oklch(0.68 0.22 22); + --color-danger-dim: oklch(0.68 0.22 22 / 0.18); + --color-info: oklch(0.72 0.13 232); + --color-info-dim: oklch(0.72 0.13 232 / 0.18); +} + +/* Light theme (default) */ +:root { + --bg: oklch(0.985 0.003 240); + --bg-subtle: oklch(0.97 0.004 240); + --surface: oklch(1 0 0); + --surface-2: oklch(0.98 0.003 240); + --surface-3: oklch(0.955 0.004 240); + --fg: oklch(0.22 0.015 240); + --fg-muted: oklch(0.48 0.015 240); + --fg-subtle: oklch(0.62 0.012 240); + --border: oklch(0.92 0.005 240); + --border-strong: oklch(0.86 0.007 240); + --ring: oklch(0.72 0.17 162 / 0.4); +} + +/* Dark theme */ +html.dark { + --bg: oklch(0.17 0.012 240); + --bg-subtle: oklch(0.195 0.013 240); + --surface: oklch(0.205 0.014 240); + --surface-2: oklch(0.23 0.014 240); + --surface-3: oklch(0.26 0.015 240); + --fg: oklch(0.96 0.005 240); + --fg-muted: oklch(0.72 0.012 240); + --fg-subtle: oklch(0.58 0.012 240); + --border: oklch(0.3 0.01 240 / 0.5); + --border-strong: oklch(0.35 0.01 240 / 0.7); + --ring: oklch(0.72 0.17 162 / 0.45); +} + +/* Map semantic vars onto @theme's color system so Tailwind utilities work. */ +@theme inline { + --color-bg: var(--bg); + --color-bg-subtle: var(--bg-subtle); + --color-surface: var(--surface); + --color-surface-2: var(--surface-2); + --color-surface-3: var(--surface-3); + --color-fg: var(--fg); + --color-fg-muted: var(--fg-muted); + --color-fg-subtle: var(--fg-subtle); + --color-border: var(--border); + --color-border-strong: var(--border-strong); + --color-ring: var(--ring); +} + +* { + box-sizing: border-box; + border-color: var(--border); +} + +html, +body { + margin: 0; + min-height: 100vh; +} + +body { + font-family: var(--font-sans); + background: var(--bg); + color: var(--fg); + font-feature-settings: "cv02", "cv03", "cv04", "cv11"; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; +} + +/* Subtle scrollbars */ +::-webkit-scrollbar { + width: 10px; + height: 10px; +} +::-webkit-scrollbar-track { + background: transparent; +} +::-webkit-scrollbar-thumb { + background: var(--border-strong); + border: 2px solid var(--bg); + border-radius: 8px; +} +::-webkit-scrollbar-thumb:hover { + background: var(--fg-subtle); +} + +/* Focus rings */ +*:focus-visible { + outline: 2px solid var(--color-accent); + outline-offset: 2px; + border-radius: var(--radius-sm); +} + +/* Monospace helper */ +.font-mono { + font-family: var(--font-mono); + font-feature-settings: "zero", "ss02"; +} + +/* Animations */ +@keyframes fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } +} +@keyframes slide-up { + from { + opacity: 0; + transform: translateY(4px); + } + to { + opacity: 1; + transform: translateY(0); + } +} +@keyframes shimmer { + from { + background-position: -200% 0; + } + to { + background-position: 200% 0; + } +} + +.animate-fade-in { + animation: fade-in 0.2s ease-out; +} +.animate-slide-up { + animation: slide-up 0.24s ease-out; +} +.animate-shimmer { + background: linear-gradient( + 90deg, + var(--surface-2) 25%, + var(--surface-3) 50%, + var(--surface-2) 75% + ); + background-size: 200% 100%; + animation: shimmer 1.4s infinite; +} diff --git a/dashboard-next/src/main.tsx b/dashboard-next/src/main.tsx new file mode 100644 index 0000000..f4132f9 --- /dev/null +++ b/dashboard-next/src/main.tsx @@ -0,0 +1,28 @@ +import { createRouter, RouterProvider } from "@tanstack/react-router"; +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { Providers } from "@/providers"; +import { routeTree } from "./route-tree.gen"; +import "./globals.css"; + +const router = createRouter({ + routeTree, + defaultPreload: "intent", +}); + +declare module "@tanstack/react-router" { + interface Register { + router: typeof router; + } +} + +const rootEl = document.getElementById("app"); +if (!rootEl) throw new Error("#app element not found"); + +createRoot(rootEl).render( + + + + + , +); diff --git a/dashboard-next/src/vite-env.d.ts b/dashboard-next/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/dashboard-next/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/dashboard-next/tsconfig.json b/dashboard-next/tsconfig.json new file mode 100644 index 0000000..b003e88 --- /dev/null +++ b/dashboard-next/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "moduleResolution": "bundler", + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "jsx": "react-jsx", + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + "types": ["node", "vite/client"] + }, + "include": ["src", "vite.config.ts"] +} diff --git a/dashboard-next/vite.config.ts b/dashboard-next/vite.config.ts new file mode 100644 index 0000000..f428239 --- /dev/null +++ b/dashboard-next/vite.config.ts @@ -0,0 +1,39 @@ +import path from "node:path"; +import { TanStackRouterVite } from "@tanstack/router-plugin/vite"; +import tailwindcss from "@tailwindcss/vite"; +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; + +const backend = "http://127.0.0.1:8080"; + +export default defineConfig({ + plugins: [ + TanStackRouterVite({ + routesDirectory: "./src/routes", + generatedRouteTree: "./src/route-tree.gen.ts", + quoteStyle: "double", + semicolons: true, + }), + react(), + tailwindcss(), + ], + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + }, + }, + build: { + outDir: "dist", + emptyOutDir: true, + sourcemap: false, + target: "es2022", + }, + server: { + proxy: { + "/api": backend, + "/health": backend, + "/readiness": backend, + "/metrics": backend, + }, + }, +}); From 8b973eebcbaa2d25b156501e9a66eb8aa4beb261 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 00:32:25 +0530 Subject: [PATCH 02/42] feat(dashboard-next): add lib utilities and providers --- dashboard-next/src/lib/api-client.ts | 74 ++++++++++++++++++ dashboard-next/src/lib/cn.ts | 6 ++ dashboard-next/src/lib/number.ts | 21 ++++++ dashboard-next/src/lib/site.ts | 6 ++ dashboard-next/src/lib/status.ts | 51 +++++++++++++ dashboard-next/src/lib/time.ts | 46 ++++++++++++ .../providers/command-palette-provider.tsx | 44 +++++++++++ dashboard-next/src/providers/index.tsx | 23 ++++++ .../src/providers/query-provider.tsx | 35 +++++++++ .../providers/refresh-interval-provider.tsx | 50 +++++++++++++ .../src/providers/theme-provider.tsx | 75 +++++++++++++++++++ 11 files changed, 431 insertions(+) create mode 100644 dashboard-next/src/lib/api-client.ts create mode 100644 dashboard-next/src/lib/cn.ts create mode 100644 dashboard-next/src/lib/number.ts create mode 100644 dashboard-next/src/lib/site.ts create mode 100644 dashboard-next/src/lib/status.ts create mode 100644 dashboard-next/src/lib/time.ts create mode 100644 dashboard-next/src/providers/command-palette-provider.tsx create mode 100644 dashboard-next/src/providers/index.tsx create mode 100644 dashboard-next/src/providers/query-provider.tsx create mode 100644 dashboard-next/src/providers/refresh-interval-provider.tsx create mode 100644 dashboard-next/src/providers/theme-provider.tsx diff --git a/dashboard-next/src/lib/api-client.ts b/dashboard-next/src/lib/api-client.ts new file mode 100644 index 0000000..153327a --- /dev/null +++ b/dashboard-next/src/lib/api-client.ts @@ -0,0 +1,74 @@ +const API_BASE = import.meta.env.VITE_API_BASE ?? ""; + +export class ApiError extends Error { + readonly status: number; + readonly body: unknown; + + constructor(message: string, status: number, body: unknown) { + super(message); + this.name = "ApiError"; + this.status = status; + this.body = body; + } +} + +type Query = Record; + +function buildUrl(path: string, params?: Query): string { + const url = `${API_BASE}${path}`; + if (!params) return url; + const search = new URLSearchParams(); + for (const [key, value] of Object.entries(params)) { + if (value === null || value === undefined || value === "") continue; + search.set(key, String(value)); + } + const query = search.toString(); + return query ? `${url}?${query}` : url; +} + +async function parse(response: Response): Promise { + const contentType = response.headers.get("content-type") ?? ""; + const payload: unknown = contentType.includes("application/json") + ? await response.json() + : await response.text(); + + if (!response.ok) { + const message = + typeof payload === "object" && payload && "error" in payload + ? String((payload as { error: unknown }).error) + : `Request failed with status ${response.status}`; + throw new ApiError(message, response.status, payload); + } + return payload as T; +} + +export interface RequestOptions { + signal?: AbortSignal; + params?: Query; + headers?: Record; +} + +export const api = { + async get(path: string, options: RequestOptions = {}): Promise { + const response = await fetch(buildUrl(path, options.params), { + method: "GET", + signal: options.signal, + headers: { Accept: "application/json", ...options.headers }, + }); + return parse(response); + }, + + async post(path: string, body?: unknown, options: RequestOptions = {}): Promise { + const response = await fetch(buildUrl(path, options.params), { + method: "POST", + signal: options.signal, + headers: { + Accept: "application/json", + ...(body !== undefined ? { "Content-Type": "application/json" } : {}), + ...options.headers, + }, + body: body === undefined ? undefined : JSON.stringify(body), + }); + return parse(response); + }, +}; diff --git a/dashboard-next/src/lib/cn.ts b/dashboard-next/src/lib/cn.ts new file mode 100644 index 0000000..a70ebb6 --- /dev/null +++ b/dashboard-next/src/lib/cn.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]): string { + return twMerge(clsx(inputs)); +} diff --git a/dashboard-next/src/lib/number.ts b/dashboard-next/src/lib/number.ts new file mode 100644 index 0000000..ac73d53 --- /dev/null +++ b/dashboard-next/src/lib/number.ts @@ -0,0 +1,21 @@ +const COMPACT = new Intl.NumberFormat("en", { notation: "compact", maximumFractionDigits: 1 }); +const INTEGER = new Intl.NumberFormat("en"); + +export function formatCount(n: number): string { + if (!Number.isFinite(n)) return "—"; + if (Math.abs(n) < 1000) return INTEGER.format(n); + return COMPACT.format(n); +} + +export function formatPercent(value: number, digits = 1): string { + if (!Number.isFinite(value)) return "—"; + return `${(value * 100).toFixed(digits)}%`; +} + +export function formatBytes(bytes: number): string { + if (!Number.isFinite(bytes)) return "—"; + const units = ["B", "KB", "MB", "GB", "TB"]; + const exp = Math.min(units.length - 1, Math.floor(Math.log(Math.max(bytes, 1)) / Math.log(1024))); + const value = bytes / 1024 ** exp; + return `${value.toFixed(value < 10 ? 2 : 1)} ${units[exp]}`; +} diff --git a/dashboard-next/src/lib/site.ts b/dashboard-next/src/lib/site.ts new file mode 100644 index 0000000..cf75717 --- /dev/null +++ b/dashboard-next/src/lib/site.ts @@ -0,0 +1,6 @@ +export const site = { + name: "Taskito", + shortName: "Taskito", + docsUrl: "https://byteveda.github.io/taskito", + repoUrl: "https://github.com/ByteVeda/taskito", +} as const; diff --git a/dashboard-next/src/lib/status.ts b/dashboard-next/src/lib/status.ts new file mode 100644 index 0000000..1be36e9 --- /dev/null +++ b/dashboard-next/src/lib/status.ts @@ -0,0 +1,51 @@ +export type JobStatus = + | "pending" + | "running" + | "completed" + | "failed" + | "dead" + | "cancelled" + | "scheduled"; + +export type CircuitState = "closed" | "open" | "half_open"; + +export type ResourceHealth = "healthy" | "degraded" | "unhealthy" | "unknown"; + +type ToneKey = "neutral" | "accent" | "info" | "success" | "warning" | "danger"; + +const TONE_CLASS: Record = { + neutral: + "bg-[var(--surface-3)] text-[var(--fg-muted)] ring-1 ring-inset ring-[var(--border-strong)]", + accent: "bg-accent-dim text-accent ring-1 ring-inset ring-accent/30", + info: "bg-info-dim text-info ring-1 ring-inset ring-info/30", + success: "bg-success-dim text-success ring-1 ring-inset ring-success/30", + warning: "bg-warning-dim text-warning ring-1 ring-inset ring-warning/30", + danger: "bg-danger-dim text-danger ring-1 ring-inset ring-danger/30", +}; + +export const JOB_STATUS_TONE: Record = { + pending: "neutral", + running: "info", + scheduled: "accent", + completed: "success", + failed: "danger", + dead: "danger", + cancelled: "warning", +}; + +export const CIRCUIT_TONE: Record = { + closed: "success", + half_open: "warning", + open: "danger", +}; + +export const RESOURCE_TONE: Record = { + healthy: "success", + degraded: "warning", + unhealthy: "danger", + unknown: "neutral", +}; + +export function toneClasses(tone: ToneKey): string { + return TONE_CLASS[tone]; +} diff --git a/dashboard-next/src/lib/time.ts b/dashboard-next/src/lib/time.ts new file mode 100644 index 0000000..40d3c68 --- /dev/null +++ b/dashboard-next/src/lib/time.ts @@ -0,0 +1,46 @@ +const RTF = new Intl.RelativeTimeFormat("en", { numeric: "auto" }); + +export function formatAbsolute(iso: string | number | Date): string { + const date = typeof iso === "string" || typeof iso === "number" ? new Date(iso) : iso; + return date.toLocaleString(undefined, { + year: "numeric", + month: "short", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }); +} + +const UNITS: Array<[Intl.RelativeTimeFormatUnit, number]> = [ + ["year", 60 * 60 * 24 * 365], + ["month", 60 * 60 * 24 * 30], + ["day", 60 * 60 * 24], + ["hour", 60 * 60], + ["minute", 60], + ["second", 1], +]; + +export function formatRelative(iso: string | number | Date, nowMs?: number): string { + const target = typeof iso === "string" || typeof iso === "number" ? new Date(iso) : iso; + const now = nowMs ?? Date.now(); + const diffSec = Math.round((target.getTime() - now) / 1000); + for (const [unit, step] of UNITS) { + if (Math.abs(diffSec) >= step || unit === "second") { + return RTF.format(Math.round(diffSec / step), unit); + } + } + return RTF.format(diffSec, "second"); +} + +export function formatDuration(ms: number): string { + if (ms < 1000) return `${ms.toFixed(0)}ms`; + const s = ms / 1000; + if (s < 60) return `${s.toFixed(s < 10 ? 2 : 1)}s`; + const m = Math.floor(s / 60); + const rs = Math.round(s - m * 60); + if (m < 60) return `${m}m ${rs}s`; + const h = Math.floor(m / 60); + const rm = m - h * 60; + return `${h}h ${rm}m`; +} diff --git a/dashboard-next/src/providers/command-palette-provider.tsx b/dashboard-next/src/providers/command-palette-provider.tsx new file mode 100644 index 0000000..ea7cafe --- /dev/null +++ b/dashboard-next/src/providers/command-palette-provider.tsx @@ -0,0 +1,44 @@ +import { + createContext, + type ReactNode, + useCallback, + useContext, + useEffect, + useMemo, + useState, +} from "react"; + +interface CommandPaletteContextValue { + open: boolean; + setOpen: (open: boolean) => void; + toggle: () => void; +} + +const CommandPaletteContext = createContext(null); + +export function CommandPaletteProvider({ children }: { children: ReactNode }) { + const [open, setOpen] = useState(false); + const toggle = useCallback(() => setOpen((v) => !v), []); + + useEffect(() => { + function onKeyDown(event: KeyboardEvent) { + if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") { + event.preventDefault(); + setOpen((v) => !v); + } else if (event.key === "Escape") { + setOpen(false); + } + } + window.addEventListener("keydown", onKeyDown); + return () => window.removeEventListener("keydown", onKeyDown); + }, []); + + const value = useMemo(() => ({ open, setOpen, toggle }), [open, toggle]); + return {children}; +} + +export function useCommandPalette(): CommandPaletteContextValue { + const ctx = useContext(CommandPaletteContext); + if (!ctx) throw new Error("useCommandPalette must be used within CommandPaletteProvider"); + return ctx; +} diff --git a/dashboard-next/src/providers/index.tsx b/dashboard-next/src/providers/index.tsx new file mode 100644 index 0000000..8ea3d73 --- /dev/null +++ b/dashboard-next/src/providers/index.tsx @@ -0,0 +1,23 @@ +import type { ReactNode } from "react"; +import { CommandPaletteProvider } from "./command-palette-provider"; +import { QueryProvider } from "./query-provider"; +import { RefreshIntervalProvider } from "./refresh-interval-provider"; +import { ThemeProvider } from "./theme-provider"; + +export function Providers({ children }: { children: ReactNode }) { + return ( + + + + {children} + + + + ); +} + +export { useCommandPalette } from "./command-palette-provider"; +export type { RefreshOption } from "./refresh-interval-provider"; +export { useRefreshInterval } from "./refresh-interval-provider"; +export type { ResolvedTheme, Theme } from "./theme-provider"; +export { useTheme } from "./theme-provider"; diff --git a/dashboard-next/src/providers/query-provider.tsx b/dashboard-next/src/providers/query-provider.tsx new file mode 100644 index 0000000..de077ef --- /dev/null +++ b/dashboard-next/src/providers/query-provider.tsx @@ -0,0 +1,35 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { type ReactNode, useState } from "react"; + +function createQueryClient() { + return new QueryClient({ + defaultOptions: { + queries: { + staleTime: 5_000, + gcTime: 5 * 60_000, + refetchOnWindowFocus: true, + retry: (failureCount, error) => { + const status = (error as { status?: number }).status; + if (status && status >= 400 && status < 500) return false; + return failureCount < 2; + }, + }, + mutations: { + retry: false, + }, + }, + }); +} + +export function QueryProvider({ children }: { children: ReactNode }) { + const [client] = useState(createQueryClient); + return ( + + {children} + {import.meta.env.DEV ? ( + + ) : null} + + ); +} diff --git a/dashboard-next/src/providers/refresh-interval-provider.tsx b/dashboard-next/src/providers/refresh-interval-provider.tsx new file mode 100644 index 0000000..073b2ab --- /dev/null +++ b/dashboard-next/src/providers/refresh-interval-provider.tsx @@ -0,0 +1,50 @@ +import { createContext, type ReactNode, useContext, useMemo, useState } from "react"; + +export type RefreshOption = "2s" | "5s" | "10s" | "off"; + +const REFRESH_MS: Record = { + "2s": 2_000, + "5s": 5_000, + "10s": 10_000, + off: false, +}; + +const STORAGE_KEY = "taskito.refresh"; + +interface RefreshContextValue { + option: RefreshOption; + intervalMs: number | false; + setOption: (option: RefreshOption) => void; +} + +const RefreshContext = createContext(null); + +function readStored(): RefreshOption { + const stored = localStorage.getItem(STORAGE_KEY); + if (stored === "2s" || stored === "5s" || stored === "10s" || stored === "off") return stored; + return "5s"; +} + +export function RefreshIntervalProvider({ children }: { children: ReactNode }) { + const [option, setOptionState] = useState(() => readStored()); + + const value = useMemo( + () => ({ + option, + intervalMs: REFRESH_MS[option], + setOption: (next) => { + setOptionState(next); + localStorage.setItem(STORAGE_KEY, next); + }, + }), + [option], + ); + + return {children}; +} + +export function useRefreshInterval(): RefreshContextValue { + const ctx = useContext(RefreshContext); + if (!ctx) throw new Error("useRefreshInterval must be used within RefreshIntervalProvider"); + return ctx; +} diff --git a/dashboard-next/src/providers/theme-provider.tsx b/dashboard-next/src/providers/theme-provider.tsx new file mode 100644 index 0000000..58f1bb0 --- /dev/null +++ b/dashboard-next/src/providers/theme-provider.tsx @@ -0,0 +1,75 @@ +import { createContext, type ReactNode, useCallback, useContext, useEffect, useState } from "react"; + +export type Theme = "light" | "dark" | "system"; +export type ResolvedTheme = "light" | "dark"; + +const STORAGE_KEY = "taskito.theme"; + +interface ThemeContextValue { + theme: Theme; + resolved: ResolvedTheme; + setTheme: (theme: Theme) => void; + toggle: () => void; +} + +const ThemeContext = createContext(null); + +function readSystem(): ResolvedTheme { + return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; +} + +function readStored(): Theme { + const stored = localStorage.getItem(STORAGE_KEY); + if (stored === "light" || stored === "dark" || stored === "system") return stored; + return "system"; +} + +function applyTheme(resolved: ResolvedTheme) { + document.documentElement.classList.toggle("dark", resolved === "dark"); +} + +export function ThemeProvider({ children }: { children: ReactNode }) { + const [theme, setThemeState] = useState(() => readStored()); + const [resolved, setResolved] = useState(() => + readStored() === "system" ? readSystem() : (readStored() as ResolvedTheme), + ); + + useEffect(() => { + const next = theme === "system" ? readSystem() : theme; + setResolved(next); + applyTheme(next); + localStorage.setItem(STORAGE_KEY, theme); + }, [theme]); + + useEffect(() => { + if (theme !== "system") return; + const media = window.matchMedia("(prefers-color-scheme: dark)"); + const handler = () => { + const next = media.matches ? "dark" : "light"; + setResolved(next); + applyTheme(next); + }; + media.addEventListener("change", handler); + return () => media.removeEventListener("change", handler); + }, [theme]); + + const setTheme = useCallback((next: Theme) => setThemeState(next), []); + const toggle = useCallback(() => { + setThemeState((prev) => { + const current = prev === "system" ? readSystem() : prev; + return current === "dark" ? "light" : "dark"; + }); + }, []); + + return ( + + {children} + + ); +} + +export function useTheme(): ThemeContextValue { + const ctx = useContext(ThemeContext); + if (!ctx) throw new Error("useTheme must be used within ThemeProvider"); + return ctx; +} From 22101ec3f59ec8a76ef2b2d94cd7cba4f753248a Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 00:32:29 +0530 Subject: [PATCH 03/42] feat(dashboard-next): add app shell, primitives, route stubs --- .../src/components/layout/app-shell.tsx | 19 +++ .../src/components/layout/command-palette.tsx | 139 ++++++++++++++++++ .../src/components/layout/header.tsx | 33 +++++ dashboard-next/src/components/layout/index.ts | 7 + .../src/components/layout/page-header.tsx | 32 ++++ .../src/components/layout/refresh-control.tsx | 35 +++++ .../src/components/layout/sidebar.tsx | 108 ++++++++++++++ .../src/components/layout/theme-toggle.tsx | 43 ++++++ dashboard-next/src/components/ui/badge.tsx | 33 +++++ dashboard-next/src/components/ui/button.tsx | 49 ++++++ dashboard-next/src/components/ui/card.tsx | 60 ++++++++ .../src/components/ui/empty-state.tsx | 35 +++++ dashboard-next/src/components/ui/index.ts | 8 + dashboard-next/src/components/ui/input.tsx | 21 +++ dashboard-next/src/components/ui/kbd.tsx | 15 ++ .../src/components/ui/separator.tsx | 20 +++ dashboard-next/src/components/ui/skeleton.tsx | 8 + dashboard-next/src/routes/__root.tsx | 57 +++++++ .../src/routes/circuit-breakers.tsx | 20 +++ dashboard-next/src/routes/dead-letters.tsx | 17 +++ dashboard-next/src/routes/index.tsx | 42 ++++++ dashboard-next/src/routes/jobs/$id.tsx | 31 ++++ dashboard-next/src/routes/jobs/index.tsx | 21 +++ dashboard-next/src/routes/logs.tsx | 17 +++ dashboard-next/src/routes/metrics.tsx | 17 +++ dashboard-next/src/routes/queues.tsx | 17 +++ dashboard-next/src/routes/resources.tsx | 17 +++ dashboard-next/src/routes/system.tsx | 17 +++ dashboard-next/src/routes/workers.tsx | 17 +++ 29 files changed, 955 insertions(+) create mode 100644 dashboard-next/src/components/layout/app-shell.tsx create mode 100644 dashboard-next/src/components/layout/command-palette.tsx create mode 100644 dashboard-next/src/components/layout/header.tsx create mode 100644 dashboard-next/src/components/layout/index.ts create mode 100644 dashboard-next/src/components/layout/page-header.tsx create mode 100644 dashboard-next/src/components/layout/refresh-control.tsx create mode 100644 dashboard-next/src/components/layout/sidebar.tsx create mode 100644 dashboard-next/src/components/layout/theme-toggle.tsx create mode 100644 dashboard-next/src/components/ui/badge.tsx create mode 100644 dashboard-next/src/components/ui/button.tsx create mode 100644 dashboard-next/src/components/ui/card.tsx create mode 100644 dashboard-next/src/components/ui/empty-state.tsx create mode 100644 dashboard-next/src/components/ui/index.ts create mode 100644 dashboard-next/src/components/ui/input.tsx create mode 100644 dashboard-next/src/components/ui/kbd.tsx create mode 100644 dashboard-next/src/components/ui/separator.tsx create mode 100644 dashboard-next/src/components/ui/skeleton.tsx create mode 100644 dashboard-next/src/routes/__root.tsx create mode 100644 dashboard-next/src/routes/circuit-breakers.tsx create mode 100644 dashboard-next/src/routes/dead-letters.tsx create mode 100644 dashboard-next/src/routes/index.tsx create mode 100644 dashboard-next/src/routes/jobs/$id.tsx create mode 100644 dashboard-next/src/routes/jobs/index.tsx create mode 100644 dashboard-next/src/routes/logs.tsx create mode 100644 dashboard-next/src/routes/metrics.tsx create mode 100644 dashboard-next/src/routes/queues.tsx create mode 100644 dashboard-next/src/routes/resources.tsx create mode 100644 dashboard-next/src/routes/system.tsx create mode 100644 dashboard-next/src/routes/workers.tsx diff --git a/dashboard-next/src/components/layout/app-shell.tsx b/dashboard-next/src/components/layout/app-shell.tsx new file mode 100644 index 0000000..2430183 --- /dev/null +++ b/dashboard-next/src/components/layout/app-shell.tsx @@ -0,0 +1,19 @@ +import type { ReactNode } from "react"; +import { CommandPalette } from "./command-palette"; +import { Header } from "./header"; +import { Sidebar } from "./sidebar"; + +export function AppShell({ children }: { children: ReactNode }) { + return ( +
+ +
+
+
+
{children}
+
+
+ +
+ ); +} diff --git a/dashboard-next/src/components/layout/command-palette.tsx b/dashboard-next/src/components/layout/command-palette.tsx new file mode 100644 index 0000000..4e2b859 --- /dev/null +++ b/dashboard-next/src/components/layout/command-palette.tsx @@ -0,0 +1,139 @@ +import { useNavigate } from "@tanstack/react-router"; +import { Search } from "lucide-react"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { Kbd } from "@/components/ui/kbd"; +import { cn } from "@/lib/cn"; +import { useCommandPalette } from "@/providers/command-palette-provider"; + +interface CommandItem { + id: string; + label: string; + hint?: string; + to: string; + keywords?: string; +} + +const COMMANDS: CommandItem[] = [ + { id: "nav.overview", label: "Go to Overview", to: "/", hint: "Home" }, + { id: "nav.jobs", label: "Go to Jobs", to: "/jobs", keywords: "tasks list" }, + { id: "nav.metrics", label: "Go to Metrics", to: "/metrics", keywords: "graphs charts" }, + { id: "nav.logs", label: "Go to Logs", to: "/logs" }, + { id: "nav.queues", label: "Go to Queues", to: "/queues", keywords: "pause resume" }, + { id: "nav.workers", label: "Go to Workers", to: "/workers" }, + { id: "nav.resources", label: "Go to Resources", to: "/resources" }, + { + id: "nav.dead-letters", + label: "Go to Dead letters", + to: "/dead-letters", + keywords: "dlq retry", + }, + { + id: "nav.circuit-breakers", + label: "Go to Circuit breakers", + to: "/circuit-breakers", + keywords: "fault tolerance", + }, + { id: "nav.system", label: "Go to System", to: "/system", keywords: "proxy interception" }, +]; + +export function CommandPalette() { + const { open, setOpen } = useCommandPalette(); + const navigate = useNavigate(); + const inputRef = useRef(null); + const [query, setQuery] = useState(""); + const [activeIndex, setActiveIndex] = useState(0); + + useEffect(() => { + if (open) { + setQuery(""); + setActiveIndex(0); + queueMicrotask(() => inputRef.current?.focus()); + } + }, [open]); + + const normalized = query.trim().toLowerCase(); + const results = normalized + ? COMMANDS.filter((c) => `${c.label} ${c.keywords ?? ""}`.toLowerCase().includes(normalized)) + : COMMANDS; + + const runAt = useCallback( + (index: number) => { + const item = results[index]; + if (!item) return; + setOpen(false); + navigate({ to: item.to }); + }, + [navigate, results, setOpen], + ); + + useEffect(() => { + if (activeIndex >= results.length) setActiveIndex(Math.max(0, results.length - 1)); + }, [activeIndex, results.length]); + + if (!open) return null; + + return ( +
+
setOpen(false)} /> +
{ + if (event.key === "ArrowDown") { + event.preventDefault(); + setActiveIndex((i) => Math.min(i + 1, results.length - 1)); + } else if (event.key === "ArrowUp") { + event.preventDefault(); + setActiveIndex((i) => Math.max(i - 1, 0)); + } else if (event.key === "Enter") { + event.preventDefault(); + runAt(activeIndex); + } + }} + > +
+ + setQuery(e.target.value)} + placeholder="Type a command or search…" + className="flex-1 bg-transparent text-sm outline-none placeholder:text-[var(--fg-subtle)]" + aria-label="Search" + /> + Esc +
+
    + {results.length === 0 ? ( +
  • No matches
  • + ) : ( + results.map((item, i) => ( +
  • + +
  • + )) + )} +
+
+
+ ); +} diff --git a/dashboard-next/src/components/layout/header.tsx b/dashboard-next/src/components/layout/header.tsx new file mode 100644 index 0000000..5f41fa3 --- /dev/null +++ b/dashboard-next/src/components/layout/header.tsx @@ -0,0 +1,33 @@ +import { Search } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Kbd } from "@/components/ui/kbd"; +import { useCommandPalette } from "@/providers/command-palette-provider"; +import { RefreshControl } from "./refresh-control"; +import { ThemeToggle } from "./theme-toggle"; + +export function Header() { + const { setOpen } = useCommandPalette(); + return ( +
+ +
+ + +
+
+ ); +} diff --git a/dashboard-next/src/components/layout/index.ts b/dashboard-next/src/components/layout/index.ts new file mode 100644 index 0000000..7bf00ad --- /dev/null +++ b/dashboard-next/src/components/layout/index.ts @@ -0,0 +1,7 @@ +export { AppShell } from "./app-shell"; +export { CommandPalette } from "./command-palette"; +export { Header } from "./header"; +export { PageHeader } from "./page-header"; +export { RefreshControl } from "./refresh-control"; +export { Sidebar } from "./sidebar"; +export { ThemeToggle } from "./theme-toggle"; diff --git a/dashboard-next/src/components/layout/page-header.tsx b/dashboard-next/src/components/layout/page-header.tsx new file mode 100644 index 0000000..f608a5a --- /dev/null +++ b/dashboard-next/src/components/layout/page-header.tsx @@ -0,0 +1,32 @@ +import type { ReactNode } from "react"; +import { cn } from "@/lib/cn"; + +interface PageHeaderProps { + eyebrow?: string; + title: string; + description?: string; + actions?: ReactNode; + className?: string; +} + +export function PageHeader({ eyebrow, title, description, actions, className }: PageHeaderProps) { + return ( +
+
+ {eyebrow ? ( +
+ {eyebrow} +
+ ) : null} +

{title}

+ {description ?

{description}

: null} +
+ {actions ?
{actions}
: null} +
+ ); +} diff --git a/dashboard-next/src/components/layout/refresh-control.tsx b/dashboard-next/src/components/layout/refresh-control.tsx new file mode 100644 index 0000000..5b9ee52 --- /dev/null +++ b/dashboard-next/src/components/layout/refresh-control.tsx @@ -0,0 +1,35 @@ +import { cn } from "@/lib/cn"; +import { type RefreshOption, useRefreshInterval } from "@/providers/refresh-interval-provider"; + +const OPTIONS: RefreshOption[] = ["2s", "5s", "10s", "off"]; + +export function RefreshControl() { + const { option, setOption } = useRefreshInterval(); + return ( +
+ {OPTIONS.map((value) => { + const active = option === value; + return ( + + ); + })} +
+ ); +} diff --git a/dashboard-next/src/components/layout/sidebar.tsx b/dashboard-next/src/components/layout/sidebar.tsx new file mode 100644 index 0000000..30910a7 --- /dev/null +++ b/dashboard-next/src/components/layout/sidebar.tsx @@ -0,0 +1,108 @@ +import { Link, useLocation } from "@tanstack/react-router"; +import { + Activity, + AlertOctagon, + BarChart3, + Box, + CircuitBoard, + LayoutDashboard, + ListTree, + type LucideIcon, + ScrollText, + Server, + Settings2, + Skull, +} from "lucide-react"; +import { cn } from "@/lib/cn"; +import { site } from "@/lib/site"; + +interface NavItem { + to: string; + label: string; + icon: LucideIcon; +} + +interface NavGroup { + title: string; + items: NavItem[]; +} + +const NAV: NavGroup[] = [ + { + title: "Monitoring", + items: [ + { to: "/", label: "Overview", icon: LayoutDashboard }, + { to: "/jobs", label: "Jobs", icon: ListTree }, + { to: "/metrics", label: "Metrics", icon: BarChart3 }, + { to: "/logs", label: "Logs", icon: ScrollText }, + ], + }, + { + title: "Infrastructure", + items: [ + { to: "/queues", label: "Queues", icon: Box }, + { to: "/workers", label: "Workers", icon: Server }, + { to: "/resources", label: "Resources", icon: Activity }, + ], + }, + { + title: "Reliability", + items: [ + { to: "/dead-letters", label: "Dead letters", icon: Skull }, + { to: "/circuit-breakers", label: "Circuit breakers", icon: CircuitBoard }, + { to: "/system", label: "System", icon: Settings2 }, + ], + }, +]; + +export function Sidebar() { + const { pathname } = useLocation(); + return ( + + ); +} diff --git a/dashboard-next/src/components/layout/theme-toggle.tsx b/dashboard-next/src/components/layout/theme-toggle.tsx new file mode 100644 index 0000000..649ae35 --- /dev/null +++ b/dashboard-next/src/components/layout/theme-toggle.tsx @@ -0,0 +1,43 @@ +import { Laptop, Moon, Sun } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/cn"; +import { type Theme, useTheme } from "@/providers/theme-provider"; + +const OPTIONS: Array<{ value: Theme; label: string; icon: typeof Sun }> = [ + { value: "light", label: "Light", icon: Sun }, + { value: "dark", label: "Dark", icon: Moon }, + { value: "system", label: "System", icon: Laptop }, +]; + +export function ThemeToggle() { + const { theme, setTheme } = useTheme(); + return ( +
+ {OPTIONS.map(({ value, label, icon: Icon }) => { + const active = theme === value; + return ( + + ); + })} +
+ ); +} diff --git a/dashboard-next/src/components/ui/badge.tsx b/dashboard-next/src/components/ui/badge.tsx new file mode 100644 index 0000000..2cfe305 --- /dev/null +++ b/dashboard-next/src/components/ui/badge.tsx @@ -0,0 +1,33 @@ +import { cva, type VariantProps } from "class-variance-authority"; +import type { HTMLAttributes } from "react"; +import { cn } from "@/lib/cn"; + +const badgeVariants = cva( + "inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium tracking-tight", + { + variants: { + tone: { + neutral: + "bg-[var(--surface-3)] text-[var(--fg-muted)] ring-1 ring-inset ring-[var(--border-strong)]", + accent: "bg-accent-dim text-accent ring-1 ring-inset ring-accent/30", + info: "bg-info-dim text-info ring-1 ring-inset ring-info/30", + success: "bg-success-dim text-success ring-1 ring-inset ring-success/30", + warning: "bg-warning-dim text-warning ring-1 ring-inset ring-warning/30", + danger: "bg-danger-dim text-danger ring-1 ring-inset ring-danger/30", + }, + }, + defaultVariants: { + tone: "neutral", + }, + }, +); + +export interface BadgeProps + extends HTMLAttributes, + VariantProps {} + +export function Badge({ className, tone, ...props }: BadgeProps) { + return ; +} + +export { badgeVariants }; diff --git a/dashboard-next/src/components/ui/button.tsx b/dashboard-next/src/components/ui/button.tsx new file mode 100644 index 0000000..cc24c0d --- /dev/null +++ b/dashboard-next/src/components/ui/button.tsx @@ -0,0 +1,49 @@ +import { cva, type VariantProps } from "class-variance-authority"; +import { type ButtonHTMLAttributes, forwardRef } from "react"; +import { cn } from "@/lib/cn"; + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-ring)] disabled:pointer-events-none disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0", + { + variants: { + variant: { + default: "bg-accent text-accent-fg hover:bg-accent/90 shadow-sm", + secondary: + "bg-[var(--surface-2)] text-[var(--fg)] hover:bg-[var(--surface-3)] ring-1 ring-inset ring-[var(--border-strong)]", + ghost: "text-[var(--fg-muted)] hover:bg-[var(--surface-2)] hover:text-[var(--fg)]", + outline: + "ring-1 ring-inset ring-[var(--border-strong)] bg-transparent text-[var(--fg)] hover:bg-[var(--surface-2)]", + danger: "bg-danger text-white hover:bg-danger/90 shadow-sm", + link: "text-accent underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-3.5 py-2", + sm: "h-8 rounded-md px-3 text-xs", + lg: "h-10 rounded-md px-5", + icon: "size-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); + +export interface ButtonProps + extends ButtonHTMLAttributes, + VariantProps {} + +export const Button = forwardRef( + ({ className, variant, size, type = "button", ...props }, ref) => ( + +
+ + + ); +} + +function NotFoundView() { + return ( +
+
+
+ 404 +
+

Page not found

+

+ The page you are looking for doesn't exist or has been moved. +

+ + Go home + +
+
+ ); +} diff --git a/dashboard-next/src/routes/circuit-breakers.tsx b/dashboard-next/src/routes/circuit-breakers.tsx new file mode 100644 index 0000000..0183002 --- /dev/null +++ b/dashboard-next/src/routes/circuit-breakers.tsx @@ -0,0 +1,20 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { CircuitBoard } from "lucide-react"; +import { PageHeader } from "@/components/layout"; +import { EmptyState } from "@/components/ui/empty-state"; + +export const Route = createFileRoute("/circuit-breakers")({ + component: CircuitBreakersPage, +}); + +function CircuitBreakersPage() { + return ( + <> + + + + ); +} diff --git a/dashboard-next/src/routes/dead-letters.tsx b/dashboard-next/src/routes/dead-letters.tsx new file mode 100644 index 0000000..fbfec2a --- /dev/null +++ b/dashboard-next/src/routes/dead-letters.tsx @@ -0,0 +1,17 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { Skull } from "lucide-react"; +import { PageHeader } from "@/components/layout"; +import { EmptyState } from "@/components/ui/empty-state"; + +export const Route = createFileRoute("/dead-letters")({ + component: DeadLettersPage, +}); + +function DeadLettersPage() { + return ( + <> + + + + ); +} diff --git a/dashboard-next/src/routes/index.tsx b/dashboard-next/src/routes/index.tsx new file mode 100644 index 0000000..e81ee58 --- /dev/null +++ b/dashboard-next/src/routes/index.tsx @@ -0,0 +1,42 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { Clock, ListTree, Pause, Play, Skull } from "lucide-react"; +import { PageHeader } from "@/components/layout"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Skeleton } from "@/components/ui/skeleton"; + +export const Route = createFileRoute("/")({ + component: OverviewPage, +}); + +const STATS = [ + { key: "pending", label: "Pending", icon: Clock, tone: "text-[var(--fg-muted)]" }, + { key: "running", label: "Running", icon: Play, tone: "text-info" }, + { key: "completed", label: "Completed", icon: ListTree, tone: "text-success" }, + { key: "failed", label: "Failed / dead", icon: Skull, tone: "text-danger" }, + { key: "paused", label: "Paused queues", icon: Pause, tone: "text-warning" }, +] as const; + +function OverviewPage() { + return ( + <> + +
+ {STATS.map(({ key, label, icon: Icon, tone }) => ( + + + {label} + + + + + + + ))} +
+ + ); +} diff --git a/dashboard-next/src/routes/jobs/$id.tsx b/dashboard-next/src/routes/jobs/$id.tsx new file mode 100644 index 0000000..972a5c9 --- /dev/null +++ b/dashboard-next/src/routes/jobs/$id.tsx @@ -0,0 +1,31 @@ +import { createFileRoute, Link } from "@tanstack/react-router"; +import { ArrowLeft } from "lucide-react"; +import { PageHeader } from "@/components/layout"; +import { buttonVariants } from "@/components/ui/button"; +import { EmptyState } from "@/components/ui/empty-state"; +import { cn } from "@/lib/cn"; + +export const Route = createFileRoute("/jobs/$id")({ + component: JobDetailPage, +}); + +function JobDetailPage() { + const { id } = Route.useParams(); + return ( + <> + + All jobs + + } + /> + + + ); +} diff --git a/dashboard-next/src/routes/jobs/index.tsx b/dashboard-next/src/routes/jobs/index.tsx new file mode 100644 index 0000000..113ae2c --- /dev/null +++ b/dashboard-next/src/routes/jobs/index.tsx @@ -0,0 +1,21 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { ListTree } from "lucide-react"; +import { PageHeader } from "@/components/layout"; +import { EmptyState } from "@/components/ui/empty-state"; + +export const Route = createFileRoute("/jobs/")({ + component: JobsPage, +}); + +function JobsPage() { + return ( + <> + + + + ); +} diff --git a/dashboard-next/src/routes/logs.tsx b/dashboard-next/src/routes/logs.tsx new file mode 100644 index 0000000..a11706b --- /dev/null +++ b/dashboard-next/src/routes/logs.tsx @@ -0,0 +1,17 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { ScrollText } from "lucide-react"; +import { PageHeader } from "@/components/layout"; +import { EmptyState } from "@/components/ui/empty-state"; + +export const Route = createFileRoute("/logs")({ + component: LogsPage, +}); + +function LogsPage() { + return ( + <> + + + + ); +} diff --git a/dashboard-next/src/routes/metrics.tsx b/dashboard-next/src/routes/metrics.tsx new file mode 100644 index 0000000..1d7d432 --- /dev/null +++ b/dashboard-next/src/routes/metrics.tsx @@ -0,0 +1,17 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { BarChart3 } from "lucide-react"; +import { PageHeader } from "@/components/layout"; +import { EmptyState } from "@/components/ui/empty-state"; + +export const Route = createFileRoute("/metrics")({ + component: MetricsPage, +}); + +function MetricsPage() { + return ( + <> + + + + ); +} diff --git a/dashboard-next/src/routes/queues.tsx b/dashboard-next/src/routes/queues.tsx new file mode 100644 index 0000000..d2db4db --- /dev/null +++ b/dashboard-next/src/routes/queues.tsx @@ -0,0 +1,17 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { Box } from "lucide-react"; +import { PageHeader } from "@/components/layout"; +import { EmptyState } from "@/components/ui/empty-state"; + +export const Route = createFileRoute("/queues")({ + component: QueuesPage, +}); + +function QueuesPage() { + return ( + <> + + + + ); +} diff --git a/dashboard-next/src/routes/resources.tsx b/dashboard-next/src/routes/resources.tsx new file mode 100644 index 0000000..cb70f7f --- /dev/null +++ b/dashboard-next/src/routes/resources.tsx @@ -0,0 +1,17 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { Activity } from "lucide-react"; +import { PageHeader } from "@/components/layout"; +import { EmptyState } from "@/components/ui/empty-state"; + +export const Route = createFileRoute("/resources")({ + component: ResourcesPage, +}); + +function ResourcesPage() { + return ( + <> + + + + ); +} diff --git a/dashboard-next/src/routes/system.tsx b/dashboard-next/src/routes/system.tsx new file mode 100644 index 0000000..1823e95 --- /dev/null +++ b/dashboard-next/src/routes/system.tsx @@ -0,0 +1,17 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { Settings2 } from "lucide-react"; +import { PageHeader } from "@/components/layout"; +import { EmptyState } from "@/components/ui/empty-state"; + +export const Route = createFileRoute("/system")({ + component: SystemPage, +}); + +function SystemPage() { + return ( + <> + + + + ); +} diff --git a/dashboard-next/src/routes/workers.tsx b/dashboard-next/src/routes/workers.tsx new file mode 100644 index 0000000..d1534ff --- /dev/null +++ b/dashboard-next/src/routes/workers.tsx @@ -0,0 +1,17 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { Server } from "lucide-react"; +import { PageHeader } from "@/components/layout"; +import { EmptyState } from "@/components/ui/empty-state"; + +export const Route = createFileRoute("/workers")({ + component: WorkersPage, +}); + +function WorkersPage() { + return ( + <> + + + + ); +} From e249809a987a8616437bd7249cb196d0f129ed75 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 00:32:34 +0530 Subject: [PATCH 04/42] ci: add dashboard-next lint job --- .github/workflows/ci.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 66d0ad0..c0e5b8d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,6 +82,35 @@ jobs: - name: Build check run: cd dashboard && pnpm exec vite build + dashboard-next-lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Set up pnpm + uses: pnpm/action-setup@v4 + with: + version: "10.30.3" + + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: "22" + cache: "pnpm" + cache-dependency-path: dashboard-next/pnpm-lock.yaml + + - name: Install dependencies + run: cd dashboard-next && pnpm install --frozen-lockfile + + - name: Biome lint + format check + run: cd dashboard-next && pnpm exec biome ci src/ + + - name: TypeScript check + run: cd dashboard-next && pnpm exec tsc --noEmit + + - name: Build check + run: cd dashboard-next && pnpm exec vite build + rust-test: runs-on: ubuntu-latest steps: From e56617b2e212e19ca7eb26a49c76682be75ae394 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 00:40:03 +0530 Subject: [PATCH 05/42] feat(dashboard): serve multi-file SPA with legacy fallback --- py_src/taskito/dashboard.py | 170 +++++++++++++++++++++++++++++++----- 1 file changed, 149 insertions(+), 21 deletions(-) diff --git a/py_src/taskito/dashboard.py b/py_src/taskito/dashboard.py index a32178c..6b677ae 100644 --- a/py_src/taskito/dashboard.py +++ b/py_src/taskito/dashboard.py @@ -9,12 +9,22 @@ from taskito.dashboard import serve_dashboard serve_dashboard(queue, host="0.0.0.0", port=8080) + +Static asset delivery supports two layouts: + +- **Multi-file SPA** at ``py_src/taskito/static/dashboard/`` with + ``index.html`` plus hashed ``assets/`` produced by the new Vite build. +- **Legacy single-file HTML** at ``py_src/taskito/templates/dashboard.html`` + as a fallback for older wheels that don't ship the multi-file layout. + +Whichever layout is present at startup wins; there's no runtime switch. """ from __future__ import annotations import json import logging +import os import re import threading from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer @@ -35,27 +45,115 @@ def __init__(self, message: str) -> None: self.message = message +class _NotFound(Exception): + """Raised by route handlers to signal a 404 response.""" + + def __init__(self, message: str) -> None: + self.message = message + + +# ── Static asset delivery ──────────────────────────────────────────── + +_CONTENT_TYPES: dict[str, str] = { + ".html": "text/html; charset=utf-8", + ".js": "application/javascript; charset=utf-8", + ".mjs": "application/javascript; charset=utf-8", + ".css": "text/css; charset=utf-8", + ".json": "application/json; charset=utf-8", + ".svg": "image/svg+xml", + ".png": "image/png", + ".ico": "image/x-icon", + ".webmanifest": "application/manifest+json", + ".woff2": "font/woff2", + ".woff": "font/woff", + ".ttf": "font/ttf", + ".txt": "text/plain; charset=utf-8", + ".map": "application/json; charset=utf-8", +} + +_STATIC_ROOT_REL = "static/dashboard" +_IMMUTABLE_PREFIX = "/assets/" + + +def _content_type_for(path: str) -> str: + """Return the Content-Type for a request path by extension.""" + ext = os.path.splitext(path)[1].lower() + return _CONTENT_TYPES.get(ext, "application/octet-stream") + + +def _resolve_static_node(base: Any, rel_path: str) -> Any | None: + """Resolve a request path to a file node under ``base``. + + Rejects traversal attempts, null bytes, and backslash escapes. Returns + ``None`` if the resolved node is not an existing regular file. + + ``base`` must support ``joinpath(name)``; the returned node must + support ``is_file()`` and ``read_bytes()``. Works with both + ``pathlib.Path`` and ``importlib.resources.abc.Traversable``. + """ + clean = rel_path.lstrip("/") + if not clean: + return None + parts = clean.split("/") + for part in parts: + if part in ("", ".", ".."): + return None + if "\x00" in part or "\\" in part: + return None + node = base + for part in parts: + node = node.joinpath(part) + return node if node.is_file() else None + + +_static_root_lock = threading.Lock() +_static_root_resolved = False +_static_root: Any | None = None + + +def _get_static_root() -> Any | None: + """Return the Traversable root of the bundled SPA, or ``None``. + + Cached after the first call. Returns ``None`` if the package doesn't + ship a multi-file dashboard layout (in which case callers should fall + back to the legacy single-file template). + """ + global _static_root_resolved, _static_root + if _static_root_resolved: + return _static_root + with _static_root_lock: + if not _static_root_resolved: + try: + candidate = resources.files("taskito").joinpath(_STATIC_ROOT_REL) + if candidate.joinpath("index.html").is_file(): + _static_root = candidate + except (ModuleNotFoundError, FileNotFoundError, AttributeError): + _static_root = None + _static_root_resolved = True + return _static_root + + def _read_template(path: str) -> str: """Read a file from the bundled templates directory.""" return resources.files("taskito").joinpath(path).read_text(encoding="utf-8") -def _load_spa_html() -> str: - """Load the pre-built dashboard SPA (single-file Vite output).""" +def _load_legacy_html() -> str: + """Load the pre-built single-file dashboard (legacy layout).""" return _read_template("templates/dashboard.html") -_SPA_HTML: str | None = None -_spa_lock = threading.Lock() +_LEGACY_HTML: str | None = None +_legacy_lock = threading.Lock() -def _get_spa_html() -> str: - global _SPA_HTML - if _SPA_HTML is None: - with _spa_lock: - if _SPA_HTML is None: - _SPA_HTML = _load_spa_html() - return _SPA_HTML +def _get_legacy_html() -> str: + global _LEGACY_HTML + if _LEGACY_HTML is None: + with _legacy_lock: + if _LEGACY_HTML is None: + _LEGACY_HTML = _load_legacy_html() + return _LEGACY_HTML def _parse_int_qs(qs: dict, key: str, default: int) -> int: @@ -139,13 +237,6 @@ def _handle_stats_queues(queue: Queue, qs: dict) -> dict: return queue.stats_all_queues() -class _NotFound(Exception): - """Raised by route handlers to signal a 404 response.""" - - def __init__(self, message: str) -> None: - self.message = message - - def _handle_get_job(queue: Queue, _qs: dict, job_id: str) -> dict: job = queue.get_job(job_id) if job is None: @@ -338,7 +429,7 @@ def _handle_get(self) -> None: elif path == "/metrics": self._serve_prometheus_metrics() else: - self._serve_spa() + self._serve_spa(path) def do_POST(self) -> None: try: @@ -389,11 +480,48 @@ def _serve_prometheus_metrics(self) -> None: except ImportError: self._json_response({"error": "prometheus-client not installed"}, status=501) - def _serve_spa(self) -> None: - body = _get_spa_html().encode() + def _serve_spa(self, req_path: str) -> None: + """Serve the SPA: an asset under static/dashboard/, the index.html + fallback for client-side routes, or the legacy single-file HTML. + """ + static_root = _get_static_root() + if static_root is None: + self._serve_legacy_html() + return + + node = _resolve_static_node(static_root, req_path) + if node is not None: + immutable = req_path.startswith(_IMMUTABLE_PREFIX) + self._send_asset(node, _content_type_for(req_path), immutable=immutable) + return + + if req_path.startswith(_IMMUTABLE_PREFIX): + self._json_response({"error": "Not found"}, status=404) + return + + index = static_root.joinpath("index.html") + if index.is_file(): + self._send_asset(index, "text/html; charset=utf-8", immutable=False) + return + + self._serve_legacy_html() + + def _send_asset(self, node: Any, content_type: str, *, immutable: bool) -> None: + body: bytes = node.read_bytes() + cache = "public, max-age=31536000, immutable" if immutable else "no-cache" + self.send_response(200) + self.send_header("Content-Type", content_type) + self.send_header("Content-Length", str(len(body))) + self.send_header("Cache-Control", cache) + self.end_headers() + self.wfile.write(body) + + def _serve_legacy_html(self) -> None: + body = _get_legacy_html().encode() self.send_response(200) self.send_header("Content-Type", "text/html; charset=utf-8") self.send_header("Content-Length", str(len(body))) + self.send_header("Cache-Control", "no-cache") self.end_headers() self.wfile.write(body) From 0c52ec8e205410f77128dbd824c730741ba6f206 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 00:40:07 +0530 Subject: [PATCH 06/42] test(dashboard): unit tests for static asset resolver --- tests/python/test_dashboard_static.py | 111 ++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 tests/python/test_dashboard_static.py diff --git a/tests/python/test_dashboard_static.py b/tests/python/test_dashboard_static.py new file mode 100644 index 0000000..0f7eb84 --- /dev/null +++ b/tests/python/test_dashboard_static.py @@ -0,0 +1,111 @@ +"""Tests for dashboard static asset resolution and Content-Type mapping.""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +from taskito.dashboard import _content_type_for, _resolve_static_node + + +@pytest.fixture +def static_root(tmp_path: Path) -> Path: + """Layout mirroring a built Vite SPA tree.""" + (tmp_path / "index.html").write_text("") + assets = tmp_path / "assets" + assets.mkdir() + (assets / "index-abc.js").write_text("// js") + (assets / "index-abc.css").write_text("/* css */") + (assets / "nested").mkdir() + (assets / "nested" / "deep.png").write_bytes(b"\x89PNG") + return tmp_path + + +# ── _resolve_static_node ──────────────────────────────────────────── + + +def test_resolve_index_html(static_root: Path) -> None: + node = _resolve_static_node(static_root, "/index.html") + assert node is not None + assert node.read_text() == "" + + +def test_resolve_hashed_asset(static_root: Path) -> None: + node = _resolve_static_node(static_root, "/assets/index-abc.js") + assert node is not None + assert node.read_text() == "// js" + + +def test_resolve_nested_asset(static_root: Path) -> None: + node = _resolve_static_node(static_root, "/assets/nested/deep.png") + assert node is not None + assert node.read_bytes() == b"\x89PNG" + + +def test_resolve_missing_file_returns_none(static_root: Path) -> None: + assert _resolve_static_node(static_root, "/assets/missing.js") is None + + +def test_resolve_directory_returns_none(static_root: Path) -> None: + # A directory matches joinpath but is_file() is False + assert _resolve_static_node(static_root, "/assets") is None + + +def test_resolve_empty_path_returns_none(static_root: Path) -> None: + assert _resolve_static_node(static_root, "") is None + assert _resolve_static_node(static_root, "/") is None + + +def test_resolve_rejects_parent_traversal(static_root: Path) -> None: + assert _resolve_static_node(static_root, "/../secret") is None + assert _resolve_static_node(static_root, "/assets/../../secret") is None + + +def test_resolve_rejects_current_directory(static_root: Path) -> None: + assert _resolve_static_node(static_root, "/./index.html") is None + + +def test_resolve_rejects_null_byte(static_root: Path) -> None: + assert _resolve_static_node(static_root, "/index.html\x00.png") is None + + +def test_resolve_rejects_backslash(static_root: Path) -> None: + # Windows-style separators should be rejected to avoid ambiguity + assert _resolve_static_node(static_root, "/assets\\index-abc.js") is None + + +def test_resolve_rejects_double_slash(static_root: Path) -> None: + # Empty segments from double slashes are rejected + assert _resolve_static_node(static_root, "/assets//index-abc.js") is None + + +# ── _content_type_for ────────────────────────────────────────────── + + +@pytest.mark.parametrize( + ("path", "expected"), + [ + ("/index.html", "text/html; charset=utf-8"), + ("/assets/index-abc.js", "application/javascript; charset=utf-8"), + ("/assets/index-abc.mjs", "application/javascript; charset=utf-8"), + ("/assets/index-abc.css", "text/css; charset=utf-8"), + ("/icon.svg", "image/svg+xml"), + ("/icon.png", "image/png"), + ("/favicon.ico", "image/x-icon"), + ("/fonts/inter.woff2", "font/woff2"), + ("/fonts/inter.woff", "font/woff"), + ("/app.webmanifest", "application/manifest+json"), + ("/data.json", "application/json; charset=utf-8"), + ("/unknown.bin", "application/octet-stream"), + ("/no-extension", "application/octet-stream"), + ], +) +def test_content_type_for(path: str, expected: str) -> None: + assert _content_type_for(path) == expected + + +def test_content_type_case_insensitive() -> None: + # Uppercase extensions should still match + assert _content_type_for("/IMAGE.PNG") == "image/png" + assert _content_type_for("/script.JS") == "application/javascript; charset=utf-8" From 0083e1f53dcdcbcc4c9abedfbe036777db39383c Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 00:49:13 +0530 Subject: [PATCH 07/42] chore(dashboard-next): add radix, cmdk, react-table, error boundary deps --- dashboard-next/package.json | 10 + dashboard-next/pnpm-lock.yaml | 979 ++++++++++++++++++++++++++++++++++ 2 files changed, 989 insertions(+) diff --git a/dashboard-next/package.json b/dashboard-next/package.json index e8eaf28..29fc195 100644 --- a/dashboard-next/package.json +++ b/dashboard-next/package.json @@ -15,14 +15,24 @@ "ci": "biome ci src/ && tsc --noEmit && vite build" }, "dependencies": { + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-tooltip": "^1.2.8", "@tanstack/react-query": "^5.62.7", "@tanstack/react-query-devtools": "^5.62.7", "@tanstack/react-router": "^1.95.1", + "@tanstack/react-table": "^8.21.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", "lucide-react": "^0.577.0", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-error-boundary": "^6.1.1", "sonner": "^1.7.1", "tailwind-merge": "^2.6.0" }, diff --git a/dashboard-next/pnpm-lock.yaml b/dashboard-next/pnpm-lock.yaml index cfa5f48..6557e9b 100644 --- a/dashboard-next/pnpm-lock.yaml +++ b/dashboard-next/pnpm-lock.yaml @@ -8,6 +8,27 @@ importers: .: dependencies: + '@radix-ui/react-dialog': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.16 + version: 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-scroll-area': + specifier: ^1.2.10 + version: 1.2.10(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-select': + specifier: ^2.2.6 + version: 2.2.6(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': + specifier: ^1.2.4 + version: 1.2.4(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-tabs': + specifier: ^1.1.13 + version: 1.1.13(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-tooltip': + specifier: ^1.2.8 + version: 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tanstack/react-query': specifier: ^5.62.7 version: 5.100.1(react@18.3.1) @@ -17,12 +38,18 @@ importers: '@tanstack/react-router': specifier: ^1.95.1 version: 1.168.23(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tanstack/react-table': + specifier: ^8.21.3 + version: 8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 clsx: specifier: ^2.1.1 version: 2.1.1 + cmdk: + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) lucide-react: specifier: ^0.577.0 version: 0.577.0(react@18.3.1) @@ -32,6 +59,9 @@ importers: react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) + react-error-boundary: + specifier: ^6.1.1 + version: 6.1.1(react@18.3.1) sonner: specifier: ^1.7.1 version: 1.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -536,6 +566,21 @@ packages: cpu: [x64] os: [win32] + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + + '@floating-ui/react-dom@2.1.8': + resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -552,6 +597,384 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.15': + resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.11': + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dropdown-menu@2.1.16': + resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.3': + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-menu@2.1.16': + resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.2.8': + resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.4': + resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.11': + resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-scroll-area@1.2.10': + resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-select@2.2.6': + resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.2.4': + resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-tabs@1.1.13': + resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tooltip@1.2.8': + resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.3': + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -821,6 +1244,13 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@tanstack/react-table@8.21.3': + resolution: {integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==} + engines: {node: '>=12'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + '@tanstack/router-core@1.168.15': resolution: {integrity: sha512-Wr0424NDtD8fT/uALobMZ9DdcfsTyXtW5IPR++7zvW8/7RaIOeaqXpVDId8ywaGtqPWLWOfaUg2zUtYtukoXYA==} engines: {node: '>=20.19'} @@ -859,6 +1289,10 @@ packages: '@tanstack/store@0.9.3': resolution: {integrity: sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==} + '@tanstack/table-core@8.21.3': + resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} + engines: {node: '>=12'} + '@tanstack/virtual-file-routes@1.161.7': resolution: {integrity: sha512-olW33+Cn+bsCsZKPwEGhlkqS6w3M2slFv11JIobdnCFKMLG97oAI2kWKdx5/zsywTL8flpnoIgaZZPlQTFYhdQ==} engines: {node: '>=20.19'} @@ -912,6 +1346,10 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + babel-dead-code-elimination@1.0.12: resolution: {integrity: sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==} @@ -947,6 +1385,12 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + cmdk@1.1.1: + resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + react-dom: ^18 || ^19 || ^19.0.0-rc + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -969,6 +1413,9 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + diff@8.0.4: resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} engines: {node: '>=0.3.1'} @@ -1016,6 +1463,10 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + get-tsconfig@4.14.0: resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} @@ -1195,10 +1646,45 @@ packages: peerDependencies: react: ^18.3.1 + react-error-boundary@6.1.1: + resolution: {integrity: sha512-BrYwPOdXi5mqkk5lw+Uvt0ThHx32rCt3BkukS4X23A2AIWDPSGX6iaWTc0y9TU/mHDA/6qOSGel+B2ERkOvD1w==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-refresh@0.17.0: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + react@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} @@ -1260,6 +1746,9 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.21.0: resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} engines: {node: '>=18.0.0'} @@ -1283,6 +1772,26 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + use-sync-external-store@1.6.0: resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} peerDependencies: @@ -1652,6 +2161,23 @@ snapshots: '@esbuild/win32-x64@0.27.7': optional: true + '@floating-ui/core@1.7.5': + dependencies: + '@floating-ui/utils': 0.2.11 + + '@floating-ui/dom@1.7.6': + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 + + '@floating-ui/react-dom@2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/dom': 1.7.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@floating-ui/utils@0.2.11': {} + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -1671,6 +2197,383 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@radix-ui/number@1.1.1': {} + + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.28)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-context@1.1.2(@types/react@18.3.28)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-dialog@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-direction@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-focus-guards@1.1.3(@types/react@18.3.28)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-id@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-menu@2.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-popper@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/react-dom': 2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-rect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/rect': 1.1.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-presence@1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-primitive@2.1.4(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-slot': 1.2.4(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-select@2.2.6(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-slot@1.2.3(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-slot@1.2.4(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-tabs@1.1.13(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-previous@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-rect@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-size@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/rect@1.1.1': {} + '@rolldown/pluginutils@1.0.0-beta.27': {} '@rollup/rollup-android-arm-eabi@4.60.2': @@ -1849,6 +2752,12 @@ snapshots: react-dom: 18.3.1(react@18.3.1) use-sync-external-store: 1.6.0(react@18.3.1) + '@tanstack/react-table@8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/table-core': 8.21.3 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + '@tanstack/router-core@1.168.15': dependencies: '@tanstack/history': 1.161.6 @@ -1906,6 +2815,8 @@ snapshots: '@tanstack/store@0.9.3': {} + '@tanstack/table-core@8.21.3': {} + '@tanstack/virtual-file-routes@1.161.7': {} '@types/babel__core@7.20.5': @@ -1967,6 +2878,10 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.2 + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + babel-dead-code-elimination@1.0.12: dependencies: '@babel/core': 7.29.0 @@ -2012,6 +2927,18 @@ snapshots: clsx@2.1.1: {} + cmdk@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + convert-source-map@2.0.0: {} cookie-es@3.1.1: {} @@ -2024,6 +2951,8 @@ snapshots: detect-libc@2.1.2: {} + detect-node-es@1.1.0: {} + diff@8.0.4: {} electron-to-chromium@1.5.344: {} @@ -2106,6 +3035,8 @@ snapshots: gensync@1.0.0-beta.2: {} + get-nonce@1.0.1: {} + get-tsconfig@4.14.0: dependencies: resolve-pkg-maps: 1.0.0 @@ -2233,8 +3164,39 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 + react-error-boundary@6.1.1(react@18.3.1): + dependencies: + react: 18.3.1 + react-refresh@0.17.0: {} + react-remove-scroll-bar@2.3.8(@types/react@18.3.28)(react@18.3.1): + dependencies: + react: 18.3.1 + react-style-singleton: 2.2.3(@types/react@18.3.28)(react@18.3.1) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.28 + + react-remove-scroll@2.7.2(@types/react@18.3.28)(react@18.3.1): + dependencies: + react: 18.3.1 + react-remove-scroll-bar: 2.3.8(@types/react@18.3.28)(react@18.3.1) + react-style-singleton: 2.2.3(@types/react@18.3.28)(react@18.3.1) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@18.3.28)(react@18.3.1) + use-sidecar: 1.1.3(@types/react@18.3.28)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + + react-style-singleton@2.2.3(@types/react@18.3.28)(react@18.3.1): + dependencies: + get-nonce: 1.0.1 + react: 18.3.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.28 + react@18.3.1: dependencies: loose-envify: 1.4.0 @@ -2310,6 +3272,8 @@ snapshots: dependencies: is-number: 7.0.0 + tslib@2.8.1: {} + tsx@4.21.0: dependencies: esbuild: 0.27.7 @@ -2334,6 +3298,21 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + use-callback-ref@1.3.3(@types/react@18.3.28)(react@18.3.1): + dependencies: + react: 18.3.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.28 + + use-sidecar@1.1.3(@types/react@18.3.28)(react@18.3.1): + dependencies: + detect-node-es: 1.1.0 + react: 18.3.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.28 + use-sync-external-store@1.6.0(react@18.3.1): dependencies: react: 18.3.1 From 2251617af8ea25b7832bc5d219156b68d3c325df Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 00:49:20 +0530 Subject: [PATCH 08/42] feat(dashboard-next): add shadcn ui primitives + DataTable --- dashboard-next/src/components/ui/command.tsx | 143 ++++++++++++++ .../src/components/ui/confirm-dialog.tsx | 62 ++++++ .../src/components/ui/data-table.tsx | 121 ++++++++++++ dashboard-next/src/components/ui/dialog.tsx | 103 ++++++++++ .../src/components/ui/dropdown-menu.tsx | 180 ++++++++++++++++++ .../src/components/ui/error-state.tsx | 49 +++++ dashboard-next/src/components/ui/index.ts | 91 ++++++++- .../src/components/ui/pagination.tsx | 49 +++++ .../src/components/ui/scroll-area.tsx | 43 +++++ dashboard-next/src/components/ui/select.tsx | 154 +++++++++++++++ dashboard-next/src/components/ui/sheet.tsx | 106 +++++++++++ .../src/components/ui/stat-card.tsx | 37 ++++ dashboard-next/src/components/ui/table.tsx | 100 ++++++++++ dashboard-next/src/components/ui/tabs.tsx | 55 ++++++ dashboard-next/src/components/ui/toaster.tsx | 27 +++ dashboard-next/src/components/ui/tooltip.tsx | 28 +++ 16 files changed, 1347 insertions(+), 1 deletion(-) create mode 100644 dashboard-next/src/components/ui/command.tsx create mode 100644 dashboard-next/src/components/ui/confirm-dialog.tsx create mode 100644 dashboard-next/src/components/ui/data-table.tsx create mode 100644 dashboard-next/src/components/ui/dialog.tsx create mode 100644 dashboard-next/src/components/ui/dropdown-menu.tsx create mode 100644 dashboard-next/src/components/ui/error-state.tsx create mode 100644 dashboard-next/src/components/ui/pagination.tsx create mode 100644 dashboard-next/src/components/ui/scroll-area.tsx create mode 100644 dashboard-next/src/components/ui/select.tsx create mode 100644 dashboard-next/src/components/ui/sheet.tsx create mode 100644 dashboard-next/src/components/ui/stat-card.tsx create mode 100644 dashboard-next/src/components/ui/table.tsx create mode 100644 dashboard-next/src/components/ui/tabs.tsx create mode 100644 dashboard-next/src/components/ui/toaster.tsx create mode 100644 dashboard-next/src/components/ui/tooltip.tsx diff --git a/dashboard-next/src/components/ui/command.tsx b/dashboard-next/src/components/ui/command.tsx new file mode 100644 index 0000000..04e116a --- /dev/null +++ b/dashboard-next/src/components/ui/command.tsx @@ -0,0 +1,143 @@ +import { Command as CommandPrimitive } from "cmdk"; +import { Search } from "lucide-react"; +import { type ComponentPropsWithoutRef, forwardRef } from "react"; +import { Dialog, DialogContent } from "@/components/ui/dialog"; +import { cn } from "@/lib/cn"; + +const Command = forwardRef>( + ({ className, ...props }, ref) => ( + + ), +); +Command.displayName = CommandPrimitive.displayName; + +interface CommandDialogProps extends ComponentPropsWithoutRef { + label?: string; +} + +function CommandDialog({ children, label = "Command menu", ...props }: CommandDialogProps) { + return ( + + + + {children} + + + + ); +} + +const CommandInput = forwardRef< + HTMLInputElement, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ + +
+)); +CommandInput.displayName = CommandPrimitive.Input.displayName; + +const CommandList = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +CommandList.displayName = CommandPrimitive.List.displayName; + +const CommandEmpty = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef +>((props, ref) => ( + +)); +CommandEmpty.displayName = CommandPrimitive.Empty.displayName; + +const CommandGroup = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +CommandGroup.displayName = CommandPrimitive.Group.displayName; + +const CommandSeparator = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +CommandSeparator.displayName = CommandPrimitive.Separator.displayName; + +const CommandItem = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +CommandItem.displayName = CommandPrimitive.Item.displayName; + +function CommandShortcut({ className, ...props }: React.HTMLAttributes) { + return ( + + ); +} + +export { + Command, + CommandDialog, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, + CommandShortcut, +}; diff --git a/dashboard-next/src/components/ui/confirm-dialog.tsx b/dashboard-next/src/components/ui/confirm-dialog.tsx new file mode 100644 index 0000000..1b8122e --- /dev/null +++ b/dashboard-next/src/components/ui/confirm-dialog.tsx @@ -0,0 +1,62 @@ +import type { ReactNode } from "react"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; + +interface ConfirmDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + title: ReactNode; + description?: ReactNode; + confirmLabel?: string; + cancelLabel?: string; + variant?: "default" | "danger"; + onConfirm: () => void | Promise; + pending?: boolean; +} + +export function ConfirmDialog({ + open, + onOpenChange, + title, + description, + confirmLabel = "Confirm", + cancelLabel = "Cancel", + variant = "default", + onConfirm, + pending = false, +}: ConfirmDialogProps) { + async function handleConfirm() { + await onConfirm(); + onOpenChange(false); + } + + return ( + + + + {title} + {description ? {description} : null} + + + + + + + + ); +} diff --git a/dashboard-next/src/components/ui/data-table.tsx b/dashboard-next/src/components/ui/data-table.tsx new file mode 100644 index 0000000..85d8d0f --- /dev/null +++ b/dashboard-next/src/components/ui/data-table.tsx @@ -0,0 +1,121 @@ +import { + type ColumnDef, + flexRender, + getCoreRowModel, + getSortedRowModel, + type Row, + type SortingState, + useReactTable, +} from "@tanstack/react-table"; +import { ArrowDown, ArrowUp, ChevronsUpDown } from "lucide-react"; +import { type ReactNode, useState } from "react"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { cn } from "@/lib/cn"; + +interface DataTableProps { + columns: ColumnDef[]; + data: TData[]; + empty?: ReactNode; + onRowClick?: (row: TData) => void; + rowKey?: (row: TData, index: number) => string; + className?: string; + initialSorting?: SortingState; +} + +export function DataTable({ + columns, + data, + empty, + onRowClick, + rowKey, + className, + initialSorting = [], +}: DataTableProps) { + const [sorting, setSorting] = useState(initialSorting); + + const table = useReactTable({ + data, + columns, + state: { sorting }, + onSortingChange: setSorting, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + }); + + return ( +
+ + + {table.getHeaderGroups().map((group) => ( + + {group.headers.map((header) => { + const canSort = header.column.getCanSort(); + const sorted = header.column.getIsSorted(); + return ( + + {header.isPlaceholder ? null : canSort ? ( + + ) : ( + flexRender(header.column.columnDef.header, header.getContext()) + )} + + ); + })} + + ))} + + + {table.getRowModel().rows.length === 0 ? ( + + + {empty ?? "No data"} + + + ) : ( + table.getRowModel().rows.map((row: Row, index) => ( + onRowClick(row.original) : undefined} + > + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + )} + +
+
+ ); +} diff --git a/dashboard-next/src/components/ui/dialog.tsx b/dashboard-next/src/components/ui/dialog.tsx new file mode 100644 index 0000000..be09639 --- /dev/null +++ b/dashboard-next/src/components/ui/dialog.tsx @@ -0,0 +1,103 @@ +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { X } from "lucide-react"; +import { type ComponentPropsWithoutRef, forwardRef, type HTMLAttributes } from "react"; +import { cn } from "@/lib/cn"; + +const Dialog = DialogPrimitive.Root; +const DialogTrigger = DialogPrimitive.Trigger; +const DialogPortal = DialogPrimitive.Portal; +const DialogClose = DialogPrimitive.Close; + +const DialogOverlay = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +const DialogContent = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + + + +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +function DialogHeader({ className, ...props }: HTMLAttributes) { + return ( +
+ ); +} + +function DialogFooter({ className, ...props }: HTMLAttributes) { + return ( +
+ ); +} + +const DialogTitle = forwardRef< + HTMLHeadingElement, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +const DialogDescription = forwardRef< + HTMLParagraphElement, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +}; diff --git a/dashboard-next/src/components/ui/dropdown-menu.tsx b/dashboard-next/src/components/ui/dropdown-menu.tsx new file mode 100644 index 0000000..332b72e --- /dev/null +++ b/dashboard-next/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,180 @@ +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; +import { Check, ChevronRight, Circle } from "lucide-react"; +import { type ComponentPropsWithoutRef, forwardRef, type HTMLAttributes } from "react"; +import { cn } from "@/lib/cn"; + +const DropdownMenu = DropdownMenuPrimitive.Root; +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; +const DropdownMenuGroup = DropdownMenuPrimitive.Group; +const DropdownMenuPortal = DropdownMenuPrimitive.Portal; +const DropdownMenuSub = DropdownMenuPrimitive.Sub; +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; + +const DropdownMenuSubTrigger = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef & { inset?: boolean } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)); +DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName; + +const DropdownMenuSubContent = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName; + +const DropdownMenuContent = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef +>(({ className, sideOffset = 6, ...props }, ref) => ( + + + +)); +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; + +const DropdownMenuItem = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef & { inset?: boolean } +>(({ className, inset, ...props }, ref) => ( + +)); +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; + +const DropdownMenuCheckboxItem = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)); +DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName; + +const DropdownMenuRadioItem = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)); +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; + +const DropdownMenuLabel = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef & { inset?: boolean } +>(({ className, inset, ...props }, ref) => ( + +)); +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; + +const DropdownMenuSeparator = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; + +function DropdownMenuShortcut({ className, ...props }: HTMLAttributes) { + return ( + + ); +} + +export { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuPortal, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuTrigger, +}; diff --git a/dashboard-next/src/components/ui/error-state.tsx b/dashboard-next/src/components/ui/error-state.tsx new file mode 100644 index 0000000..3822ece --- /dev/null +++ b/dashboard-next/src/components/ui/error-state.tsx @@ -0,0 +1,49 @@ +import { AlertTriangle, type LucideIcon } from "lucide-react"; +import type { ReactNode } from "react"; +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/cn"; + +interface ErrorStateProps { + icon?: LucideIcon; + title?: string; + description?: string; + onRetry?: () => void; + retryLabel?: string; + action?: ReactNode; + className?: string; +} + +export function ErrorState({ + icon: Icon = AlertTriangle, + title = "Something went wrong", + description, + onRetry, + retryLabel = "Retry", + action, + className, +}: ErrorStateProps) { + return ( +
+
+ +
+
+
{title}
+ {description ? ( +
{description}
+ ) : null} +
+ {action ?? + (onRetry ? ( + + ) : null)} +
+ ); +} diff --git a/dashboard-next/src/components/ui/index.ts b/dashboard-next/src/components/ui/index.ts index e7dc9e9..dc98ac2 100644 --- a/dashboard-next/src/components/ui/index.ts +++ b/dashboard-next/src/components/ui/index.ts @@ -1,8 +1,97 @@ export { Badge, badgeVariants } from "./badge"; export { Button, buttonVariants } from "./button"; -export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "./card"; +export { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "./card"; +export { + Command, + CommandDialog, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, + CommandShortcut, +} from "./command"; +export { ConfirmDialog } from "./confirm-dialog"; +export { DataTable } from "./data-table"; +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +} from "./dialog"; +export { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuPortal, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuTrigger, +} from "./dropdown-menu"; export { EmptyState } from "./empty-state"; +export { ErrorState } from "./error-state"; export { Input } from "./input"; export { Kbd } from "./kbd"; +export { Pagination } from "./pagination"; +export { ScrollArea, ScrollBar } from "./scroll-area"; +export { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectScrollDownButton, + SelectScrollUpButton, + SelectSeparator, + SelectTrigger, + SelectValue, +} from "./select"; export { Separator } from "./separator"; +export { + Sheet, + SheetClose, + SheetContent, + SheetDescription, + SheetHeader, + SheetOverlay, + SheetPortal, + SheetTitle, + SheetTrigger, +} from "./sheet"; export { Skeleton } from "./skeleton"; +export { StatCard } from "./stat-card"; +export { + Table, + TableBody, + TableCaption, + TableCell, + TableFooter, + TableHead, + TableHeader, + TableRow, +} from "./table"; +export { Tabs, TabsContent, TabsList, TabsTrigger } from "./tabs"; +export { Toaster, toast } from "./toaster"; +export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip"; diff --git a/dashboard-next/src/components/ui/pagination.tsx b/dashboard-next/src/components/ui/pagination.tsx new file mode 100644 index 0000000..e6238fe --- /dev/null +++ b/dashboard-next/src/components/ui/pagination.tsx @@ -0,0 +1,49 @@ +import { ChevronLeft, ChevronRight } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/cn"; + +interface PaginationProps { + page: number; + pageCount?: number; + hasMore?: boolean; + onChange: (page: number) => void; + className?: string; +} + +export function Pagination({ page, pageCount, hasMore, onChange, className }: PaginationProps) { + const canNext = pageCount != null ? page < pageCount - 1 : Boolean(hasMore); + const canPrev = page > 0; + return ( +
+ + Page {page + 1} + {pageCount != null ? ( + <> + {" "} + of {pageCount} + + ) : null} + +
+ + +
+
+ ); +} diff --git a/dashboard-next/src/components/ui/scroll-area.tsx b/dashboard-next/src/components/ui/scroll-area.tsx new file mode 100644 index 0000000..caa266a --- /dev/null +++ b/dashboard-next/src/components/ui/scroll-area.tsx @@ -0,0 +1,43 @@ +import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"; +import { type ComponentPropsWithoutRef, forwardRef } from "react"; +import { cn } from "@/lib/cn"; + +const ScrollArea = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + {children} + + + + +)); +ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName; + +const ScrollBar = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef +>(({ className, orientation = "vertical", ...props }, ref) => ( + + + +)); +ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName; + +export { ScrollArea, ScrollBar }; diff --git a/dashboard-next/src/components/ui/select.tsx b/dashboard-next/src/components/ui/select.tsx new file mode 100644 index 0000000..bfdda0b --- /dev/null +++ b/dashboard-next/src/components/ui/select.tsx @@ -0,0 +1,154 @@ +import * as SelectPrimitive from "@radix-ui/react-select"; +import { Check, ChevronDown, ChevronUp } from "lucide-react"; +import { type ComponentPropsWithoutRef, forwardRef } from "react"; +import { cn } from "@/lib/cn"; + +const Select = SelectPrimitive.Root; +const SelectGroup = SelectPrimitive.Group; +const SelectValue = SelectPrimitive.Value; + +const SelectTrigger = forwardRef< + HTMLButtonElement, + ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + span]:line-clamp-1", + className, + )} + {...props} + > + {children} + + + + +)); +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; + +const SelectScrollUpButton = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; + +const SelectScrollDownButton = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName; + +const SelectContent = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef +>(({ className, children, position = "popper", ...props }, ref) => ( + + + + + {children} + + + + +)); +SelectContent.displayName = SelectPrimitive.Content.displayName; + +const SelectLabel = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SelectLabel.displayName = SelectPrimitive.Label.displayName; + +const SelectItem = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)); +SelectItem.displayName = SelectPrimitive.Item.displayName; + +const SelectSeparator = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SelectSeparator.displayName = SelectPrimitive.Separator.displayName; + +export { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectScrollDownButton, + SelectScrollUpButton, + SelectSeparator, + SelectTrigger, + SelectValue, +}; diff --git a/dashboard-next/src/components/ui/sheet.tsx b/dashboard-next/src/components/ui/sheet.tsx new file mode 100644 index 0000000..1178628 --- /dev/null +++ b/dashboard-next/src/components/ui/sheet.tsx @@ -0,0 +1,106 @@ +import * as SheetPrimitive from "@radix-ui/react-dialog"; +import { cva, type VariantProps } from "class-variance-authority"; +import { X } from "lucide-react"; +import { type ComponentPropsWithoutRef, forwardRef, type HTMLAttributes } from "react"; +import { cn } from "@/lib/cn"; + +const Sheet = SheetPrimitive.Root; +const SheetTrigger = SheetPrimitive.Trigger; +const SheetClose = SheetPrimitive.Close; +const SheetPortal = SheetPrimitive.Portal; + +const SheetOverlay = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; + +const sheetVariants = cva( + "fixed z-50 gap-4 bg-[var(--surface)] p-6 shadow-xl transition ease-in-out duration-300 data-[state=open]:animate-slide-up", + { + variants: { + side: { + top: "inset-x-0 top-0 border-b border-[var(--border)]", + bottom: "inset-x-0 bottom-0 border-t border-[var(--border)]", + left: "inset-y-0 left-0 h-full w-3/4 max-w-sm border-r border-[var(--border)]", + right: "inset-y-0 right-0 h-full w-3/4 max-w-sm border-l border-[var(--border)]", + }, + }, + defaultVariants: { side: "right" }, + }, +); + +interface SheetContentProps + extends ComponentPropsWithoutRef, + VariantProps {} + +const SheetContent = forwardRef( + ({ side, className, children, ...props }, ref) => ( + + + + {children} + + + + + + ), +); +SheetContent.displayName = SheetPrimitive.Content.displayName; + +function SheetHeader({ className, ...props }: HTMLAttributes) { + return
; +} + +const SheetTitle = forwardRef< + HTMLHeadingElement, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SheetTitle.displayName = SheetPrimitive.Title.displayName; + +const SheetDescription = forwardRef< + HTMLParagraphElement, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SheetDescription.displayName = SheetPrimitive.Description.displayName; + +export { + Sheet, + SheetClose, + SheetContent, + SheetDescription, + SheetHeader, + SheetOverlay, + SheetPortal, + SheetTitle, + SheetTrigger, +}; diff --git a/dashboard-next/src/components/ui/stat-card.tsx b/dashboard-next/src/components/ui/stat-card.tsx new file mode 100644 index 0000000..33f8bb8 --- /dev/null +++ b/dashboard-next/src/components/ui/stat-card.tsx @@ -0,0 +1,37 @@ +import { forwardRef, type HTMLAttributes, type ReactNode } from "react"; +import { Card } from "@/components/ui/card"; +import { cn } from "@/lib/cn"; + +interface StatCardProps extends HTMLAttributes { + label: string; + value: ReactNode; + hint?: ReactNode; + icon?: ReactNode; + trend?: "up" | "down" | "flat"; + tone?: "neutral" | "accent" | "success" | "warning" | "danger" | "info"; +} + +const TONE_RING: Record, string> = { + neutral: "text-[var(--fg-muted)]", + accent: "text-accent", + info: "text-info", + success: "text-success", + warning: "text-warning", + danger: "text-danger", +}; + +export const StatCard = forwardRef( + ({ label, value, hint, icon, tone = "neutral", className, ...props }, ref) => ( + +
+
+ {label} +
+ {icon ?
{icon}
: null} +
+
{value}
+ {hint ?
{hint}
: null} +
+ ), +); +StatCard.displayName = "StatCard"; diff --git a/dashboard-next/src/components/ui/table.tsx b/dashboard-next/src/components/ui/table.tsx new file mode 100644 index 0000000..5483f2e --- /dev/null +++ b/dashboard-next/src/components/ui/table.tsx @@ -0,0 +1,100 @@ +import { + forwardRef, + type HTMLAttributes, + type TdHTMLAttributes, + type ThHTMLAttributes, +} from "react"; +import { cn } from "@/lib/cn"; + +const Table = forwardRef>( + ({ className, ...props }, ref) => ( +
+ + + ), +); +Table.displayName = "Table"; + +const TableHeader = forwardRef>( + ({ className, ...props }, ref) => ( + + ), +); +TableHeader.displayName = "TableHeader"; + +const TableBody = forwardRef>( + ({ className, ...props }, ref) => ( + + ), +); +TableBody.displayName = "TableBody"; + +const TableFooter = forwardRef>( + ({ className, ...props }, ref) => ( + tr]:last:border-b-0", + className, + )} + {...props} + /> + ), +); +TableFooter.displayName = "TableFooter"; + +const TableRow = forwardRef>( + ({ className, ...props }, ref) => ( + + ), +); +TableRow.displayName = "TableRow"; + +const TableHead = forwardRef>( + ({ className, ...props }, ref) => ( +
+ ), +); +TableHead.displayName = "TableHead"; + +const TableCell = forwardRef>( + ({ className, ...props }, ref) => ( + + ), +); +TableCell.displayName = "TableCell"; + +const TableCaption = forwardRef>( + ({ className, ...props }, ref) => ( +
+ ), +); +TableCaption.displayName = "TableCaption"; + +export { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow }; diff --git a/dashboard-next/src/components/ui/tabs.tsx b/dashboard-next/src/components/ui/tabs.tsx new file mode 100644 index 0000000..c5a26be --- /dev/null +++ b/dashboard-next/src/components/ui/tabs.tsx @@ -0,0 +1,55 @@ +import * as TabsPrimitive from "@radix-ui/react-tabs"; +import { type ComponentPropsWithoutRef, forwardRef } from "react"; +import { cn } from "@/lib/cn"; + +const Tabs = TabsPrimitive.Root; + +const TabsList = forwardRef>( + ({ className, ...props }, ref) => ( + + ), +); +TabsList.displayName = TabsPrimitive.List.displayName; + +const TabsTrigger = forwardRef< + HTMLButtonElement, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName; + +const TabsContent = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsContent.displayName = TabsPrimitive.Content.displayName; + +export { Tabs, TabsContent, TabsList, TabsTrigger }; diff --git a/dashboard-next/src/components/ui/toaster.tsx b/dashboard-next/src/components/ui/toaster.tsx new file mode 100644 index 0000000..0006c87 --- /dev/null +++ b/dashboard-next/src/components/ui/toaster.tsx @@ -0,0 +1,27 @@ +import { Toaster as Sonner, type ToasterProps } from "sonner"; +import { useTheme } from "@/providers/theme-provider"; + +export function Toaster(props: ToasterProps) { + const { resolved } = useTheme(); + return ( + + ); +} + +export { toast } from "sonner"; diff --git a/dashboard-next/src/components/ui/tooltip.tsx b/dashboard-next/src/components/ui/tooltip.tsx new file mode 100644 index 0000000..635c85e --- /dev/null +++ b/dashboard-next/src/components/ui/tooltip.tsx @@ -0,0 +1,28 @@ +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; +import { type ComponentPropsWithoutRef, forwardRef } from "react"; +import { cn } from "@/lib/cn"; + +const TooltipProvider = TooltipPrimitive.Provider; +const Tooltip = TooltipPrimitive.Root; +const TooltipTrigger = TooltipPrimitive.Trigger; + +const TooltipContent = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef +>(({ className, sideOffset = 6, ...props }, ref) => ( + + + +)); +TooltipContent.displayName = TooltipPrimitive.Content.displayName; + +export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }; From c07f89af204e12e4d9de733e3dc2a2c1802c2fda Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 00:49:24 +0530 Subject: [PATCH 09/42] feat(dashboard-next): shell polish (mobile nav, breadcrumbs, refresh indicator, error boundary) --- .../src/components/layout/app-shell.tsx | 24 +- .../src/components/layout/breadcrumbs.tsx | 41 ++++ .../src/components/layout/command-palette.tsx | 229 +++++++++--------- .../src/components/layout/header.tsx | 17 +- dashboard-next/src/components/layout/index.ts | 5 + .../src/components/layout/last-refreshed.tsx | 43 ++++ .../src/components/layout/mobile-menu.tsx | 109 +++++++++ .../src/components/layout/page-header.tsx | 17 +- .../layout/route-error-boundary.tsx | 36 +++ dashboard-next/src/hooks/index.ts | 2 + .../src/hooks/use-last-refreshed.ts | 17 ++ dashboard-next/src/hooks/use-media-query.ts | 17 ++ dashboard-next/src/providers/index.tsx | 6 +- dashboard-next/src/routes/jobs/$id.tsx | 15 +- 14 files changed, 431 insertions(+), 147 deletions(-) create mode 100644 dashboard-next/src/components/layout/breadcrumbs.tsx create mode 100644 dashboard-next/src/components/layout/last-refreshed.tsx create mode 100644 dashboard-next/src/components/layout/mobile-menu.tsx create mode 100644 dashboard-next/src/components/layout/route-error-boundary.tsx create mode 100644 dashboard-next/src/hooks/index.ts create mode 100644 dashboard-next/src/hooks/use-last-refreshed.ts create mode 100644 dashboard-next/src/hooks/use-media-query.ts diff --git a/dashboard-next/src/components/layout/app-shell.tsx b/dashboard-next/src/components/layout/app-shell.tsx index 2430183..3a48e84 100644 --- a/dashboard-next/src/components/layout/app-shell.tsx +++ b/dashboard-next/src/components/layout/app-shell.tsx @@ -1,19 +1,25 @@ import type { ReactNode } from "react"; +import { TooltipProvider } from "@/components/ui/tooltip"; import { CommandPalette } from "./command-palette"; import { Header } from "./header"; +import { RouteErrorBoundary } from "./route-error-boundary"; import { Sidebar } from "./sidebar"; export function AppShell({ children }: { children: ReactNode }) { return ( -
- -
-
-
-
{children}
-
+ +
+ +
+
+
+
+ {children} +
+
+
+
- -
+ ); } diff --git a/dashboard-next/src/components/layout/breadcrumbs.tsx b/dashboard-next/src/components/layout/breadcrumbs.tsx new file mode 100644 index 0000000..c8c1bd0 --- /dev/null +++ b/dashboard-next/src/components/layout/breadcrumbs.tsx @@ -0,0 +1,41 @@ +import { Link } from "@tanstack/react-router"; +import { ChevronRight } from "lucide-react"; +import type { ReactNode } from "react"; +import { cn } from "@/lib/cn"; + +export interface Crumb { + label: ReactNode; + to?: string; +} + +interface BreadcrumbsProps { + items: Crumb[]; + className?: string; +} + +export function Breadcrumbs({ items, className }: BreadcrumbsProps) { + if (items.length === 0) return null; + return ( + + ); +} diff --git a/dashboard-next/src/components/layout/command-palette.tsx b/dashboard-next/src/components/layout/command-palette.tsx index 4e2b859..9d960e4 100644 --- a/dashboard-next/src/components/layout/command-palette.tsx +++ b/dashboard-next/src/components/layout/command-palette.tsx @@ -1,139 +1,130 @@ import { useNavigate } from "@tanstack/react-router"; -import { Search } from "lucide-react"; -import { useCallback, useEffect, useRef, useState } from "react"; -import { Kbd } from "@/components/ui/kbd"; -import { cn } from "@/lib/cn"; +import { + Activity, + BarChart3, + Box, + CircuitBoard, + LayoutDashboard, + ListTree, + type LucideIcon, + Moon, + ScrollText, + Server, + Settings2, + Skull, + Sun, +} from "lucide-react"; +import { useCallback } from "react"; +import { + CommandDialog, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, + CommandShortcut, +} from "@/components/ui/command"; import { useCommandPalette } from "@/providers/command-palette-provider"; +import { type RefreshOption, useRefreshInterval } from "@/providers/refresh-interval-provider"; +import { useTheme } from "@/providers/theme-provider"; -interface CommandItem { - id: string; +interface NavCmd { label: string; - hint?: string; to: string; - keywords?: string; + icon: LucideIcon; + hint?: string; } -const COMMANDS: CommandItem[] = [ - { id: "nav.overview", label: "Go to Overview", to: "/", hint: "Home" }, - { id: "nav.jobs", label: "Go to Jobs", to: "/jobs", keywords: "tasks list" }, - { id: "nav.metrics", label: "Go to Metrics", to: "/metrics", keywords: "graphs charts" }, - { id: "nav.logs", label: "Go to Logs", to: "/logs" }, - { id: "nav.queues", label: "Go to Queues", to: "/queues", keywords: "pause resume" }, - { id: "nav.workers", label: "Go to Workers", to: "/workers" }, - { id: "nav.resources", label: "Go to Resources", to: "/resources" }, - { - id: "nav.dead-letters", - label: "Go to Dead letters", - to: "/dead-letters", - keywords: "dlq retry", - }, - { - id: "nav.circuit-breakers", - label: "Go to Circuit breakers", - to: "/circuit-breakers", - keywords: "fault tolerance", - }, - { id: "nav.system", label: "Go to System", to: "/system", keywords: "proxy interception" }, +const NAV_COMMANDS: NavCmd[] = [ + { label: "Overview", to: "/", icon: LayoutDashboard, hint: "Home" }, + { label: "Jobs", to: "/jobs", icon: ListTree }, + { label: "Metrics", to: "/metrics", icon: BarChart3 }, + { label: "Logs", to: "/logs", icon: ScrollText }, + { label: "Queues", to: "/queues", icon: Box }, + { label: "Workers", to: "/workers", icon: Server }, + { label: "Resources", to: "/resources", icon: Activity }, + { label: "Dead letters", to: "/dead-letters", icon: Skull }, + { label: "Circuit breakers", to: "/circuit-breakers", icon: CircuitBoard }, + { label: "System", to: "/system", icon: Settings2 }, ]; +const REFRESH_COMMANDS: RefreshOption[] = ["2s", "5s", "10s", "off"]; + export function CommandPalette() { const { open, setOpen } = useCommandPalette(); const navigate = useNavigate(); - const inputRef = useRef(null); - const [query, setQuery] = useState(""); - const [activeIndex, setActiveIndex] = useState(0); - - useEffect(() => { - if (open) { - setQuery(""); - setActiveIndex(0); - queueMicrotask(() => inputRef.current?.focus()); - } - }, [open]); + const { setTheme } = useTheme(); + const { setOption } = useRefreshInterval(); - const normalized = query.trim().toLowerCase(); - const results = normalized - ? COMMANDS.filter((c) => `${c.label} ${c.keywords ?? ""}`.toLowerCase().includes(normalized)) - : COMMANDS; - - const runAt = useCallback( - (index: number) => { - const item = results[index]; - if (!item) return; + const go = useCallback( + (to: string) => { setOpen(false); - navigate({ to: item.to }); + navigate({ to }); }, - [navigate, results, setOpen], + [navigate, setOpen], ); - useEffect(() => { - if (activeIndex >= results.length) setActiveIndex(Math.max(0, results.length - 1)); - }, [activeIndex, results.length]); - - if (!open) return null; - return ( -
-
setOpen(false)} /> -
{ - if (event.key === "ArrowDown") { - event.preventDefault(); - setActiveIndex((i) => Math.min(i + 1, results.length - 1)); - } else if (event.key === "ArrowUp") { - event.preventDefault(); - setActiveIndex((i) => Math.max(i - 1, 0)); - } else if (event.key === "Enter") { - event.preventDefault(); - runAt(activeIndex); - } - }} - > -
- - setQuery(e.target.value)} - placeholder="Type a command or search…" - className="flex-1 bg-transparent text-sm outline-none placeholder:text-[var(--fg-subtle)]" - aria-label="Search" - /> - Esc -
-
    - {results.length === 0 ? ( -
  • No matches
  • - ) : ( - results.map((item, i) => ( -
  • - -
  • - )) - )} -
-
-
+ + + + No results found. + + {NAV_COMMANDS.map(({ label, to, icon: Icon, hint }) => ( + go(to)}> + + {label} + {hint ? {hint} : null} + + ))} + + + + { + setTheme("light"); + setOpen(false); + }} + > + Light theme + + { + setTheme("dark"); + setOpen(false); + }} + > + Dark theme + + { + setTheme("system"); + setOpen(false); + }} + > + System theme + + + + + {REFRESH_COMMANDS.map((option) => ( + { + setOption(option); + setOpen(false); + }} + > + {option === "off" ? "Off" : `Every ${option}`} + + ))} + + + ); } diff --git a/dashboard-next/src/components/layout/header.tsx b/dashboard-next/src/components/layout/header.tsx index 5f41fa3..8f5de82 100644 --- a/dashboard-next/src/components/layout/header.tsx +++ b/dashboard-next/src/components/layout/header.tsx @@ -2,18 +2,21 @@ import { Search } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Kbd } from "@/components/ui/kbd"; import { useCommandPalette } from "@/providers/command-palette-provider"; +import { LastRefreshed } from "./last-refreshed"; +import { MobileMenu } from "./mobile-menu"; import { RefreshControl } from "./refresh-control"; import { ThemeToggle } from "./theme-toggle"; export function Header() { const { setOpen } = useCommandPalette(); return ( -
+
+ +
+
diff --git a/dashboard-next/src/components/layout/index.ts b/dashboard-next/src/components/layout/index.ts index 7bf00ad..c842370 100644 --- a/dashboard-next/src/components/layout/index.ts +++ b/dashboard-next/src/components/layout/index.ts @@ -1,7 +1,12 @@ export { AppShell } from "./app-shell"; +export type { Crumb } from "./breadcrumbs"; +export { Breadcrumbs } from "./breadcrumbs"; export { CommandPalette } from "./command-palette"; export { Header } from "./header"; +export { LastRefreshed } from "./last-refreshed"; +export { MobileMenu } from "./mobile-menu"; export { PageHeader } from "./page-header"; export { RefreshControl } from "./refresh-control"; +export { RouteErrorBoundary } from "./route-error-boundary"; export { Sidebar } from "./sidebar"; export { ThemeToggle } from "./theme-toggle"; diff --git a/dashboard-next/src/components/layout/last-refreshed.tsx b/dashboard-next/src/components/layout/last-refreshed.tsx new file mode 100644 index 0000000..8e49a67 --- /dev/null +++ b/dashboard-next/src/components/layout/last-refreshed.tsx @@ -0,0 +1,43 @@ +import { Loader2 } from "lucide-react"; +import { useEffect, useState } from "react"; +import { useLastRefreshed } from "@/hooks"; +import { cn } from "@/lib/cn"; + +function formatAgo(ms: number): string { + const seconds = Math.floor(ms / 1000); + if (seconds < 5) return "just now"; + if (seconds < 60) return `${seconds}s ago`; + const minutes = Math.floor(seconds / 60); + if (minutes < 60) return `${minutes}m ago`; + const hours = Math.floor(minutes / 60); + return `${hours}h ago`; +} + +export function LastRefreshed({ className }: { className?: string }) { + const { lastRefreshedAt, isFetching } = useLastRefreshed(); + const [, setTick] = useState(0); + + useEffect(() => { + const id = setInterval(() => setTick((n) => n + 1), 1000); + return () => clearInterval(id); + }, []); + + const label = isFetching ? "Refreshing…" : `Updated ${formatAgo(Date.now() - lastRefreshedAt)}`; + + return ( + + {isFetching ? ( + + ) : ( + + )} + {label} + + ); +} diff --git a/dashboard-next/src/components/layout/mobile-menu.tsx b/dashboard-next/src/components/layout/mobile-menu.tsx new file mode 100644 index 0000000..9b67ae9 --- /dev/null +++ b/dashboard-next/src/components/layout/mobile-menu.tsx @@ -0,0 +1,109 @@ +import { Link, useLocation } from "@tanstack/react-router"; +import { + Activity, + BarChart3, + Box, + CircuitBoard, + LayoutDashboard, + ListTree, + type LucideIcon, + Menu, + ScrollText, + Server, + Settings2, + Skull, +} from "lucide-react"; +import { useEffect, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"; +import { cn } from "@/lib/cn"; +import { site } from "@/lib/site"; + +interface NavItem { + to: string; + label: string; + icon: LucideIcon; +} + +const NAV: Array<{ title: string; items: NavItem[] }> = [ + { + title: "Monitoring", + items: [ + { to: "/", label: "Overview", icon: LayoutDashboard }, + { to: "/jobs", label: "Jobs", icon: ListTree }, + { to: "/metrics", label: "Metrics", icon: BarChart3 }, + { to: "/logs", label: "Logs", icon: ScrollText }, + ], + }, + { + title: "Infrastructure", + items: [ + { to: "/queues", label: "Queues", icon: Box }, + { to: "/workers", label: "Workers", icon: Server }, + { to: "/resources", label: "Resources", icon: Activity }, + ], + }, + { + title: "Reliability", + items: [ + { to: "/dead-letters", label: "Dead letters", icon: Skull }, + { to: "/circuit-breakers", label: "Circuit breakers", icon: CircuitBoard }, + { to: "/system", label: "System", icon: Settings2 }, + ], + }, +]; + +export function MobileMenu() { + const { pathname } = useLocation(); + const [open, setOpen] = useState(false); + + // Close on navigation + useEffect(() => { + setOpen(false); + }, [pathname]); + + return ( + + + + + + + {site.name} Dashboard + + + + + ); +} diff --git a/dashboard-next/src/components/layout/page-header.tsx b/dashboard-next/src/components/layout/page-header.tsx index f608a5a..387b78b 100644 --- a/dashboard-next/src/components/layout/page-header.tsx +++ b/dashboard-next/src/components/layout/page-header.tsx @@ -1,15 +1,24 @@ import type { ReactNode } from "react"; import { cn } from "@/lib/cn"; +import { Breadcrumbs, type Crumb } from "./breadcrumbs"; interface PageHeaderProps { eyebrow?: string; title: string; description?: string; actions?: ReactNode; + breadcrumbs?: Crumb[]; className?: string; } -export function PageHeader({ eyebrow, title, description, actions, className }: PageHeaderProps) { +export function PageHeader({ + eyebrow, + title, + description, + actions, + breadcrumbs, + className, +}: PageHeaderProps) { return (
- {eyebrow ? ( + {breadcrumbs && breadcrumbs.length > 0 ? ( + + ) : eyebrow ? (
{eyebrow}
@@ -26,7 +37,7 @@ export function PageHeader({ eyebrow, title, description, actions, className }:

{title}

{description ?

{description}

: null}
- {actions ?
{actions}
: null} + {actions ?
{actions}
: null}
); } diff --git a/dashboard-next/src/components/layout/route-error-boundary.tsx b/dashboard-next/src/components/layout/route-error-boundary.tsx new file mode 100644 index 0000000..685bdc5 --- /dev/null +++ b/dashboard-next/src/components/layout/route-error-boundary.tsx @@ -0,0 +1,36 @@ +import { QueryErrorResetBoundary } from "@tanstack/react-query"; +import { AlertTriangle } from "lucide-react"; +import type { ReactNode } from "react"; +import { ErrorBoundary, type FallbackProps } from "react-error-boundary"; +import { Button } from "@/components/ui/button"; + +function RouteErrorFallback({ error, resetErrorBoundary }: FallbackProps) { + return ( +
+
+
+ +
+

Something went wrong

+

+ {error instanceof Error ? error.message : String(error)} +

+ +
+
+ ); +} + +export function RouteErrorBoundary({ children }: { children: ReactNode }) { + return ( + + {({ reset }) => ( + + {children} + + )} + + ); +} diff --git a/dashboard-next/src/hooks/index.ts b/dashboard-next/src/hooks/index.ts new file mode 100644 index 0000000..66ebcac --- /dev/null +++ b/dashboard-next/src/hooks/index.ts @@ -0,0 +1,2 @@ +export { useLastRefreshed } from "./use-last-refreshed"; +export { useMediaQuery } from "./use-media-query"; diff --git a/dashboard-next/src/hooks/use-last-refreshed.ts b/dashboard-next/src/hooks/use-last-refreshed.ts new file mode 100644 index 0000000..74f0350 --- /dev/null +++ b/dashboard-next/src/hooks/use-last-refreshed.ts @@ -0,0 +1,17 @@ +import { useIsFetching } from "@tanstack/react-query"; +import { useEffect, useState } from "react"; + +/** + * Tracks the timestamp of the last moment no queries were actively fetching, + * providing an approximate "last refreshed at" time for the dashboard. + */ +export function useLastRefreshed(): { lastRefreshedAt: number; isFetching: boolean } { + const fetching = useIsFetching(); + const [lastRefreshedAt, setLastRefreshedAt] = useState(() => Date.now()); + + useEffect(() => { + if (fetching === 0) setLastRefreshedAt(Date.now()); + }, [fetching]); + + return { lastRefreshedAt, isFetching: fetching > 0 }; +} diff --git a/dashboard-next/src/hooks/use-media-query.ts b/dashboard-next/src/hooks/use-media-query.ts new file mode 100644 index 0000000..118185c --- /dev/null +++ b/dashboard-next/src/hooks/use-media-query.ts @@ -0,0 +1,17 @@ +import { useEffect, useState } from "react"; + +export function useMediaQuery(query: string): boolean { + const [matches, setMatches] = useState(() => + typeof window === "undefined" ? false : window.matchMedia(query).matches, + ); + + useEffect(() => { + const media = window.matchMedia(query); + const handler = (event: MediaQueryListEvent) => setMatches(event.matches); + setMatches(media.matches); + media.addEventListener("change", handler); + return () => media.removeEventListener("change", handler); + }, [query]); + + return matches; +} diff --git a/dashboard-next/src/providers/index.tsx b/dashboard-next/src/providers/index.tsx index 8ea3d73..bdffd57 100644 --- a/dashboard-next/src/providers/index.tsx +++ b/dashboard-next/src/providers/index.tsx @@ -1,4 +1,5 @@ import type { ReactNode } from "react"; +import { Toaster } from "@/components/ui/toaster"; import { CommandPaletteProvider } from "./command-palette-provider"; import { QueryProvider } from "./query-provider"; import { RefreshIntervalProvider } from "./refresh-interval-provider"; @@ -9,7 +10,10 @@ export function Providers({ children }: { children: ReactNode }) { - {children} + + {children} + + diff --git a/dashboard-next/src/routes/jobs/$id.tsx b/dashboard-next/src/routes/jobs/$id.tsx index 972a5c9..4656eaf 100644 --- a/dashboard-next/src/routes/jobs/$id.tsx +++ b/dashboard-next/src/routes/jobs/$id.tsx @@ -1,9 +1,6 @@ -import { createFileRoute, Link } from "@tanstack/react-router"; -import { ArrowLeft } from "lucide-react"; +import { createFileRoute } from "@tanstack/react-router"; import { PageHeader } from "@/components/layout"; -import { buttonVariants } from "@/components/ui/button"; import { EmptyState } from "@/components/ui/empty-state"; -import { cn } from "@/lib/cn"; export const Route = createFileRoute("/jobs/$id")({ component: JobDetailPage, @@ -13,15 +10,7 @@ function JobDetailPage() { const { id } = Route.useParams(); return ( <> - - All jobs - - } - /> + Date: Sat, 25 Apr 2026 01:00:52 +0530 Subject: [PATCH 10/42] feat(dashboard-next): add shared api types --- dashboard-next/src/lib/api-types.ts | 172 ++++++++++++++++++++++++++++ dashboard-next/src/lib/status.ts | 56 +++++---- 2 files changed, 198 insertions(+), 30 deletions(-) create mode 100644 dashboard-next/src/lib/api-types.ts diff --git a/dashboard-next/src/lib/api-types.ts b/dashboard-next/src/lib/api-types.ts new file mode 100644 index 0000000..51208af --- /dev/null +++ b/dashboard-next/src/lib/api-types.ts @@ -0,0 +1,172 @@ +/** + * Response shapes for the Taskito dashboard API. + * + * These mirror the `to_dict()` output of the underlying Rust/Python models. + * Keep in sync with: + * - `py_src/taskito/dashboard.py` route handlers + * - `docs/guide/observability/dashboard-api.md` (documented contract) + */ + +export type JobStatus = "pending" | "running" | "complete" | "failed" | "dead" | "cancelled"; + +export interface QueueStats { + pending: number; + running: number; + completed: number; + failed: number; + dead: number; + cancelled: number; +} + +export type QueueStatsMap = Record< + string, + { + pending: number; + running: number; + completed?: number; + failed?: number; + dead?: number; + cancelled?: number; + } +>; + +export interface Job { + id: string; + task_name: string; + queue: string; + status: JobStatus; + priority: number; + progress: number | null; + retry_count: number; + max_retries: number; + created_at: number; + scheduled_at: number; + started_at: number | null; + completed_at: number | null; + timeout_ms: number; + error: string | null; + unique_key: string | null; + metadata: string | null; +} + +export interface JobError { + attempt: number; + error: string; + failed_at: number; +} + +export interface TaskLog { + job_id: string; + task_name: string; + level: string; + message: string; + extra: string | null; + logged_at: number; +} + +export interface ReplayEntry { + replay_job_id: string; + replayed_at: number; + original_error: string | null; + replay_error: string | null; +} + +export interface DagNode { + id: string; + task_name: string; + status: JobStatus; +} + +export interface DagEdge { + from: string; + to: string; +} + +export interface DagData { + nodes: DagNode[]; + edges: DagEdge[]; +} + +export interface DeadLetter { + id: string; + original_job_id: string; + task_name: string; + queue: string; + error: string | null; + retry_count: number; + failed_at: number; +} + +export interface TaskMetrics { + count: number; + success_count: number; + failure_count: number; + avg_ms: number; + p50_ms: number; + p95_ms: number; + p99_ms: number; + min_ms: number; + max_ms: number; +} + +export type MetricsResponse = Record; + +export interface TimeseriesBucket { + timestamp: number; + count: number; + success: number; + failure: number; + avg_ms: number; +} + +export interface Worker { + worker_id: string; + queues: string; + last_heartbeat: number; + registered_at: number; + tags: string | null; +} + +export interface CircuitBreaker { + task_name: string; + state: "closed" | "open" | "half_open"; + failure_count: number; + threshold: number; + window_ms: number; + cooldown_ms: number; + last_failure_at: number | null; +} + +export interface ResourcePoolStats { + active: number; + idle: number; + size: number; + total_timeouts: number; +} + +export interface ResourceStatus { + name: string; + scope: string; + health: string; + init_duration_ms: number; + recreations: number; + depends_on: string[]; + pool?: ResourcePoolStats; +} + +export type ProxyStats = Record< + string, + { + reconstructions: number; + avg_ms: number; + errors: number; + } +>; + +export type InterceptionStats = Record< + string, + { + count: number; + avg_ms: number; + } +>; diff --git a/dashboard-next/src/lib/status.ts b/dashboard-next/src/lib/status.ts index 1be36e9..5d3d964 100644 --- a/dashboard-next/src/lib/status.ts +++ b/dashboard-next/src/lib/status.ts @@ -1,51 +1,47 @@ -export type JobStatus = - | "pending" - | "running" - | "completed" - | "failed" - | "dead" - | "cancelled" - | "scheduled"; +import type { JobStatus } from "@/lib/api-types"; + +export type { JobStatus }; export type CircuitState = "closed" | "open" | "half_open"; export type ResourceHealth = "healthy" | "degraded" | "unhealthy" | "unknown"; -type ToneKey = "neutral" | "accent" | "info" | "success" | "warning" | "danger"; - -const TONE_CLASS: Record = { - neutral: - "bg-[var(--surface-3)] text-[var(--fg-muted)] ring-1 ring-inset ring-[var(--border-strong)]", - accent: "bg-accent-dim text-accent ring-1 ring-inset ring-accent/30", - info: "bg-info-dim text-info ring-1 ring-inset ring-info/30", - success: "bg-success-dim text-success ring-1 ring-inset ring-success/30", - warning: "bg-warning-dim text-warning ring-1 ring-inset ring-warning/30", - danger: "bg-danger-dim text-danger ring-1 ring-inset ring-danger/30", -}; +export type Tone = "neutral" | "accent" | "info" | "success" | "warning" | "danger"; -export const JOB_STATUS_TONE: Record = { +export const JOB_STATUS_TONE: Record = { pending: "neutral", running: "info", - scheduled: "accent", - completed: "success", + complete: "success", failed: "danger", dead: "danger", cancelled: "warning", }; -export const CIRCUIT_TONE: Record = { +export const JOB_STATUS_LABEL: Record = { + pending: "Pending", + running: "Running", + complete: "Completed", + failed: "Failed", + dead: "Dead", + cancelled: "Cancelled", +}; + +export const CIRCUIT_TONE: Record = { closed: "success", half_open: "warning", open: "danger", }; -export const RESOURCE_TONE: Record = { - healthy: "success", - degraded: "warning", - unhealthy: "danger", - unknown: "neutral", +export const CIRCUIT_LABEL: Record = { + closed: "Closed", + half_open: "Half open", + open: "Open", }; -export function toneClasses(tone: ToneKey): string { - return TONE_CLASS[tone]; +export function resourceTone(health: string): Tone { + const key = health.toLowerCase(); + if (key === "healthy") return "success"; + if (key === "degraded") return "warning"; + if (key === "unhealthy") return "danger"; + return "neutral"; } From c8930c9316c35d28245d36d3836127abd235179b Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 01:00:59 +0530 Subject: [PATCH 11/42] feat(dashboard-next): overview page with stats, queues, recent jobs, sparkline --- dashboard-next/src/features/overview/api.ts | 29 +++++ .../src/features/overview/components/index.ts | 4 + .../overview/components/queue-breakdown.tsx | 122 ++++++++++++++++++ .../overview/components/recent-jobs.tsx | 91 +++++++++++++ .../overview/components/stats-grid.tsx | 60 +++++++++ .../components/throughput-sparkline.tsx | 90 +++++++++++++ dashboard-next/src/features/overview/hooks.ts | 54 ++++++++ dashboard-next/src/features/overview/index.ts | 2 + dashboard-next/src/routes/index.tsx | 70 ++++++---- 9 files changed, 499 insertions(+), 23 deletions(-) create mode 100644 dashboard-next/src/features/overview/api.ts create mode 100644 dashboard-next/src/features/overview/components/index.ts create mode 100644 dashboard-next/src/features/overview/components/queue-breakdown.tsx create mode 100644 dashboard-next/src/features/overview/components/recent-jobs.tsx create mode 100644 dashboard-next/src/features/overview/components/stats-grid.tsx create mode 100644 dashboard-next/src/features/overview/components/throughput-sparkline.tsx create mode 100644 dashboard-next/src/features/overview/hooks.ts create mode 100644 dashboard-next/src/features/overview/index.ts diff --git a/dashboard-next/src/features/overview/api.ts b/dashboard-next/src/features/overview/api.ts new file mode 100644 index 0000000..e1adbe2 --- /dev/null +++ b/dashboard-next/src/features/overview/api.ts @@ -0,0 +1,29 @@ +import { api } from "@/lib/api-client"; +import type { Job, QueueStats, QueueStatsMap, TimeseriesBucket } from "@/lib/api-types"; + +export function fetchStats(signal?: AbortSignal): Promise { + return api.get("/api/stats", { signal }); +} + +export function fetchQueueStats(signal?: AbortSignal): Promise { + return api.get("/api/stats/queues", { signal }); +} + +export function fetchPausedQueues(signal?: AbortSignal): Promise { + return api.get("/api/queues/paused", { signal }); +} + +export function fetchRecentJobs(limit: number, signal?: AbortSignal): Promise { + return api.get("/api/jobs", { signal, params: { limit } }); +} + +export function fetchThroughput( + bucketSeconds: number, + sinceSeconds: number, + signal?: AbortSignal, +): Promise { + return api.get("/api/metrics/timeseries", { + signal, + params: { bucket: bucketSeconds, since: sinceSeconds }, + }); +} diff --git a/dashboard-next/src/features/overview/components/index.ts b/dashboard-next/src/features/overview/components/index.ts new file mode 100644 index 0000000..3500593 --- /dev/null +++ b/dashboard-next/src/features/overview/components/index.ts @@ -0,0 +1,4 @@ +export { QueueBreakdown } from "./queue-breakdown"; +export { RecentJobs } from "./recent-jobs"; +export { StatsGrid } from "./stats-grid"; +export { ThroughputSparkline } from "./throughput-sparkline"; diff --git a/dashboard-next/src/features/overview/components/queue-breakdown.tsx b/dashboard-next/src/features/overview/components/queue-breakdown.tsx new file mode 100644 index 0000000..6ca568e --- /dev/null +++ b/dashboard-next/src/features/overview/components/queue-breakdown.tsx @@ -0,0 +1,122 @@ +import type { ColumnDef } from "@tanstack/react-table"; +import { useMemo } from "react"; +import { Badge } from "@/components/ui/badge"; +import { DataTable } from "@/components/ui/data-table"; +import { ErrorState } from "@/components/ui/error-state"; +import { Skeleton } from "@/components/ui/skeleton"; +import type { QueueStatsMap } from "@/lib/api-types"; +import { formatCount } from "@/lib/number"; + +interface Row { + name: string; + pending: number; + running: number; + completed: number; + failed: number; + dead: number; + paused: boolean; +} + +interface QueueBreakdownProps { + queueStats: QueueStatsMap | undefined; + paused: string[] | undefined; + loading: boolean; + error: Error | null; + onRetry: () => void; +} + +export function QueueBreakdown({ + queueStats, + paused, + loading, + error, + onRetry, +}: QueueBreakdownProps) { + const rows = useMemo(() => { + if (!queueStats) return []; + const pausedSet = new Set(paused ?? []); + return Object.entries(queueStats) + .map(([name, s]) => ({ + name, + pending: s.pending ?? 0, + running: s.running ?? 0, + completed: s.completed ?? 0, + failed: s.failed ?? 0, + dead: s.dead ?? 0, + paused: pausedSet.has(name), + })) + .sort((a, b) => a.name.localeCompare(b.name)); + }, [queueStats, paused]); + + const columns = useMemo[]>( + () => [ + { + accessorKey: "name", + header: "Queue", + cell: ({ row }) => ( +
+ {row.original.name} + {row.original.paused ? Paused : null} +
+ ), + }, + { + accessorKey: "pending", + header: "Pending", + cell: ({ getValue }) => ( + {formatCount(getValue())} + ), + }, + { + accessorKey: "running", + header: "Running", + cell: ({ getValue }) => ( + {formatCount(getValue())} + ), + }, + { + accessorKey: "completed", + header: "Completed", + cell: ({ getValue }) => ( + + {formatCount(getValue())} + + ), + }, + { + accessorKey: "failed", + header: "Failed", + cell: ({ row }) => { + const total = row.original.failed + row.original.dead; + return ( + 0 ? "text-danger" : "text-[var(--fg-muted)]"}`} + > + {formatCount(total)} + + ); + }, + }, + ], + [], + ); + + if (error) { + return ( + + ); + } + + if (loading && rows.length === 0) { + return ; + } + + return ( + r.name} + empty="No queues with activity yet" + /> + ); +} diff --git a/dashboard-next/src/features/overview/components/recent-jobs.tsx b/dashboard-next/src/features/overview/components/recent-jobs.tsx new file mode 100644 index 0000000..898df4d --- /dev/null +++ b/dashboard-next/src/features/overview/components/recent-jobs.tsx @@ -0,0 +1,91 @@ +import { Link, useNavigate } from "@tanstack/react-router"; +import type { ColumnDef } from "@tanstack/react-table"; +import { useMemo } from "react"; +import { Badge } from "@/components/ui/badge"; +import { DataTable } from "@/components/ui/data-table"; +import { ErrorState } from "@/components/ui/error-state"; +import { Skeleton } from "@/components/ui/skeleton"; +import type { Job } from "@/lib/api-types"; +import { JOB_STATUS_LABEL, JOB_STATUS_TONE } from "@/lib/status"; +import { formatRelative } from "@/lib/time"; + +interface RecentJobsProps { + jobs: Job[] | undefined; + loading: boolean; + error: Error | null; + onRetry: () => void; +} + +export function RecentJobs({ jobs, loading, error, onRetry }: RecentJobsProps) { + const navigate = useNavigate(); + + const columns = useMemo[]>( + () => [ + { + accessorKey: "id", + header: "Job", + cell: ({ row }) => ( + e.stopPropagation()} + > + {row.original.id.slice(0, 8)}… + + ), + }, + { + accessorKey: "task_name", + header: "Task", + cell: ({ getValue }) => ( + {getValue()} + ), + }, + { + accessorKey: "queue", + header: "Queue", + cell: ({ getValue }) => ( + {getValue()} + ), + }, + { + accessorKey: "status", + header: "Status", + cell: ({ row }) => ( + + {JOB_STATUS_LABEL[row.original.status]} + + ), + }, + { + accessorKey: "created_at", + header: "Created", + cell: ({ getValue }) => ( + + {formatRelative(getValue() * 1000)} + + ), + }, + ], + [], + ); + + if (error) { + return ; + } + + if (loading && !jobs) { + return ; + } + + return ( + j.id} + empty="No jobs yet" + onRowClick={(job) => navigate({ to: "/jobs/$id", params: { id: job.id } })} + /> + ); +} diff --git a/dashboard-next/src/features/overview/components/stats-grid.tsx b/dashboard-next/src/features/overview/components/stats-grid.tsx new file mode 100644 index 0000000..00ba7d8 --- /dev/null +++ b/dashboard-next/src/features/overview/components/stats-grid.tsx @@ -0,0 +1,60 @@ +import { CheckCircle2, Clock, Pause, Play, Skull } from "lucide-react"; +import { Skeleton } from "@/components/ui/skeleton"; +import { StatCard } from "@/components/ui/stat-card"; +import type { QueueStats } from "@/lib/api-types"; +import { formatCount } from "@/lib/number"; + +interface StatsGridProps { + stats: QueueStats | undefined; + pausedCount: number | undefined; + loading?: boolean; +} + +export function StatsGrid({ stats, pausedCount, loading }: StatsGridProps) { + const failedTotal = (stats?.failed ?? 0) + (stats?.dead ?? 0); + return ( +
+ } + value={loading ? : formatCount(stats?.pending ?? 0)} + /> + } + value={loading ? : formatCount(stats?.running ?? 0)} + /> + } + value={loading ? : formatCount(stats?.completed ?? 0)} + /> + } + value={loading ? : formatCount(failedTotal)} + hint={ + stats + ? `${formatCount(stats.dead)} dead · ${formatCount(stats.cancelled)} cancelled` + : undefined + } + /> + } + value={ + loading || pausedCount == null ? ( + + ) : ( + formatCount(pausedCount) + ) + } + /> +
+ ); +} diff --git a/dashboard-next/src/features/overview/components/throughput-sparkline.tsx b/dashboard-next/src/features/overview/components/throughput-sparkline.tsx new file mode 100644 index 0000000..c4c66e3 --- /dev/null +++ b/dashboard-next/src/features/overview/components/throughput-sparkline.tsx @@ -0,0 +1,90 @@ +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Skeleton } from "@/components/ui/skeleton"; +import type { TimeseriesBucket } from "@/lib/api-types"; +import { formatCount } from "@/lib/number"; + +interface ThroughputSparklineProps { + buckets: TimeseriesBucket[] | undefined; + loading?: boolean; +} + +export function ThroughputSparkline({ buckets, loading }: ThroughputSparklineProps) { + const points = buckets ?? []; + const total = points.reduce((sum, b) => sum + b.count, 0); + const peak = points.reduce((max, b) => Math.max(max, b.count), 0); + + return ( + + + Throughput — last hour + {loading ? ( + + ) : ( + + {formatCount(total)} runs · peak {formatCount(peak)}/min + + )} + + + {loading ? ( + + ) : points.length === 0 ? ( +
+ No activity in this window +
+ ) : ( + + )} +
+
+ ); +} + +function Sparkline({ buckets }: { buckets: TimeseriesBucket[] }) { + const width = 800; + const height = 80; + const maxCount = Math.max(1, ...buckets.map((b) => b.count)); + const step = buckets.length > 1 ? width / (buckets.length - 1) : 0; + + const areaPath = buckets + .map((b, i) => { + const x = i * step; + const y = height - (b.count / maxCount) * height; + return `${i === 0 ? "M" : "L"} ${x.toFixed(1)} ${y.toFixed(1)}`; + }) + .concat([`L ${width} ${height}`, `L 0 ${height}`, "Z"]) + .join(" "); + + const linePath = buckets + .map((b, i) => { + const x = i * step; + const y = height - (b.count / maxCount) * height; + return `${i === 0 ? "M" : "L"} ${x.toFixed(1)} ${y.toFixed(1)}`; + }) + .join(" "); + + return ( + + + + + + + + + + + ); +} diff --git a/dashboard-next/src/features/overview/hooks.ts b/dashboard-next/src/features/overview/hooks.ts new file mode 100644 index 0000000..6a296e4 --- /dev/null +++ b/dashboard-next/src/features/overview/hooks.ts @@ -0,0 +1,54 @@ +import { useQuery } from "@tanstack/react-query"; +import { useRefreshInterval } from "@/providers/refresh-interval-provider"; +import { + fetchPausedQueues, + fetchQueueStats, + fetchRecentJobs, + fetchStats, + fetchThroughput, +} from "./api"; + +export function useStats() { + const { intervalMs } = useRefreshInterval(); + return useQuery({ + queryKey: ["stats"], + queryFn: ({ signal }) => fetchStats(signal), + refetchInterval: intervalMs, + }); +} + +export function useQueueStats() { + const { intervalMs } = useRefreshInterval(); + return useQuery({ + queryKey: ["stats", "queues"], + queryFn: ({ signal }) => fetchQueueStats(signal), + refetchInterval: intervalMs, + }); +} + +export function usePausedQueues() { + const { intervalMs } = useRefreshInterval(); + return useQuery({ + queryKey: ["queues", "paused"], + queryFn: ({ signal }) => fetchPausedQueues(signal), + refetchInterval: intervalMs, + }); +} + +export function useRecentJobs(limit = 10) { + const { intervalMs } = useRefreshInterval(); + return useQuery({ + queryKey: ["jobs", "recent", limit], + queryFn: ({ signal }) => fetchRecentJobs(limit, signal), + refetchInterval: intervalMs, + }); +} + +export function useThroughput(bucketSeconds = 60, sinceSeconds = 3600) { + const { intervalMs } = useRefreshInterval(); + return useQuery({ + queryKey: ["metrics", "throughput", bucketSeconds, sinceSeconds], + queryFn: ({ signal }) => fetchThroughput(bucketSeconds, sinceSeconds, signal), + refetchInterval: intervalMs, + }); +} diff --git a/dashboard-next/src/features/overview/index.ts b/dashboard-next/src/features/overview/index.ts new file mode 100644 index 0000000..a234113 --- /dev/null +++ b/dashboard-next/src/features/overview/index.ts @@ -0,0 +1,2 @@ +export * from "./components"; +export * from "./hooks"; diff --git a/dashboard-next/src/routes/index.tsx b/dashboard-next/src/routes/index.tsx index e81ee58..d07b57e 100644 --- a/dashboard-next/src/routes/index.tsx +++ b/dashboard-next/src/routes/index.tsx @@ -1,22 +1,28 @@ import { createFileRoute } from "@tanstack/react-router"; -import { Clock, ListTree, Pause, Play, Skull } from "lucide-react"; import { PageHeader } from "@/components/layout"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Skeleton } from "@/components/ui/skeleton"; +import { + QueueBreakdown, + RecentJobs, + StatsGrid, + ThroughputSparkline, + usePausedQueues, + useQueueStats, + useRecentJobs, + useStats, + useThroughput, +} from "@/features/overview"; export const Route = createFileRoute("/")({ component: OverviewPage, }); -const STATS = [ - { key: "pending", label: "Pending", icon: Clock, tone: "text-[var(--fg-muted)]" }, - { key: "running", label: "Running", icon: Play, tone: "text-info" }, - { key: "completed", label: "Completed", icon: ListTree, tone: "text-success" }, - { key: "failed", label: "Failed / dead", icon: Skull, tone: "text-danger" }, - { key: "paused", label: "Paused queues", icon: Pause, tone: "text-warning" }, -] as const; - function OverviewPage() { + const stats = useStats(); + const queueStats = useQueueStats(); + const paused = usePausedQueues(); + const jobs = useRecentJobs(10); + const throughput = useThroughput(60, 3600); + return ( <> -
- {STATS.map(({ key, label, icon: Icon, tone }) => ( - - - {label} - - - - - - - ))} + +
+ + + + +
+
+

Queues

+
+ queueStats.refetch()} + /> +
+ +
+
+

Recent jobs

+
+ jobs.refetch()} + /> +
); From 2e272af77b22f96ba86a91718c4f57505c3a311b Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 01:01:05 +0530 Subject: [PATCH 12/42] feat(dashboard-next): port workers, circuit breakers, resources, system --- .../src/features/circuit-breakers/api.ts | 6 + .../components/circuit-breakers-table.tsx | 116 ++++++++++++++++ .../circuit-breakers/components/index.ts | 1 + .../src/features/circuit-breakers/hooks.ts | 12 ++ .../src/features/circuit-breakers/index.ts | 2 + dashboard-next/src/features/resources/api.ts | 6 + .../features/resources/components/index.ts | 1 + .../resources/components/resources-table.tsx | 128 ++++++++++++++++++ .../src/features/resources/hooks.ts | 12 ++ .../src/features/resources/index.ts | 2 + dashboard-next/src/features/system/api.ts | 10 ++ .../src/features/system/components/index.ts | 2 + .../system/components/interception-table.tsx | 79 +++++++++++ .../system/components/proxy-table.tsx | 88 ++++++++++++ dashboard-next/src/features/system/hooks.ts | 21 +++ dashboard-next/src/features/system/index.ts | 2 + dashboard-next/src/features/workers/api.ts | 6 + .../src/features/workers/components/index.ts | 1 + .../workers/components/workers-table.tsx | 111 +++++++++++++++ dashboard-next/src/features/workers/hooks.ts | 12 ++ dashboard-next/src/features/workers/index.ts | 2 + .../src/routes/circuit-breakers.tsx | 12 +- dashboard-next/src/routes/resources.tsx | 17 ++- dashboard-next/src/routes/system.tsx | 39 +++++- dashboard-next/src/routes/workers.tsx | 23 +++- 25 files changed, 696 insertions(+), 15 deletions(-) create mode 100644 dashboard-next/src/features/circuit-breakers/api.ts create mode 100644 dashboard-next/src/features/circuit-breakers/components/circuit-breakers-table.tsx create mode 100644 dashboard-next/src/features/circuit-breakers/components/index.ts create mode 100644 dashboard-next/src/features/circuit-breakers/hooks.ts create mode 100644 dashboard-next/src/features/circuit-breakers/index.ts create mode 100644 dashboard-next/src/features/resources/api.ts create mode 100644 dashboard-next/src/features/resources/components/index.ts create mode 100644 dashboard-next/src/features/resources/components/resources-table.tsx create mode 100644 dashboard-next/src/features/resources/hooks.ts create mode 100644 dashboard-next/src/features/resources/index.ts create mode 100644 dashboard-next/src/features/system/api.ts create mode 100644 dashboard-next/src/features/system/components/index.ts create mode 100644 dashboard-next/src/features/system/components/interception-table.tsx create mode 100644 dashboard-next/src/features/system/components/proxy-table.tsx create mode 100644 dashboard-next/src/features/system/hooks.ts create mode 100644 dashboard-next/src/features/system/index.ts create mode 100644 dashboard-next/src/features/workers/api.ts create mode 100644 dashboard-next/src/features/workers/components/index.ts create mode 100644 dashboard-next/src/features/workers/components/workers-table.tsx create mode 100644 dashboard-next/src/features/workers/hooks.ts create mode 100644 dashboard-next/src/features/workers/index.ts diff --git a/dashboard-next/src/features/circuit-breakers/api.ts b/dashboard-next/src/features/circuit-breakers/api.ts new file mode 100644 index 0000000..0ced3f0 --- /dev/null +++ b/dashboard-next/src/features/circuit-breakers/api.ts @@ -0,0 +1,6 @@ +import { api } from "@/lib/api-client"; +import type { CircuitBreaker } from "@/lib/api-types"; + +export function fetchCircuitBreakers(signal?: AbortSignal): Promise { + return api.get("/api/circuit-breakers", { signal }); +} diff --git a/dashboard-next/src/features/circuit-breakers/components/circuit-breakers-table.tsx b/dashboard-next/src/features/circuit-breakers/components/circuit-breakers-table.tsx new file mode 100644 index 0000000..87d4d05 --- /dev/null +++ b/dashboard-next/src/features/circuit-breakers/components/circuit-breakers-table.tsx @@ -0,0 +1,116 @@ +import type { ColumnDef } from "@tanstack/react-table"; +import { CircuitBoard } from "lucide-react"; +import { useMemo } from "react"; +import { Badge } from "@/components/ui/badge"; +import { DataTable } from "@/components/ui/data-table"; +import { EmptyState } from "@/components/ui/empty-state"; +import { ErrorState } from "@/components/ui/error-state"; +import { Skeleton } from "@/components/ui/skeleton"; +import type { CircuitBreaker } from "@/lib/api-types"; +import { CIRCUIT_LABEL, CIRCUIT_TONE } from "@/lib/status"; +import { formatDuration } from "@/lib/time"; + +interface CircuitBreakersTableProps { + breakers: CircuitBreaker[] | undefined; + loading: boolean; + error: Error | null; + onRetry: () => void; +} + +export function CircuitBreakersTable({ + breakers, + loading, + error, + onRetry, +}: CircuitBreakersTableProps) { + const columns = useMemo[]>( + () => [ + { + accessorKey: "task_name", + header: "Task", + cell: ({ getValue }) => ( + {getValue()} + ), + }, + { + accessorKey: "state", + header: "State", + cell: ({ row }) => ( + {CIRCUIT_LABEL[row.original.state]} + ), + }, + { + accessorKey: "failure_count", + header: "Failures / Threshold", + cell: ({ row }) => { + const { failure_count, threshold } = row.original; + const over = failure_count >= threshold; + return ( + + {failure_count} / {threshold} + + ); + }, + }, + { + accessorKey: "window_ms", + header: "Window", + cell: ({ getValue }) => ( + + {formatDuration(getValue())} + + ), + }, + { + accessorKey: "cooldown_ms", + header: "Cooldown", + cell: ({ getValue }) => ( + + {formatDuration(getValue())} + + ), + }, + { + accessorKey: "last_failure_at", + header: "Last failure", + cell: ({ getValue }) => { + const v = getValue(); + if (!v) return ; + const ago = Math.round((Date.now() - v * 1000) / 1000); + return ( + + {ago < 60 ? `${ago}s ago` : `${Math.round(ago / 60)}m ago`} + + ); + }, + }, + ], + [], + ); + + if (error) { + return ( + + ); + } + + if (loading && !breakers) { + return ; + } + + if (!breakers || breakers.length === 0) { + return ( + + ); + } + + return b.task_name} />; +} diff --git a/dashboard-next/src/features/circuit-breakers/components/index.ts b/dashboard-next/src/features/circuit-breakers/components/index.ts new file mode 100644 index 0000000..3b8515a --- /dev/null +++ b/dashboard-next/src/features/circuit-breakers/components/index.ts @@ -0,0 +1 @@ +export { CircuitBreakersTable } from "./circuit-breakers-table"; diff --git a/dashboard-next/src/features/circuit-breakers/hooks.ts b/dashboard-next/src/features/circuit-breakers/hooks.ts new file mode 100644 index 0000000..546447e --- /dev/null +++ b/dashboard-next/src/features/circuit-breakers/hooks.ts @@ -0,0 +1,12 @@ +import { useQuery } from "@tanstack/react-query"; +import { useRefreshInterval } from "@/providers/refresh-interval-provider"; +import { fetchCircuitBreakers } from "./api"; + +export function useCircuitBreakers() { + const { intervalMs } = useRefreshInterval(); + return useQuery({ + queryKey: ["circuit-breakers"], + queryFn: ({ signal }) => fetchCircuitBreakers(signal), + refetchInterval: intervalMs, + }); +} diff --git a/dashboard-next/src/features/circuit-breakers/index.ts b/dashboard-next/src/features/circuit-breakers/index.ts new file mode 100644 index 0000000..a234113 --- /dev/null +++ b/dashboard-next/src/features/circuit-breakers/index.ts @@ -0,0 +1,2 @@ +export * from "./components"; +export * from "./hooks"; diff --git a/dashboard-next/src/features/resources/api.ts b/dashboard-next/src/features/resources/api.ts new file mode 100644 index 0000000..066202b --- /dev/null +++ b/dashboard-next/src/features/resources/api.ts @@ -0,0 +1,6 @@ +import { api } from "@/lib/api-client"; +import type { ResourceStatus } from "@/lib/api-types"; + +export function fetchResources(signal?: AbortSignal): Promise { + return api.get("/api/resources", { signal }); +} diff --git a/dashboard-next/src/features/resources/components/index.ts b/dashboard-next/src/features/resources/components/index.ts new file mode 100644 index 0000000..1d9e5c3 --- /dev/null +++ b/dashboard-next/src/features/resources/components/index.ts @@ -0,0 +1 @@ +export { ResourcesTable } from "./resources-table"; diff --git a/dashboard-next/src/features/resources/components/resources-table.tsx b/dashboard-next/src/features/resources/components/resources-table.tsx new file mode 100644 index 0000000..7cce0d9 --- /dev/null +++ b/dashboard-next/src/features/resources/components/resources-table.tsx @@ -0,0 +1,128 @@ +import type { ColumnDef } from "@tanstack/react-table"; +import { Activity } from "lucide-react"; +import { useMemo } from "react"; +import { Badge } from "@/components/ui/badge"; +import { DataTable } from "@/components/ui/data-table"; +import { EmptyState } from "@/components/ui/empty-state"; +import { ErrorState } from "@/components/ui/error-state"; +import { Skeleton } from "@/components/ui/skeleton"; +import type { ResourceStatus } from "@/lib/api-types"; +import { resourceTone } from "@/lib/status"; +import { formatDuration } from "@/lib/time"; + +interface ResourcesTableProps { + resources: ResourceStatus[] | undefined; + loading: boolean; + error: Error | null; + onRetry: () => void; +} + +export function ResourcesTable({ resources, loading, error, onRetry }: ResourcesTableProps) { + const columns = useMemo[]>( + () => [ + { + accessorKey: "name", + header: "Resource", + cell: ({ getValue }) => ( + {getValue()} + ), + }, + { + accessorKey: "scope", + header: "Scope", + cell: ({ getValue }) => ( + + {getValue()} + + ), + }, + { + accessorKey: "health", + header: "Health", + cell: ({ getValue }) => { + const health = getValue(); + return {health}; + }, + }, + { + accessorKey: "init_duration_ms", + header: "Init", + cell: ({ getValue }) => ( + + {formatDuration(getValue())} + + ), + }, + { + accessorKey: "recreations", + header: "Recreations", + cell: ({ getValue }) => { + const n = getValue(); + return ( + 0 ? "text-warning" : "text-[var(--fg-muted)]"}`}> + {n} + + ); + }, + }, + { + id: "pool", + header: "Pool", + cell: ({ row }) => { + const p = row.original.pool; + if (!p) return ; + return ( + + {p.active}/{p.size} active · {p.idle} idle + {p.total_timeouts > 0 ? ( + · {p.total_timeouts} timeouts + ) : null} + + ); + }, + }, + { + accessorKey: "depends_on", + header: "Depends on", + cell: ({ getValue }) => { + const deps = getValue(); + if (!deps || deps.length === 0) { + return ; + } + return ( +
+ {deps.map((d) => ( + + {d} + + ))} +
+ ); + }, + }, + ], + [], + ); + + if (error) { + return ( + + ); + } + + if (loading && !resources) { + return ; + } + + if (!resources || resources.length === 0) { + return ( + + ); + } + + return r.name} />; +} diff --git a/dashboard-next/src/features/resources/hooks.ts b/dashboard-next/src/features/resources/hooks.ts new file mode 100644 index 0000000..ac7a822 --- /dev/null +++ b/dashboard-next/src/features/resources/hooks.ts @@ -0,0 +1,12 @@ +import { useQuery } from "@tanstack/react-query"; +import { useRefreshInterval } from "@/providers/refresh-interval-provider"; +import { fetchResources } from "./api"; + +export function useResources() { + const { intervalMs } = useRefreshInterval(); + return useQuery({ + queryKey: ["resources"], + queryFn: ({ signal }) => fetchResources(signal), + refetchInterval: intervalMs, + }); +} diff --git a/dashboard-next/src/features/resources/index.ts b/dashboard-next/src/features/resources/index.ts new file mode 100644 index 0000000..a234113 --- /dev/null +++ b/dashboard-next/src/features/resources/index.ts @@ -0,0 +1,2 @@ +export * from "./components"; +export * from "./hooks"; diff --git a/dashboard-next/src/features/system/api.ts b/dashboard-next/src/features/system/api.ts new file mode 100644 index 0000000..656a0e2 --- /dev/null +++ b/dashboard-next/src/features/system/api.ts @@ -0,0 +1,10 @@ +import { api } from "@/lib/api-client"; +import type { InterceptionStats, ProxyStats } from "@/lib/api-types"; + +export function fetchProxyStats(signal?: AbortSignal): Promise { + return api.get("/api/proxy-stats", { signal }); +} + +export function fetchInterceptionStats(signal?: AbortSignal): Promise { + return api.get("/api/interception-stats", { signal }); +} diff --git a/dashboard-next/src/features/system/components/index.ts b/dashboard-next/src/features/system/components/index.ts new file mode 100644 index 0000000..60c802c --- /dev/null +++ b/dashboard-next/src/features/system/components/index.ts @@ -0,0 +1,2 @@ +export { InterceptionTable } from "./interception-table"; +export { ProxyTable } from "./proxy-table"; diff --git a/dashboard-next/src/features/system/components/interception-table.tsx b/dashboard-next/src/features/system/components/interception-table.tsx new file mode 100644 index 0000000..22160c3 --- /dev/null +++ b/dashboard-next/src/features/system/components/interception-table.tsx @@ -0,0 +1,79 @@ +import type { ColumnDef } from "@tanstack/react-table"; +import { useMemo } from "react"; +import { DataTable } from "@/components/ui/data-table"; +import { ErrorState } from "@/components/ui/error-state"; +import { Skeleton } from "@/components/ui/skeleton"; +import type { InterceptionStats } from "@/lib/api-types"; +import { formatCount } from "@/lib/number"; + +interface Row { + strategy: string; + count: number; + avg_ms: number; +} + +interface InterceptionTableProps { + stats: InterceptionStats | undefined; + loading: boolean; + error: Error | null; + onRetry: () => void; +} + +export function InterceptionTable({ stats, loading, error, onRetry }: InterceptionTableProps) { + const rows = useMemo(() => { + if (!stats) return []; + return Object.entries(stats) + .map(([strategy, v]) => ({ strategy, ...v })) + .sort((a, b) => b.count - a.count); + }, [stats]); + + const columns = useMemo[]>( + () => [ + { + accessorKey: "strategy", + header: "Strategy", + cell: ({ getValue }) => ( + {getValue()} + ), + }, + { + accessorKey: "count", + header: "Count", + cell: ({ getValue }) => ( + {formatCount(getValue())} + ), + }, + { + accessorKey: "avg_ms", + header: "Avg", + cell: ({ getValue }) => ( + + {getValue().toFixed(1)}ms + + ), + }, + ], + [], + ); + + if (error) { + return ( + + ); + } + if (loading && rows.length === 0) { + return ; + } + return ( + r.strategy} + empty="No interceptions recorded" + /> + ); +} diff --git a/dashboard-next/src/features/system/components/proxy-table.tsx b/dashboard-next/src/features/system/components/proxy-table.tsx new file mode 100644 index 0000000..b7002f7 --- /dev/null +++ b/dashboard-next/src/features/system/components/proxy-table.tsx @@ -0,0 +1,88 @@ +import type { ColumnDef } from "@tanstack/react-table"; +import { useMemo } from "react"; +import { DataTable } from "@/components/ui/data-table"; +import { ErrorState } from "@/components/ui/error-state"; +import { Skeleton } from "@/components/ui/skeleton"; +import type { ProxyStats } from "@/lib/api-types"; +import { formatCount } from "@/lib/number"; + +interface Row { + handler: string; + reconstructions: number; + avg_ms: number; + errors: number; +} + +interface ProxyTableProps { + stats: ProxyStats | undefined; + loading: boolean; + error: Error | null; + onRetry: () => void; +} + +export function ProxyTable({ stats, loading, error, onRetry }: ProxyTableProps) { + const rows = useMemo(() => { + if (!stats) return []; + return Object.entries(stats) + .map(([handler, v]) => ({ handler, ...v })) + .sort((a, b) => b.reconstructions - a.reconstructions); + }, [stats]); + + const columns = useMemo[]>( + () => [ + { + accessorKey: "handler", + header: "Handler", + cell: ({ getValue }) => ( + {getValue()} + ), + }, + { + accessorKey: "reconstructions", + header: "Reconstructions", + cell: ({ getValue }) => ( + {formatCount(getValue())} + ), + }, + { + accessorKey: "avg_ms", + header: "Avg", + cell: ({ getValue }) => ( + + {getValue().toFixed(1)}ms + + ), + }, + { + accessorKey: "errors", + header: "Errors", + cell: ({ getValue }) => { + const n = getValue(); + return ( + 0 ? "text-danger" : "text-[var(--fg-muted)]"}`}> + {n} + + ); + }, + }, + ], + [], + ); + + if (error) { + return ( + + ); + } + if (loading && rows.length === 0) { + return ; + } + return ( + r.handler} + empty="No proxy reconstructions recorded" + /> + ); +} diff --git a/dashboard-next/src/features/system/hooks.ts b/dashboard-next/src/features/system/hooks.ts new file mode 100644 index 0000000..a0f2e4f --- /dev/null +++ b/dashboard-next/src/features/system/hooks.ts @@ -0,0 +1,21 @@ +import { useQuery } from "@tanstack/react-query"; +import { useRefreshInterval } from "@/providers/refresh-interval-provider"; +import { fetchInterceptionStats, fetchProxyStats } from "./api"; + +export function useProxyStats() { + const { intervalMs } = useRefreshInterval(); + return useQuery({ + queryKey: ["system", "proxy-stats"], + queryFn: ({ signal }) => fetchProxyStats(signal), + refetchInterval: intervalMs, + }); +} + +export function useInterceptionStats() { + const { intervalMs } = useRefreshInterval(); + return useQuery({ + queryKey: ["system", "interception-stats"], + queryFn: ({ signal }) => fetchInterceptionStats(signal), + refetchInterval: intervalMs, + }); +} diff --git a/dashboard-next/src/features/system/index.ts b/dashboard-next/src/features/system/index.ts new file mode 100644 index 0000000..a234113 --- /dev/null +++ b/dashboard-next/src/features/system/index.ts @@ -0,0 +1,2 @@ +export * from "./components"; +export * from "./hooks"; diff --git a/dashboard-next/src/features/workers/api.ts b/dashboard-next/src/features/workers/api.ts new file mode 100644 index 0000000..bbf8810 --- /dev/null +++ b/dashboard-next/src/features/workers/api.ts @@ -0,0 +1,6 @@ +import { api } from "@/lib/api-client"; +import type { Worker } from "@/lib/api-types"; + +export function fetchWorkers(signal?: AbortSignal): Promise { + return api.get("/api/workers", { signal }); +} diff --git a/dashboard-next/src/features/workers/components/index.ts b/dashboard-next/src/features/workers/components/index.ts new file mode 100644 index 0000000..9b5939d --- /dev/null +++ b/dashboard-next/src/features/workers/components/index.ts @@ -0,0 +1 @@ +export { WorkersTable } from "./workers-table"; diff --git a/dashboard-next/src/features/workers/components/workers-table.tsx b/dashboard-next/src/features/workers/components/workers-table.tsx new file mode 100644 index 0000000..00a8b86 --- /dev/null +++ b/dashboard-next/src/features/workers/components/workers-table.tsx @@ -0,0 +1,111 @@ +import type { ColumnDef } from "@tanstack/react-table"; +import { Server } from "lucide-react"; +import { useMemo } from "react"; +import { Badge } from "@/components/ui/badge"; +import { DataTable } from "@/components/ui/data-table"; +import { EmptyState } from "@/components/ui/empty-state"; +import { ErrorState } from "@/components/ui/error-state"; +import { Skeleton } from "@/components/ui/skeleton"; +import type { Worker } from "@/lib/api-types"; +import { formatRelative } from "@/lib/time"; + +const STALE_AFTER_MS = 30_000; + +interface WorkersTableProps { + workers: Worker[] | undefined; + loading: boolean; + error: Error | null; + onRetry: () => void; +} + +export function WorkersTable({ workers, loading, error, onRetry }: WorkersTableProps) { + const columns = useMemo[]>( + () => [ + { + accessorKey: "worker_id", + header: "Worker", + cell: ({ getValue }) => ( + {getValue()} + ), + }, + { + accessorKey: "queues", + header: "Queues", + cell: ({ getValue }) => { + const raw = getValue(); + const parts = raw + .split(",") + .map((s) => s.trim()) + .filter(Boolean); + return ( +
+ {parts.map((q) => ( + + {q} + + ))} +
+ ); + }, + }, + { + accessorKey: "last_heartbeat", + header: "Heartbeat", + cell: ({ getValue }) => { + const ts = getValue(); + const stale = Date.now() - ts * 1000 > STALE_AFTER_MS; + return ( +
+ + {formatRelative(ts * 1000)} +
+ ); + }, + }, + { + accessorKey: "registered_at", + header: "Registered", + cell: ({ getValue }) => ( + + {formatRelative(getValue() * 1000)} + + ), + }, + { + accessorKey: "tags", + header: "Tags", + cell: ({ getValue }) => { + const tags = getValue(); + if (!tags) return ; + return {tags}; + }, + }, + ], + [], + ); + + if (error) { + return ( + + ); + } + + if (loading && !workers) { + return ; + } + + if (!workers || workers.length === 0) { + return ( + + ); + } + + return w.worker_id} />; +} diff --git a/dashboard-next/src/features/workers/hooks.ts b/dashboard-next/src/features/workers/hooks.ts new file mode 100644 index 0000000..4ab8baf --- /dev/null +++ b/dashboard-next/src/features/workers/hooks.ts @@ -0,0 +1,12 @@ +import { useQuery } from "@tanstack/react-query"; +import { useRefreshInterval } from "@/providers/refresh-interval-provider"; +import { fetchWorkers } from "./api"; + +export function useWorkers() { + const { intervalMs } = useRefreshInterval(); + return useQuery({ + queryKey: ["workers"], + queryFn: ({ signal }) => fetchWorkers(signal), + refetchInterval: intervalMs, + }); +} diff --git a/dashboard-next/src/features/workers/index.ts b/dashboard-next/src/features/workers/index.ts new file mode 100644 index 0000000..a234113 --- /dev/null +++ b/dashboard-next/src/features/workers/index.ts @@ -0,0 +1,2 @@ +export * from "./components"; +export * from "./hooks"; diff --git a/dashboard-next/src/routes/circuit-breakers.tsx b/dashboard-next/src/routes/circuit-breakers.tsx index 0183002..14f0fde 100644 --- a/dashboard-next/src/routes/circuit-breakers.tsx +++ b/dashboard-next/src/routes/circuit-breakers.tsx @@ -1,20 +1,26 @@ import { createFileRoute } from "@tanstack/react-router"; -import { CircuitBoard } from "lucide-react"; import { PageHeader } from "@/components/layout"; -import { EmptyState } from "@/components/ui/empty-state"; +import { CircuitBreakersTable, useCircuitBreakers } from "@/features/circuit-breakers"; export const Route = createFileRoute("/circuit-breakers")({ component: CircuitBreakersPage, }); function CircuitBreakersPage() { + const breakers = useCircuitBreakers(); + return ( <> - + breakers.refetch()} + /> ); } diff --git a/dashboard-next/src/routes/resources.tsx b/dashboard-next/src/routes/resources.tsx index cb70f7f..7b19639 100644 --- a/dashboard-next/src/routes/resources.tsx +++ b/dashboard-next/src/routes/resources.tsx @@ -1,17 +1,26 @@ import { createFileRoute } from "@tanstack/react-router"; -import { Activity } from "lucide-react"; import { PageHeader } from "@/components/layout"; -import { EmptyState } from "@/components/ui/empty-state"; +import { ResourcesTable, useResources } from "@/features/resources"; export const Route = createFileRoute("/resources")({ component: ResourcesPage, }); function ResourcesPage() { + const resources = useResources(); + return ( <> - - + + resources.refetch()} + /> ); } diff --git a/dashboard-next/src/routes/system.tsx b/dashboard-next/src/routes/system.tsx index 1823e95..46a0410 100644 --- a/dashboard-next/src/routes/system.tsx +++ b/dashboard-next/src/routes/system.tsx @@ -1,17 +1,48 @@ import { createFileRoute } from "@tanstack/react-router"; -import { Settings2 } from "lucide-react"; import { PageHeader } from "@/components/layout"; -import { EmptyState } from "@/components/ui/empty-state"; +import { + InterceptionTable, + ProxyTable, + useInterceptionStats, + useProxyStats, +} from "@/features/system"; export const Route = createFileRoute("/system")({ component: SystemPage, }); function SystemPage() { + const proxy = useProxyStats(); + const interception = useInterceptionStats(); + return ( <> - - + +
+
+

Proxy handlers

+ proxy.refetch()} + /> +
+
+

+ Interception strategies +

+ interception.refetch()} + /> +
+
); } diff --git a/dashboard-next/src/routes/workers.tsx b/dashboard-next/src/routes/workers.tsx index d1534ff..2cd1a25 100644 --- a/dashboard-next/src/routes/workers.tsx +++ b/dashboard-next/src/routes/workers.tsx @@ -1,17 +1,32 @@ import { createFileRoute } from "@tanstack/react-router"; -import { Server } from "lucide-react"; import { PageHeader } from "@/components/layout"; -import { EmptyState } from "@/components/ui/empty-state"; +import { useWorkers, WorkersTable } from "@/features/workers"; +import { formatCount } from "@/lib/number"; export const Route = createFileRoute("/workers")({ component: WorkersPage, }); function WorkersPage() { + const workers = useWorkers(); + const count = workers.data?.length; + return ( <> - - + + workers.refetch()} + /> ); } From 24667cbb572d0cc1045a5a7ad5d9d27529c5ee4a Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 01:08:22 +0530 Subject: [PATCH 13/42] chore(dashboard-next): add debounced-value hook and widen page header --- dashboard-next/src/components/layout/page-header.tsx | 2 +- dashboard-next/src/hooks/index.ts | 1 + dashboard-next/src/hooks/use-debounced-value.ts | 10 ++++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 dashboard-next/src/hooks/use-debounced-value.ts diff --git a/dashboard-next/src/components/layout/page-header.tsx b/dashboard-next/src/components/layout/page-header.tsx index 387b78b..9867135 100644 --- a/dashboard-next/src/components/layout/page-header.tsx +++ b/dashboard-next/src/components/layout/page-header.tsx @@ -5,7 +5,7 @@ import { Breadcrumbs, type Crumb } from "./breadcrumbs"; interface PageHeaderProps { eyebrow?: string; title: string; - description?: string; + description?: ReactNode; actions?: ReactNode; breadcrumbs?: Crumb[]; className?: string; diff --git a/dashboard-next/src/hooks/index.ts b/dashboard-next/src/hooks/index.ts index 66ebcac..84ccbe6 100644 --- a/dashboard-next/src/hooks/index.ts +++ b/dashboard-next/src/hooks/index.ts @@ -1,2 +1,3 @@ +export { useDebouncedValue } from "./use-debounced-value"; export { useLastRefreshed } from "./use-last-refreshed"; export { useMediaQuery } from "./use-media-query"; diff --git a/dashboard-next/src/hooks/use-debounced-value.ts b/dashboard-next/src/hooks/use-debounced-value.ts new file mode 100644 index 0000000..fbf8290 --- /dev/null +++ b/dashboard-next/src/hooks/use-debounced-value.ts @@ -0,0 +1,10 @@ +import { useEffect, useState } from "react"; + +export function useDebouncedValue(value: T, delay = 300): T { + const [debounced, setDebounced] = useState(value); + useEffect(() => { + const id = setTimeout(() => setDebounced(value), delay); + return () => clearTimeout(id); + }, [value, delay]); + return debounced; +} From fac4dcaec8389d5c91bb161b4ac7e849f974ddf8 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 01:08:29 +0530 Subject: [PATCH 14/42] feat(dashboard-next): jobs api layer with optimistic mutations --- dashboard-next/src/features/jobs/api.ts | 38 +++++ dashboard-next/src/features/jobs/hooks.ts | 171 ++++++++++++++++++++++ dashboard-next/src/features/jobs/types.ts | 21 +++ dashboard-next/src/features/jobs/utils.ts | 97 ++++++++++++ 4 files changed, 327 insertions(+) create mode 100644 dashboard-next/src/features/jobs/api.ts create mode 100644 dashboard-next/src/features/jobs/hooks.ts create mode 100644 dashboard-next/src/features/jobs/types.ts create mode 100644 dashboard-next/src/features/jobs/utils.ts diff --git a/dashboard-next/src/features/jobs/api.ts b/dashboard-next/src/features/jobs/api.ts new file mode 100644 index 0000000..2d3b6ec --- /dev/null +++ b/dashboard-next/src/features/jobs/api.ts @@ -0,0 +1,38 @@ +import { api } from "@/lib/api-client"; +import type { DagData, Job, JobError, ReplayEntry, TaskLog } from "@/lib/api-types"; +import type { JobListQuery } from "./types"; +import { toApiParams } from "./utils"; + +export function fetchJobs(query: JobListQuery, signal?: AbortSignal): Promise { + return api.get("/api/jobs", { signal, params: toApiParams(query) }); +} + +export function fetchJob(id: string, signal?: AbortSignal): Promise { + return api.get(`/api/jobs/${encodeURIComponent(id)}`, { signal }); +} + +export function fetchJobLogs(id: string, signal?: AbortSignal): Promise { + return api.get(`/api/jobs/${encodeURIComponent(id)}/logs`, { signal }); +} + +export function fetchJobErrors(id: string, signal?: AbortSignal): Promise { + return api.get(`/api/jobs/${encodeURIComponent(id)}/errors`, { signal }); +} + +export function fetchReplayHistory(id: string, signal?: AbortSignal): Promise { + return api.get(`/api/jobs/${encodeURIComponent(id)}/replay-history`, { + signal, + }); +} + +export function fetchJobDag(id: string, signal?: AbortSignal): Promise { + return api.get(`/api/jobs/${encodeURIComponent(id)}/dag`, { signal }); +} + +export function cancelJob(id: string): Promise<{ cancelled: boolean }> { + return api.post<{ cancelled: boolean }>(`/api/jobs/${encodeURIComponent(id)}/cancel`); +} + +export function replayJob(id: string): Promise<{ replay_job_id: string }> { + return api.post<{ replay_job_id: string }>(`/api/jobs/${encodeURIComponent(id)}/replay`); +} diff --git a/dashboard-next/src/features/jobs/hooks.ts b/dashboard-next/src/features/jobs/hooks.ts new file mode 100644 index 0000000..e696ea7 --- /dev/null +++ b/dashboard-next/src/features/jobs/hooks.ts @@ -0,0 +1,171 @@ +import { + keepPreviousData, + type Query, + useMutation, + useQuery, + useQueryClient, +} from "@tanstack/react-query"; +import { toast } from "sonner"; +import type { Job } from "@/lib/api-types"; +import { useRefreshInterval } from "@/providers/refresh-interval-provider"; +import { + cancelJob, + fetchJob, + fetchJobDag, + fetchJobErrors, + fetchJobLogs, + fetchJobs, + fetchReplayHistory, + replayJob, +} from "./api"; +import type { JobListQuery } from "./types"; +import { isTerminalStatus } from "./utils"; + +const KEY = { + all: ["jobs"] as const, + list: (q: JobListQuery) => ["jobs", "list", q] as const, + detail: (id: string) => ["jobs", "detail", id] as const, + logs: (id: string) => ["jobs", "detail", id, "logs"] as const, + errors: (id: string) => ["jobs", "detail", id, "errors"] as const, + replays: (id: string) => ["jobs", "detail", id, "replays"] as const, + dag: (id: string) => ["jobs", "detail", id, "dag"] as const, +}; + +/** + * Paginated job list. Uses `keepPreviousData` to avoid flashing empty state + * between pages, and polls at the dashboard-wide refresh cadence. + */ +export function useJobs(query: JobListQuery) { + const { intervalMs } = useRefreshInterval(); + return useQuery({ + queryKey: KEY.list(query), + queryFn: ({ signal }) => fetchJobs(query, signal), + placeholderData: keepPreviousData, + refetchInterval: intervalMs, + }); +} + +/** + * Single job detail. Polling stops once the job reaches a terminal state so + * we don't keep hammering the API for data that won't change again. + */ +export function useJob(id: string, enabled = true) { + const { intervalMs } = useRefreshInterval(); + return useQuery({ + queryKey: KEY.detail(id), + queryFn: ({ signal }) => fetchJob(id, signal), + enabled, + refetchInterval: (query: Query) => { + const data = query.state.data; + if (data && isTerminalStatus(data.status)) return false; + return intervalMs; + }, + }); +} + +export function useJobLogs(id: string, enabled = true) { + const { intervalMs } = useRefreshInterval(); + return useQuery({ + queryKey: KEY.logs(id), + queryFn: ({ signal }) => fetchJobLogs(id, signal), + enabled, + refetchInterval: intervalMs, + }); +} + +export function useJobErrors(id: string, enabled = true) { + const { intervalMs } = useRefreshInterval(); + return useQuery({ + queryKey: KEY.errors(id), + queryFn: ({ signal }) => fetchJobErrors(id, signal), + enabled, + refetchInterval: intervalMs, + }); +} + +export function useReplayHistory(id: string, enabled = true) { + return useQuery({ + queryKey: KEY.replays(id), + queryFn: ({ signal }) => fetchReplayHistory(id, signal), + enabled, + }); +} + +export function useJobDag(id: string, enabled = true) { + return useQuery({ + queryKey: KEY.dag(id), + queryFn: ({ signal }) => fetchJobDag(id, signal), + enabled, + }); +} + +interface MutationContext { + prev: Job | undefined; +} + +/** + * Cancel a pending/running job. + * + * Optimistically flips local status to "cancelled" so the UI feels instant; + * if the request fails we roll back and surface a toast. On settle we + * invalidate every cached list so pagination/stats refresh. + */ +export function useCancelJob() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: (id: string) => cancelJob(id), + onMutate: async (id) => { + await qc.cancelQueries({ queryKey: KEY.detail(id) }); + const prev = qc.getQueryData(KEY.detail(id)); + if (prev) { + qc.setQueryData(KEY.detail(id), { ...prev, status: "cancelled" }); + } + return { prev } satisfies MutationContext; + }, + onError: (error, id, ctx) => { + if (ctx?.prev) qc.setQueryData(KEY.detail(id), ctx.prev); + toast.error("Couldn't cancel job", { + description: error instanceof Error ? error.message : String(error), + }); + }, + onSuccess: (result) => { + if (result.cancelled) { + toast.success("Job cancelled"); + } else { + toast.info("Job wasn't in a cancellable state"); + } + }, + onSettled: (_data, _err, id) => { + qc.invalidateQueries({ queryKey: KEY.detail(id) }); + qc.invalidateQueries({ queryKey: KEY.all }); + qc.invalidateQueries({ queryKey: ["stats"] }); + }, + }); +} + +/** + * Replay a terminal job. Returns the new job ID to the caller so the route + * can navigate to it. + */ +export function useReplayJob() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: (id: string) => replayJob(id), + onError: (error) => { + toast.error("Couldn't replay job", { + description: error instanceof Error ? error.message : String(error), + }); + }, + onSuccess: (result) => { + toast.success("Job re-enqueued", { + description: `New job: ${result.replay_job_id.slice(0, 8)}…`, + }); + }, + onSettled: (_data, _err, id) => { + qc.invalidateQueries({ queryKey: KEY.detail(id) }); + qc.invalidateQueries({ queryKey: KEY.replays(id) }); + qc.invalidateQueries({ queryKey: KEY.all }); + qc.invalidateQueries({ queryKey: ["stats"] }); + }, + }); +} diff --git a/dashboard-next/src/features/jobs/types.ts b/dashboard-next/src/features/jobs/types.ts new file mode 100644 index 0000000..c8ed0fb --- /dev/null +++ b/dashboard-next/src/features/jobs/types.ts @@ -0,0 +1,21 @@ +import type { JobStatus } from "@/lib/api-types"; + +/** + * Filter criteria for listing jobs. Every field is optional and narrowing + * — pagination lives alongside so a single `JobListQuery` round-trips + * through the URL search params. + */ +export interface JobFilters { + status?: JobStatus; + queue?: string; + task?: string; + metadata?: string; + error?: string; + createdAfter?: number; + createdBefore?: number; +} + +export interface JobListQuery extends JobFilters { + page: number; + pageSize: number; +} diff --git a/dashboard-next/src/features/jobs/utils.ts b/dashboard-next/src/features/jobs/utils.ts new file mode 100644 index 0000000..866a44b --- /dev/null +++ b/dashboard-next/src/features/jobs/utils.ts @@ -0,0 +1,97 @@ +import type { JobStatus } from "@/lib/api-types"; +import type { JobFilters, JobListQuery } from "./types"; + +const STATUS_VALUES: readonly JobStatus[] = [ + "pending", + "running", + "complete", + "failed", + "dead", + "cancelled", +] as const; + +const DEFAULT_PAGE_SIZE = 25; + +function asTrimmedString(value: unknown): string | undefined { + if (typeof value !== "string") return undefined; + const trimmed = value.trim(); + return trimmed === "" ? undefined : trimmed; +} + +function asNonNegativeInteger(value: unknown): number | undefined { + const n = Number(value); + if (!Number.isFinite(n)) return undefined; + const int = Math.floor(n); + return int >= 0 ? int : undefined; +} + +function asJobStatus(value: unknown): JobStatus | undefined { + return STATUS_VALUES.find((s) => s === value); +} + +/** + * Parse raw search params (from TanStack Router's `validateSearch`) into a + * strongly-typed query. Missing/invalid fields are dropped silently so deep + * links never crash the route — an over-strict parser would be a footgun. + */ +export function parseJobListSearch(raw: Record): JobListQuery { + return { + status: asJobStatus(raw.status), + queue: asTrimmedString(raw.queue), + task: asTrimmedString(raw.task), + metadata: asTrimmedString(raw.metadata), + error: asTrimmedString(raw.error), + createdAfter: asNonNegativeInteger(raw.createdAfter), + createdBefore: asNonNegativeInteger(raw.createdBefore), + page: asNonNegativeInteger(raw.page) ?? 0, + pageSize: Math.min(Math.max(asNonNegativeInteger(raw.pageSize) ?? DEFAULT_PAGE_SIZE, 10), 200), + }; +} + +/** + * Map a `JobListQuery` to the `/api/jobs` query-string shape understood by + * the Python dashboard handler. Empty fields are omitted so the URL stays + * clean. + */ +export function toApiParams(query: JobListQuery): Record { + const params: Record = { + limit: query.pageSize, + offset: query.page * query.pageSize, + }; + if (query.status) params.status = query.status; + if (query.queue) params.queue = query.queue; + if (query.task) params.task = query.task; + if (query.metadata) params.metadata = query.metadata; + if (query.error) params.error = query.error; + if (query.createdAfter != null) params.created_after = query.createdAfter; + if (query.createdBefore != null) params.created_before = query.createdBefore; + return params; +} + +export function countActiveFilters(filters: JobFilters): number { + let n = 0; + if (filters.status) n++; + if (filters.queue) n++; + if (filters.task) n++; + if (filters.metadata) n++; + if (filters.error) n++; + if (filters.createdAfter != null) n++; + if (filters.createdBefore != null) n++; + return n; +} + +const TERMINAL_STATUSES = new Set(["complete", "failed", "dead", "cancelled"]); + +export function isTerminalStatus(status: JobStatus | undefined): boolean { + if (!status) return false; + return TERMINAL_STATUSES.has(status); +} + +export function canCancel(status: JobStatus | undefined): boolean { + return status === "pending" || status === "running"; +} + +export function canReplay(status: JobStatus | undefined): boolean { + if (!status) return false; + return TERMINAL_STATUSES.has(status); +} From 67241cff226d06750c7a7da59d91fa328ff5fd14 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 01:08:35 +0530 Subject: [PATCH 15/42] feat(dashboard-next): jobs list with url-synced filters and actions --- .../features/jobs/components/job-actions.tsx | 89 ++++++++ .../features/jobs/components/job-filters.tsx | 196 ++++++++++++++++++ .../jobs/components/job-search-bar.tsx | 52 +++++ .../features/jobs/components/job-table.tsx | 121 +++++++++++ dashboard-next/src/routes/jobs/index.tsx | 54 ++++- 5 files changed, 503 insertions(+), 9 deletions(-) create mode 100644 dashboard-next/src/features/jobs/components/job-actions.tsx create mode 100644 dashboard-next/src/features/jobs/components/job-filters.tsx create mode 100644 dashboard-next/src/features/jobs/components/job-search-bar.tsx create mode 100644 dashboard-next/src/features/jobs/components/job-table.tsx diff --git a/dashboard-next/src/features/jobs/components/job-actions.tsx b/dashboard-next/src/features/jobs/components/job-actions.tsx new file mode 100644 index 0000000..767ef6a --- /dev/null +++ b/dashboard-next/src/features/jobs/components/job-actions.tsx @@ -0,0 +1,89 @@ +import { useNavigate } from "@tanstack/react-router"; +import { Ban, Copy, RotateCcw } from "lucide-react"; +import { useState } from "react"; +import { toast } from "sonner"; +import { Button } from "@/components/ui/button"; +import { ConfirmDialog } from "@/components/ui/confirm-dialog"; +import type { Job } from "@/lib/api-types"; +import { useCancelJob, useReplayJob } from "../hooks"; +import { canCancel, canReplay } from "../utils"; + +interface JobActionsProps { + job: Job; +} + +export function JobActions({ job }: JobActionsProps) { + const navigate = useNavigate(); + const cancelMutation = useCancelJob(); + const replayMutation = useReplayJob(); + const [confirmCancel, setConfirmCancel] = useState(false); + const [confirmReplay, setConfirmReplay] = useState(false); + + const onCopyId = async () => { + try { + await navigator.clipboard.writeText(job.id); + toast.success("Job ID copied"); + } catch { + toast.error("Couldn't copy to clipboard"); + } + }; + + const onReplay = async () => { + const result = await replayMutation.mutateAsync(job.id); + if (result.replay_job_id) { + navigate({ to: "/jobs/$id", params: { id: result.replay_job_id } }); + } + }; + + return ( + <> + + {canCancel(job.status) ? ( + + ) : null} + {canReplay(job.status) ? ( + + ) : null} + + { + await cancelMutation.mutateAsync(job.id); + }} + /> + + + + ); +} diff --git a/dashboard-next/src/features/jobs/components/job-filters.tsx b/dashboard-next/src/features/jobs/components/job-filters.tsx new file mode 100644 index 0000000..c7db126 --- /dev/null +++ b/dashboard-next/src/features/jobs/components/job-filters.tsx @@ -0,0 +1,196 @@ +import { X } from "lucide-react"; +import { useEffect, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { useDebouncedValue } from "@/hooks/use-debounced-value"; +import type { JobStatus } from "@/lib/api-types"; +import { cn } from "@/lib/cn"; +import { JOB_STATUS_LABEL } from "@/lib/status"; +import type { JobFilters } from "../types"; +import { countActiveFilters } from "../utils"; + +const STATUS_OPTIONS: JobStatus[] = [ + "pending", + "running", + "complete", + "failed", + "dead", + "cancelled", +]; + +interface JobFiltersBarProps { + filters: JobFilters; + onChange: (filters: JobFilters) => void; + className?: string; +} + +/** + * Debounced filter bar for the jobs list. + * + * Text inputs (queue/task/metadata/error) keep local state and push through + * after a 300ms idle window; the Status select and date range are applied + * immediately since they're deliberate clicks. This keeps the API from + * being hammered while the user is still typing. + */ +export function JobFiltersBar({ filters, onChange, className }: JobFiltersBarProps) { + const [local, setLocal] = useState(() => ({ + queue: filters.queue ?? "", + task: filters.task ?? "", + metadata: filters.metadata ?? "", + error: filters.error ?? "", + })); + + // Reflect external changes (e.g., URL navigation) back into local state. + useEffect(() => { + setLocal({ + queue: filters.queue ?? "", + task: filters.task ?? "", + metadata: filters.metadata ?? "", + error: filters.error ?? "", + }); + }, [filters.queue, filters.task, filters.metadata, filters.error]); + + const debouncedQueue = useDebouncedValue(local.queue, 300); + const debouncedTask = useDebouncedValue(local.task, 300); + const debouncedMetadata = useDebouncedValue(local.metadata, 300); + const debouncedError = useDebouncedValue(local.error, 300); + + useEffect(() => { + const next: JobFilters = { + ...filters, + queue: debouncedQueue || undefined, + task: debouncedTask || undefined, + metadata: debouncedMetadata || undefined, + error: debouncedError || undefined, + }; + if ( + next.queue !== filters.queue || + next.task !== filters.task || + next.metadata !== filters.metadata || + next.error !== filters.error + ) { + onChange(next); + } + // eslint-disable-next-line react-hooks/exhaustive-deps -- intentional: propagate only debounced values + }, [debouncedQueue, debouncedTask, debouncedMetadata, debouncedError]); + + const activeCount = countActiveFilters(filters); + + return ( +
+
+ + setLocal((p) => ({ ...p, queue: e.target.value }))} + placeholder="Queue name" + /> + setLocal((p) => ({ ...p, task: e.target.value }))} + placeholder="Task name" + /> + setLocal((p) => ({ ...p, metadata: e.target.value }))} + placeholder="Metadata contains…" + /> + setLocal((p) => ({ ...p, error: e.target.value }))} + placeholder="Error contains…" + /> +
+ +
+ onChange({ ...filters, createdAfter: ts })} + /> + onChange({ ...filters, createdBefore: ts })} + /> + +
+
+ ); +} + +function DateInput({ + label, + value, + onChange, +}: { + label: string; + value: number | undefined; + onChange: (ts: number | undefined) => void; +}) { + // Backend stores unix seconds; uses "YYYY-MM-DDTHH:mm". + const localValue = value ? unixToLocalDatetime(value) : ""; + return ( + + ); +} + +function unixToLocalDatetime(unixSeconds: number): string { + const date = new Date(unixSeconds * 1000); + const pad = (n: number) => String(n).padStart(2, "0"); + return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`; +} diff --git a/dashboard-next/src/features/jobs/components/job-search-bar.tsx b/dashboard-next/src/features/jobs/components/job-search-bar.tsx new file mode 100644 index 0000000..b4e65f1 --- /dev/null +++ b/dashboard-next/src/features/jobs/components/job-search-bar.tsx @@ -0,0 +1,52 @@ +import { useNavigate } from "@tanstack/react-router"; +import { ArrowRight, Search } from "lucide-react"; +import { type FormEvent, useState } from "react"; +import { Input } from "@/components/ui/input"; +import { cn } from "@/lib/cn"; + +interface JobSearchBarProps { + className?: string; +} + +/** + * Jump-to-job-by-id input. Submits navigate to /jobs/$id. + * Designed to complement the filter bar without cluttering it: paste an ID + * and press Enter. + */ +export function JobSearchBar({ className }: JobSearchBarProps) { + const [id, setId] = useState(""); + const navigate = useNavigate(); + + function handleSubmit(event: FormEvent) { + event.preventDefault(); + const trimmed = id.trim(); + if (!trimmed) return; + navigate({ to: "/jobs/$id", params: { id: trimmed } }); + setId(""); + } + + return ( +
+ + setId(e.target.value)} + placeholder="Jump to job ID…" + aria-label="Jump to job by ID" + className="pl-9 pr-9 font-mono text-xs" + /> + {id ? ( + + ) : null} + + ); +} diff --git a/dashboard-next/src/features/jobs/components/job-table.tsx b/dashboard-next/src/features/jobs/components/job-table.tsx new file mode 100644 index 0000000..cd46b3f --- /dev/null +++ b/dashboard-next/src/features/jobs/components/job-table.tsx @@ -0,0 +1,121 @@ +import { useNavigate } from "@tanstack/react-router"; +import type { ColumnDef } from "@tanstack/react-table"; +import { useMemo } from "react"; +import { Badge } from "@/components/ui/badge"; +import { DataTable } from "@/components/ui/data-table"; +import { EmptyState } from "@/components/ui/empty-state"; +import { ErrorState } from "@/components/ui/error-state"; +import { Skeleton } from "@/components/ui/skeleton"; +import type { Job } from "@/lib/api-types"; +import { JOB_STATUS_LABEL, JOB_STATUS_TONE } from "@/lib/status"; +import { formatRelative } from "@/lib/time"; + +interface JobTableProps { + jobs: Job[] | undefined; + loading: boolean; + error: Error | null; + onRetry: () => void; +} + +export function JobTable({ jobs, loading, error, onRetry }: JobTableProps) { + const navigate = useNavigate(); + + const columns = useMemo[]>( + () => [ + { + accessorKey: "id", + header: "Job", + cell: ({ getValue }) => ( + {getValue().slice(0, 8)}… + ), + }, + { + accessorKey: "task_name", + header: "Task", + cell: ({ getValue }) => ( + {getValue()} + ), + }, + { + accessorKey: "queue", + header: "Queue", + cell: ({ getValue }) => ( + {getValue()} + ), + }, + { + accessorKey: "status", + header: "Status", + cell: ({ row }) => ( + + {JOB_STATUS_LABEL[row.original.status]} + + ), + }, + { + accessorKey: "retry_count", + header: "Retries", + cell: ({ row }) => { + const { retry_count, max_retries } = row.original; + return ( + 0 ? "text-warning" : "text-[var(--fg-muted)]"}`} + > + {retry_count} + {max_retries > 0 ? ` / ${max_retries}` : null} + + ); + }, + }, + { + accessorKey: "created_at", + header: "Created", + cell: ({ getValue }) => ( + + {formatRelative(getValue() * 1000)} + + ), + }, + { + accessorKey: "error", + header: "Error", + cell: ({ getValue }) => { + const err = getValue(); + if (!err) return ; + return ( + + {err} + + ); + }, + }, + ], + [], + ); + + if (error) { + return ; + } + + if (loading && !jobs) { + return ; + } + + if (!jobs || jobs.length === 0) { + return ( + + ); + } + + return ( + j.id} + onRowClick={(job) => navigate({ to: "/jobs/$id", params: { id: job.id } })} + /> + ); +} diff --git a/dashboard-next/src/routes/jobs/index.tsx b/dashboard-next/src/routes/jobs/index.tsx index 113ae2c..abe676b 100644 --- a/dashboard-next/src/routes/jobs/index.tsx +++ b/dashboard-next/src/routes/jobs/index.tsx @@ -1,21 +1,57 @@ import { createFileRoute } from "@tanstack/react-router"; -import { ListTree } from "lucide-react"; import { PageHeader } from "@/components/layout"; -import { EmptyState } from "@/components/ui/empty-state"; +import { Pagination } from "@/components/ui/pagination"; +import { JobFiltersBar, JobSearchBar, JobTable, useJobs } from "@/features/jobs"; +import type { JobFilters, JobListQuery } from "@/features/jobs/types"; +import { parseJobListSearch } from "@/features/jobs/utils"; export const Route = createFileRoute("/jobs/")({ - component: JobsPage, + component: JobsListPage, + validateSearch: (search) => parseJobListSearch(search), }); -function JobsPage() { +function JobsListPage() { + const search = Route.useSearch() as JobListQuery; + const navigate = Route.useNavigate(); + + const updateFilters = (filters: JobFilters) => { + navigate({ + search: (prev) => ({ + ...(prev as JobListQuery), + ...filters, + // Reset to first page whenever filters change + page: 0, + }), + replace: true, + }); + }; + + const setPage = (page: number) => { + navigate({ search: (prev) => ({ ...(prev as JobListQuery), page }) }); + }; + + const jobs = useJobs(search); + const data = jobs.data; + const hasMore = data ? data.length >= search.pageSize : false; + return ( <> - - } /> + +
+ + jobs.refetch()} + /> + +
); } From 9c1da53b3ab372d54fc9b140e7c0f0bbdbca1a96 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 01:08:40 +0530 Subject: [PATCH 16/42] feat(dashboard-next): job detail with tabs and DAG viewer --- .../src/features/jobs/components/index.ts | 9 + .../features/jobs/components/job-dag-tab.tsx | 256 ++++++++++++++++++ .../jobs/components/job-errors-tab.tsx | 56 ++++ .../features/jobs/components/job-logs-tab.tsx | 58 ++++ .../jobs/components/job-overview-tab.tsx | 135 +++++++++ .../jobs/components/job-replay-tab.tsx | 82 ++++++ dashboard-next/src/features/jobs/index.ts | 4 + dashboard-next/src/routes/jobs/$id.tsx | 117 +++++++- 8 files changed, 712 insertions(+), 5 deletions(-) create mode 100644 dashboard-next/src/features/jobs/components/index.ts create mode 100644 dashboard-next/src/features/jobs/components/job-dag-tab.tsx create mode 100644 dashboard-next/src/features/jobs/components/job-errors-tab.tsx create mode 100644 dashboard-next/src/features/jobs/components/job-logs-tab.tsx create mode 100644 dashboard-next/src/features/jobs/components/job-overview-tab.tsx create mode 100644 dashboard-next/src/features/jobs/components/job-replay-tab.tsx create mode 100644 dashboard-next/src/features/jobs/index.ts diff --git a/dashboard-next/src/features/jobs/components/index.ts b/dashboard-next/src/features/jobs/components/index.ts new file mode 100644 index 0000000..13a74d8 --- /dev/null +++ b/dashboard-next/src/features/jobs/components/index.ts @@ -0,0 +1,9 @@ +export { JobActions } from "./job-actions"; +export { JobDagTab } from "./job-dag-tab"; +export { JobErrorsTab } from "./job-errors-tab"; +export { JobFiltersBar } from "./job-filters"; +export { JobLogsTab } from "./job-logs-tab"; +export { JobOverviewTab } from "./job-overview-tab"; +export { JobReplayTab } from "./job-replay-tab"; +export { JobSearchBar } from "./job-search-bar"; +export { JobTable } from "./job-table"; diff --git a/dashboard-next/src/features/jobs/components/job-dag-tab.tsx b/dashboard-next/src/features/jobs/components/job-dag-tab.tsx new file mode 100644 index 0000000..1c5a5c3 --- /dev/null +++ b/dashboard-next/src/features/jobs/components/job-dag-tab.tsx @@ -0,0 +1,256 @@ +import { Link } from "@tanstack/react-router"; +import { Workflow } from "lucide-react"; +import { useMemo } from "react"; +import { EmptyState } from "@/components/ui/empty-state"; +import { ErrorState } from "@/components/ui/error-state"; +import { Skeleton } from "@/components/ui/skeleton"; +import type { DagData, DagEdge, DagNode, JobStatus } from "@/lib/api-types"; +import { JOB_STATUS_LABEL } from "@/lib/status"; + +interface JobDagTabProps { + dag: DagData | undefined; + loading: boolean; + error: Error | null; + onRetry: () => void; +} + +const STATUS_FILL: Record = { + pending: "var(--surface-3)", + running: "var(--color-info-dim)", + complete: "var(--color-success-dim)", + failed: "var(--color-danger-dim)", + dead: "var(--color-danger-dim)", + cancelled: "var(--color-warning-dim)", +}; + +const STATUS_STROKE: Record = { + pending: "var(--border-strong)", + running: "var(--color-info)", + complete: "var(--color-success)", + failed: "var(--color-danger)", + dead: "var(--color-danger)", + cancelled: "var(--color-warning)", +}; + +const NODE_WIDTH = 180; +const NODE_HEIGHT = 58; +const LAYER_GAP_X = 80; +const NODE_GAP_Y = 24; +const PADDING = 32; + +interface LaidOutNode extends DagNode { + x: number; + y: number; + layer: number; +} + +/** + * Lay out DAG nodes in BFS layers from sources (nodes with no inbound edges). + * Each layer's nodes are centered vertically so the graph is readable even + * for lopsided fan-outs. Returns positioned nodes plus canvas dimensions. + */ +function layout(data: DagData): { nodes: LaidOutNode[]; width: number; height: number } { + const nodes = data.nodes; + const edges = data.edges; + if (nodes.length === 0) return { nodes: [], width: 0, height: 0 }; + + const incoming = new Map(); + const outgoing = new Map(); + for (const n of nodes) incoming.set(n.id, 0); + for (const e of edges) { + incoming.set(e.to, (incoming.get(e.to) ?? 0) + 1); + const arr = outgoing.get(e.from); + if (arr) arr.push(e.to); + else outgoing.set(e.from, [e.to]); + } + + const layerOf = new Map(); + const queue: string[] = []; + for (const n of nodes) { + if ((incoming.get(n.id) ?? 0) === 0) { + layerOf.set(n.id, 0); + queue.push(n.id); + } + } + // Handle cycles defensively: any node we haven't ranked sits on layer 0. + if (queue.length === 0 && nodes.length > 0) { + layerOf.set(nodes[0]!.id, 0); + queue.push(nodes[0]!.id); + } + + while (queue.length > 0) { + const id = queue.shift()!; + const depth = layerOf.get(id) ?? 0; + for (const child of outgoing.get(id) ?? []) { + const nextDepth = depth + 1; + if ((layerOf.get(child) ?? -1) < nextDepth) { + layerOf.set(child, nextDepth); + queue.push(child); + } + } + } + + const layers = new Map(); + for (const n of nodes) { + const layer = layerOf.get(n.id) ?? 0; + const bucket = layers.get(layer) ?? []; + bucket.push(n.id); + layers.set(layer, bucket); + } + + const sortedLayers = [...layers.entries()].sort(([a], [b]) => a - b); + const tallestLayerSize = sortedLayers.reduce((max, [, ids]) => Math.max(max, ids.length), 0); + const canvasHeight = + PADDING * 2 + tallestLayerSize * NODE_HEIGHT + (tallestLayerSize - 1) * NODE_GAP_Y; + const canvasWidth = + PADDING * 2 + sortedLayers.length * NODE_WIDTH + (sortedLayers.length - 1) * LAYER_GAP_X; + + const laidOut: LaidOutNode[] = []; + const byId = new Map(nodes.map((n) => [n.id, n])); + for (const [layer, ids] of sortedLayers) { + const layerHeight = ids.length * NODE_HEIGHT + (ids.length - 1) * NODE_GAP_Y; + const startY = (canvasHeight - layerHeight) / 2; + ids.forEach((id, i) => { + const base = byId.get(id); + if (!base) return; + laidOut.push({ + ...base, + layer, + x: PADDING + layer * (NODE_WIDTH + LAYER_GAP_X), + y: startY + i * (NODE_HEIGHT + NODE_GAP_Y), + }); + }); + } + + return { nodes: laidOut, width: canvasWidth, height: canvasHeight }; +} + +export function JobDagTab({ dag, loading, error, onRetry }: JobDagTabProps) { + const layoutResult = useMemo( + () => (dag ? layout(dag) : { nodes: [], width: 0, height: 0 }), + [dag], + ); + + if (error) { + return ; + } + + if (loading && !dag) return ; + + if (!dag || dag.nodes.length === 0) { + return ( + + ); + } + + const positions = new Map(layoutResult.nodes.map((n) => [n.id, n])); + + return ( +
+ + + + + + + + {dag.edges.map((edge) => ( + + ))} + + + {layoutResult.nodes.map((node) => ( + + ))} + + +
+ ); +} + +function Edge({ edge, positions }: { edge: DagEdge; positions: Map }) { + const from = positions.get(edge.from); + const to = positions.get(edge.to); + if (!from || !to) return null; + + const x1 = from.x + NODE_WIDTH; + const y1 = from.y + NODE_HEIGHT / 2; + const x2 = to.x; + const y2 = to.y + NODE_HEIGHT / 2; + const mid = (x1 + x2) / 2; + const d = `M ${x1},${y1} C ${mid},${y1} ${mid},${y2} ${x2},${y2}`; + return ( + + ); +} + +function Node({ node }: { node: LaidOutNode }) { + return ( + + + + + {truncate(node.task_name, 22)} + + + {node.id.slice(0, 8)}… + + + {JOB_STATUS_LABEL[node.status].toUpperCase()} + + + + ); +} + +function truncate(value: string, max: number): string { + return value.length <= max ? value : `${value.slice(0, max - 1)}…`; +} diff --git a/dashboard-next/src/features/jobs/components/job-errors-tab.tsx b/dashboard-next/src/features/jobs/components/job-errors-tab.tsx new file mode 100644 index 0000000..342ef3a --- /dev/null +++ b/dashboard-next/src/features/jobs/components/job-errors-tab.tsx @@ -0,0 +1,56 @@ +import { AlertOctagon } from "lucide-react"; +import { EmptyState } from "@/components/ui/empty-state"; +import { ErrorState } from "@/components/ui/error-state"; +import { Skeleton } from "@/components/ui/skeleton"; +import type { JobError } from "@/lib/api-types"; +import { formatAbsolute } from "@/lib/time"; + +interface JobErrorsTabProps { + errors: JobError[] | undefined; + loading: boolean; + error: Error | null; + onRetry: () => void; +} + +export function JobErrorsTab({ errors, loading, error, onRetry }: JobErrorsTabProps) { + if (error) { + return ( + + ); + } + + if (loading && !errors) { + return ; + } + + if (!errors || errors.length === 0) { + return ( + + ); + } + + return ( +
+ {errors.map((err) => ( +
+
+ Attempt {err.attempt} + + {formatAbsolute(err.failed_at * 1000)} + +
+
+            {err.error}
+          
+
+ ))} +
+ ); +} diff --git a/dashboard-next/src/features/jobs/components/job-logs-tab.tsx b/dashboard-next/src/features/jobs/components/job-logs-tab.tsx new file mode 100644 index 0000000..0637357 --- /dev/null +++ b/dashboard-next/src/features/jobs/components/job-logs-tab.tsx @@ -0,0 +1,58 @@ +import { ScrollText } from "lucide-react"; +import { EmptyState } from "@/components/ui/empty-state"; +import { ErrorState } from "@/components/ui/error-state"; +import { Skeleton } from "@/components/ui/skeleton"; +import type { TaskLog } from "@/lib/api-types"; +import { cn } from "@/lib/cn"; +import { formatAbsolute } from "@/lib/time"; + +interface JobLogsTabProps { + logs: TaskLog[] | undefined; + loading: boolean; + error: Error | null; + onRetry: () => void; +} + +const LEVEL_STYLE: Record = { + error: "text-danger", + warning: "text-warning", + warn: "text-warning", + info: "text-info", + debug: "text-[var(--fg-subtle)]", +}; + +export function JobLogsTab({ logs, loading, error, onRetry }: JobLogsTabProps) { + if (error) { + return ; + } + + if (loading && !logs) { + return ; + } + + if (!logs || logs.length === 0) { + return ; + } + + return ( +
+
    + {logs.map((log, i) => { + const levelClass = LEVEL_STYLE[log.level.toLowerCase()] ?? "text-[var(--fg-muted)]"; + const key = `${log.logged_at}-${log.level}-${i}`; + return ( +
  • + + {formatAbsolute(log.logged_at * 1000)} + + {log.level} + + {log.message} + +
  • + ); + })} +
+
+ ); +} diff --git a/dashboard-next/src/features/jobs/components/job-overview-tab.tsx b/dashboard-next/src/features/jobs/components/job-overview-tab.tsx new file mode 100644 index 0000000..43f3b06 --- /dev/null +++ b/dashboard-next/src/features/jobs/components/job-overview-tab.tsx @@ -0,0 +1,135 @@ +import type { ReactNode } from "react"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import type { Job } from "@/lib/api-types"; +import { formatAbsolute, formatDuration, formatRelative } from "@/lib/time"; + +interface JobOverviewTabProps { + job: Job; +} + +export function JobOverviewTab({ job }: JobOverviewTabProps) { + const startedToCompleted = + job.started_at && job.completed_at ? job.completed_at - job.started_at : null; + + return ( +
+ + + Identity + + +
+ {job.task_name} + {job.queue} + {job.priority} + {job.unique_key ? {job.unique_key} : null} +
+
+
+ + + + Execution + + +
+ + {job.retry_count} + {job.max_retries > 0 ? ` / ${job.max_retries}` : null} + + {job.progress != null ? ( + {(job.progress * 100).toFixed(0)}% + ) : null} + {formatDuration(job.timeout_ms)} + {startedToCompleted != null ? ( + {formatDuration(startedToCompleted * 1000)} + ) : null} +
+
+
+ + + + Timeline + + +
+ + + + + + + {job.started_at ? ( + + + + ) : null} + {job.completed_at ? ( + + + + ) : null} +
+
+
+ + {job.metadata ? ( + + + Metadata + + +
+              {tryPrettyJson(job.metadata)}
+            
+
+
+ ) : null} + + {job.error ? ( + + + Last error + + +
+              {job.error}
+            
+
+
+ ) : null} +
+ ); +} + +function Dl({ children }: { children: ReactNode }) { + return
{children}
; +} + +function Row({ label, children }: { label: string; children: ReactNode }) { + return ( + <> +
{label}
+
{children}
+ + ); +} + +function Timestamp({ unix }: { unix: number }) { + const ms = unix * 1000; + return ( + + {formatAbsolute(ms)} + {formatRelative(ms)} + + ); +} + +function tryPrettyJson(raw: string): string { + try { + return JSON.stringify(JSON.parse(raw), null, 2); + } catch { + return raw; + } +} diff --git a/dashboard-next/src/features/jobs/components/job-replay-tab.tsx b/dashboard-next/src/features/jobs/components/job-replay-tab.tsx new file mode 100644 index 0000000..76063e0 --- /dev/null +++ b/dashboard-next/src/features/jobs/components/job-replay-tab.tsx @@ -0,0 +1,82 @@ +import { Link } from "@tanstack/react-router"; +import { RotateCcw } from "lucide-react"; +import { EmptyState } from "@/components/ui/empty-state"; +import { ErrorState } from "@/components/ui/error-state"; +import { Skeleton } from "@/components/ui/skeleton"; +import type { ReplayEntry } from "@/lib/api-types"; +import { formatAbsolute, formatRelative } from "@/lib/time"; + +interface JobReplayTabProps { + replays: ReplayEntry[] | undefined; + loading: boolean; + error: Error | null; + onRetry: () => void; +} + +export function JobReplayTab({ replays, loading, error, onRetry }: JobReplayTabProps) { + if (error) { + return ( + + ); + } + if (loading && !replays) return ; + + if (!replays || replays.length === 0) { + return ( + + ); + } + + return ( +
    + {replays.map((entry) => ( +
  • +
    + + {entry.replay_job_id} + + + {formatAbsolute(entry.replayed_at * 1000)} ·{" "} + {formatRelative(entry.replayed_at * 1000)} + +
    + {entry.original_error ? ( +
    +
    + Original error +
    +
    +                {entry.original_error}
    +              
    +
    + ) : null} + {entry.replay_error ? ( +
    +
    + Replay error +
    +
    +                {entry.replay_error}
    +              
    +
    + ) : null} +
  • + ))} +
+ ); +} diff --git a/dashboard-next/src/features/jobs/index.ts b/dashboard-next/src/features/jobs/index.ts new file mode 100644 index 0000000..a230ecf --- /dev/null +++ b/dashboard-next/src/features/jobs/index.ts @@ -0,0 +1,4 @@ +export * from "./components"; +export * from "./hooks"; +export type { JobFilters, JobListQuery } from "./types"; +export { canCancel, canReplay, isTerminalStatus } from "./utils"; diff --git a/dashboard-next/src/routes/jobs/$id.tsx b/dashboard-next/src/routes/jobs/$id.tsx index 4656eaf..7d22b26 100644 --- a/dashboard-next/src/routes/jobs/$id.tsx +++ b/dashboard-next/src/routes/jobs/$id.tsx @@ -1,6 +1,23 @@ import { createFileRoute } from "@tanstack/react-router"; import { PageHeader } from "@/components/layout"; -import { EmptyState } from "@/components/ui/empty-state"; +import { Badge } from "@/components/ui/badge"; +import { ErrorState } from "@/components/ui/error-state"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { + JobActions, + JobDagTab, + JobErrorsTab, + JobLogsTab, + JobOverviewTab, + JobReplayTab, + useJob, + useJobDag, + useJobErrors, + useJobLogs, + useReplayHistory, +} from "@/features/jobs"; +import { JOB_STATUS_LABEL, JOB_STATUS_TONE } from "@/lib/status"; export const Route = createFileRoute("/jobs/$id")({ component: JobDetailPage, @@ -8,13 +25,103 @@ export const Route = createFileRoute("/jobs/$id")({ function JobDetailPage() { const { id } = Route.useParams(); + const job = useJob(id); + const logs = useJobLogs(id); + const errors = useJobErrors(id); + const replays = useReplayHistory(id); + const dag = useJobDag(id); + + if (job.isLoading && !job.data) { + return ( + <> + + + + ); + } + + if (job.error || !job.data) { + return ( + <> + + job.refetch()} + /> + + ); + } + + const data = job.data; + const statusTone = JOB_STATUS_TONE[data.status]; + return ( <> - - + {data.id} + + } + breadcrumbs={[{ label: "Jobs", to: "/jobs" }, { label: data.id.slice(0, 8) }]} + actions={ +
+ {JOB_STATUS_LABEL[data.status]} + +
+ } /> + + + + Overview + Logs + Errors + Replays + DAG + + + + + + + logs.refetch()} + /> + + + errors.refetch()} + /> + + + replays.refetch()} + /> + + + dag.refetch()} + /> + + ); } From a0bfc042712d7728521d7f0e4c799ce153ca0ee9 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 01:27:55 +0530 Subject: [PATCH 17/42] chore(dashboard-next): add recharts, react-virtual, vitest --- dashboard-next/package.json | 10 +- dashboard-next/pnpm-lock.yaml | 669 ++++++++++++++++++++++++++++++++ dashboard-next/vitest.config.ts | 17 + 3 files changed, 694 insertions(+), 2 deletions(-) create mode 100644 dashboard-next/vitest.config.ts diff --git a/dashboard-next/package.json b/dashboard-next/package.json index 29fc195..5c893d3 100644 --- a/dashboard-next/package.json +++ b/dashboard-next/package.json @@ -12,7 +12,9 @@ "lint:fix": "biome check --fix src/", "format": "biome format --write src/", "format:check": "biome format src/", - "ci": "biome ci src/ && tsc --noEmit && vite build" + "test": "vitest run", + "test:watch": "vitest", + "ci": "biome ci src/ && tsc --noEmit && vitest run && vite build" }, "dependencies": { "@radix-ui/react-dialog": "^1.1.15", @@ -26,6 +28,7 @@ "@tanstack/react-query-devtools": "^5.62.7", "@tanstack/react-router": "^1.95.1", "@tanstack/react-table": "^8.21.3", + "@tanstack/react-virtual": "^3.13.24", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", @@ -33,6 +36,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-error-boundary": "^6.1.1", + "recharts": "^3.8.1", "sonner": "^1.7.1", "tailwind-merge": "^2.6.0" }, @@ -44,9 +48,11 @@ "@types/react": "^18.3.18", "@types/react-dom": "^18.3.5", "@vitejs/plugin-react": "^4.3.4", + "@vitest/coverage-v8": "^4.1.5", "tailwindcss": "^4.0.0", "typescript": "^5.7.0", - "vite": "^6.4.2" + "vite": "^6.4.2", + "vitest": "^4.1.5" }, "packageManager": "pnpm@10.30.3" } diff --git a/dashboard-next/pnpm-lock.yaml b/dashboard-next/pnpm-lock.yaml index 6557e9b..7eeca12 100644 --- a/dashboard-next/pnpm-lock.yaml +++ b/dashboard-next/pnpm-lock.yaml @@ -41,6 +41,9 @@ importers: '@tanstack/react-table': specifier: ^8.21.3 version: 8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tanstack/react-virtual': + specifier: ^3.13.24 + version: 3.13.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -62,6 +65,9 @@ importers: react-error-boundary: specifier: ^6.1.1 version: 6.1.1(react@18.3.1) + recharts: + specifier: ^3.8.1 + version: 3.8.1(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react-is@19.2.5)(react@18.3.1)(redux@5.0.1) sonner: specifier: ^1.7.1 version: 1.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -90,6 +96,9 @@ importers: '@vitejs/plugin-react': specifier: ^4.3.4 version: 4.7.0(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)) + '@vitest/coverage-v8': + specifier: ^4.1.5 + version: 4.1.5(vitest@4.1.5) tailwindcss: specifier: ^4.0.0 version: 4.2.4 @@ -99,6 +108,9 @@ importers: vite: specifier: ^6.4.2 version: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0) + vitest: + specifier: ^4.1.5 + version: 4.1.5(@types/node@22.19.17)(@vitest/coverage-v8@4.1.5)(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)) packages: @@ -197,6 +209,10 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + '@biomejs/biome@2.4.13': resolution: {integrity: sha512-gLXOwkOBBg0tr7bDsqlkIh4uFeKuMjxvqsrb1Tukww1iDmHcfr4Uu8MoQxp0Rcte+69+osRNWXwHsu/zxT6XqA==} engines: {node: '>=14.21.3'} @@ -975,6 +991,17 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@reduxjs/toolkit@2.11.2': + resolution: {integrity: sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==} + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -1116,6 +1143,12 @@ packages: cpu: [x64] os: [win32] + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + '@tailwindcss/node@4.2.4': resolution: {integrity: sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==} @@ -1251,6 +1284,12 @@ packages: react: '>=16.8' react-dom: '>=16.8' + '@tanstack/react-virtual@3.13.24': + resolution: {integrity: sha512-aIJvz5OSkhNIhZIpYivrxrPTKYsjW9Uzy+sP/mx0S3sev2HyvPb7xmjbYvokzEpfgYHy/HjzJ2zFAETuUfgCpg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@tanstack/router-core@1.168.15': resolution: {integrity: sha512-Wr0424NDtD8fT/uALobMZ9DdcfsTyXtW5IPR++7zvW8/7RaIOeaqXpVDId8ywaGtqPWLWOfaUg2zUtYtukoXYA==} engines: {node: '>=20.19'} @@ -1293,6 +1332,9 @@ packages: resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} engines: {node: '>=12'} + '@tanstack/virtual-core@3.14.0': + resolution: {integrity: sha512-JLANqGy/D6k4Ujmh8Tr25lGimuOXNiaVyXaCAZS0W+1390sADdGnyUdSWNIfd49gebtIxGMij4IktRVzrdr12Q==} + '@tanstack/virtual-file-routes@1.161.7': resolution: {integrity: sha512-olW33+Cn+bsCsZKPwEGhlkqS6w3M2slFv11JIobdnCFKMLG97oAI2kWKdx5/zsywTL8flpnoIgaZZPlQTFYhdQ==} engines: {node: '>=20.19'} @@ -1310,6 +1352,39 @@ packages: '@types/babel__traverse@7.28.0': resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -1327,12 +1402,53 @@ packages: '@types/react@18.3.28': resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==} + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + '@vitejs/plugin-react@4.7.0': resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@vitest/coverage-v8@4.1.5': + resolution: {integrity: sha512-38C0/Ddb7HcRG0Z4/DUem8x57d2p9jYgp18mkaYswEOQBGsI1CG4f/hjm0ZCeaJfWhSZ4k7jgs29V1Zom7Ki9A==} + peerDependencies: + '@vitest/browser': 4.1.5 + vitest: 4.1.5 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@4.1.5': + resolution: {integrity: sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==} + + '@vitest/mocker@4.1.5': + resolution: {integrity: sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.5': + resolution: {integrity: sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==} + + '@vitest/runner@4.1.5': + resolution: {integrity: sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==} + + '@vitest/snapshot@4.1.5': + resolution: {integrity: sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==} + + '@vitest/spy@4.1.5': + resolution: {integrity: sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==} + + '@vitest/utils@4.1.5': + resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==} + acorn@8.16.0: resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} engines: {node: '>=0.4.0'} @@ -1350,6 +1466,13 @@ packages: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} engines: {node: '>=10'} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-v8-to-istanbul@1.0.0: + resolution: {integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==} + babel-dead-code-elimination@1.0.12: resolution: {integrity: sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==} @@ -1374,6 +1497,10 @@ packages: caniuse-lite@1.0.30001790: resolution: {integrity: sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -1400,6 +1527,50 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1409,6 +1580,9 @@ packages: supports-color: optional: true + decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -1427,6 +1601,12 @@ packages: resolution: {integrity: sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==} engines: {node: '>=10.13.0'} + es-module-lexer@2.0.0: + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + + es-toolkit@1.46.0: + resolution: {integrity: sha512-IToJ6ct9OLl5zz6WsC/1vZEwfSZ7Myil+ygl5Tf30Xjn9AEkzNB4kqp2G7VUJKF1DtTx/ra5M5KLlXvzOg51BA==} + esbuild@0.25.12: resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} @@ -1441,6 +1621,16 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -1477,6 +1667,23 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + immer@10.2.0: + resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==} + + immer@11.1.4: + resolution: {integrity: sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -1497,10 +1704,25 @@ packages: resolution: {integrity: sha512-obH0yYahGXdzNxo+djmHhBYThUKDkz565cxkIlt2L9hXfv1NlaLKoDBHo6KxXsYrIXx2RK3x5vY36CfZcobxEw==} engines: {node: '>=18'} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + jiti@2.6.1: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1603,6 +1825,13 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magicast@0.5.2: + resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1618,6 +1847,9 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -1651,6 +1883,21 @@ packages: peerDependencies: react: ^18.0.0 || ^19.0.0 + react-is@19.2.5: + resolution: {integrity: sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ==} + + react-redux@9.2.0: + resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==} + peerDependencies: + '@types/react': ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + redux: + optional: true + react-refresh@0.17.0: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} @@ -1693,6 +1940,25 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + recharts@3.8.1: + resolution: {integrity: sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==} + engines: {node: '>=18'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + redux-thunk@3.1.0: + resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} + peerDependencies: + redux: ^5.0.0 + + redux@5.0.1: + resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} + + reselect@5.1.1: + resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -1708,6 +1974,11 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + seroval-plugins@1.5.2: resolution: {integrity: sha512-qpY0Cl+fKYFn4GOf3cMiq6l72CpuVaawb6ILjubOQ+diJ54LfOWaSSPsaswN8DRPIPW4Yq+tE1k5aKd7ILyaFg==} engines: {node: '>=10'} @@ -1718,6 +1989,9 @@ packages: resolution: {integrity: sha512-xcRN39BdsnO9Tf+VzsE7b3JyTJASItIV1FVFewJKCFcW4s4haIKS3e6vj8PGB9qBwC7tnuOywQMdv5N4qkzi7Q==} engines: {node: '>=10'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + sonner@1.7.4: resolution: {integrity: sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw==} peerDependencies: @@ -1728,6 +2002,16 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + tailwind-merge@2.6.1: resolution: {integrity: sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==} @@ -1738,10 +2022,24 @@ packages: resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.1.1: + resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} + engines: {node: '>=18'} + tinyglobby@0.2.16: resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -1797,6 +2095,9 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + victory-vendor@37.3.6: + resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} + vite@6.4.2: resolution: {integrity: sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -1837,9 +2138,55 @@ packages: yaml: optional: true + vitest@4.1.5: + resolution: {integrity: sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.5 + '@vitest/browser-preview': 4.1.5 + '@vitest/browser-webdriverio': 4.1.5 + '@vitest/coverage-istanbul': 4.1.5 + '@vitest/coverage-v8': 4.1.5 + '@vitest/ui': 4.1.5 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + webpack-virtual-modules@0.6.2: resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -1970,6 +2317,8 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@bcoe/v8-coverage@1.0.2': {} + '@biomejs/biome@2.4.13': optionalDependencies: '@biomejs/cli-darwin-arm64': 2.4.13 @@ -2574,6 +2923,18 @@ snapshots: '@radix-ui/rect@1.1.1': {} + '@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1))(react@18.3.1)': + dependencies: + '@standard-schema/spec': 1.1.0 + '@standard-schema/utils': 0.3.0 + immer: 11.1.4 + redux: 5.0.1 + redux-thunk: 3.1.0(redux@5.0.1) + reselect: 5.1.1 + optionalDependencies: + react: 18.3.1 + react-redux: 9.2.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1) + '@rolldown/pluginutils@1.0.0-beta.27': {} '@rollup/rollup-android-arm-eabi@4.60.2': @@ -2651,6 +3012,10 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.60.2': optional: true + '@standard-schema/spec@1.1.0': {} + + '@standard-schema/utils@0.3.0': {} + '@tailwindcss/node@4.2.4': dependencies: '@jridgewell/remapping': 2.3.5 @@ -2758,6 +3123,12 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + '@tanstack/react-virtual@3.13.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/virtual-core': 3.14.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + '@tanstack/router-core@1.168.15': dependencies: '@tanstack/history': 1.161.6 @@ -2817,6 +3188,8 @@ snapshots: '@tanstack/table-core@8.21.3': {} + '@tanstack/virtual-core@3.14.0': {} + '@tanstack/virtual-file-routes@1.161.7': {} '@types/babel__core@7.20.5': @@ -2840,6 +3213,37 @@ snapshots: dependencies: '@babel/types': 7.29.0 + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/d3-array@3.2.2': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/deep-eql@4.0.2': {} + '@types/estree@1.0.8': {} '@types/node@22.19.17': @@ -2857,6 +3261,8 @@ snapshots: '@types/prop-types': 15.7.15 csstype: 3.2.3 + '@types/use-sync-external-store@0.0.6': {} + '@vitejs/plugin-react@4.7.0(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))': dependencies: '@babel/core': 7.29.0 @@ -2869,6 +3275,61 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitest/coverage-v8@4.1.5(vitest@4.1.5)': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.1.5 + ast-v8-to-istanbul: 1.0.0 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + magicast: 0.5.2 + obug: 2.1.1 + std-env: 4.1.0 + tinyrainbow: 3.1.0 + vitest: 4.1.5(@types/node@22.19.17)(@vitest/coverage-v8@4.1.5)(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)) + + '@vitest/expect@4.1.5': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.5 + '@vitest/utils': 4.1.5 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.5(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))': + dependencies: + '@vitest/spy': 4.1.5 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0) + + '@vitest/pretty-format@4.1.5': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.5': + dependencies: + '@vitest/utils': 4.1.5 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.5': + dependencies: + '@vitest/pretty-format': 4.1.5 + '@vitest/utils': 4.1.5 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.5': {} + + '@vitest/utils@4.1.5': + dependencies: + '@vitest/pretty-format': 4.1.5 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + acorn@8.16.0: {} ansis@4.2.0: {} @@ -2882,6 +3343,14 @@ snapshots: dependencies: tslib: 2.8.1 + assertion-error@2.0.1: {} + + ast-v8-to-istanbul@1.0.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + babel-dead-code-elimination@1.0.12: dependencies: '@babel/core': 7.29.0 @@ -2909,6 +3378,8 @@ snapshots: caniuse-lite@1.0.30001790: {} + chai@6.2.2: {} + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -2945,10 +3416,50 @@ snapshots: csstype@3.2.3: {} + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-color@3.1.0: {} + + d3-ease@3.0.1: {} + + d3-format@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@3.1.0: {} + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + debug@4.4.3: dependencies: ms: 2.1.3 + decimal.js-light@2.5.1: {} + detect-libc@2.1.2: {} detect-node-es@1.1.0: {} @@ -2962,6 +3473,10 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.3 + es-module-lexer@2.0.0: {} + + es-toolkit@1.46.0: {} + esbuild@0.25.12: optionalDependencies: '@esbuild/aix-ppc64': 0.25.12 @@ -3022,6 +3537,14 @@ snapshots: escalade@3.2.0: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + eventemitter3@5.0.4: {} + + expect-type@1.3.0: {} + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: picomatch: 4.0.4 @@ -3047,6 +3570,16 @@ snapshots: graceful-fs@4.2.11: {} + has-flag@4.0.0: {} + + html-escaper@2.0.2: {} + + immer@10.2.0: {} + + immer@11.1.4: {} + + internmap@2.0.3: {} + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 @@ -3061,8 +3594,23 @@ snapshots: isbot@5.1.39: {} + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + jiti@2.6.1: {} + js-tokens@10.0.0: {} + js-tokens@4.0.0: {} jsesc@3.1.0: {} @@ -3134,6 +3682,16 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + magicast@0.5.2: + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.7.4 + ms@2.1.3: {} nanoid@3.3.11: {} @@ -3142,6 +3700,8 @@ snapshots: normalize-path@3.0.0: {} + obug@2.1.1: {} + pathe@2.0.3: {} picocolors@1.1.1: {} @@ -3168,6 +3728,17 @@ snapshots: dependencies: react: 18.3.1 + react-is@19.2.5: {} + + react-redux@9.2.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1): + dependencies: + '@types/use-sync-external-store': 0.0.6 + react: 18.3.1 + use-sync-external-store: 1.6.0(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + redux: 5.0.1 + react-refresh@0.17.0: {} react-remove-scroll-bar@2.3.8(@types/react@18.3.28)(react@18.3.1): @@ -3205,6 +3776,34 @@ snapshots: dependencies: picomatch: 2.3.2 + recharts@3.8.1(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react-is@19.2.5)(react@18.3.1)(redux@5.0.1): + dependencies: + '@reduxjs/toolkit': 2.11.2(react-redux@9.2.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1))(react@18.3.1) + clsx: 2.1.1 + decimal.js-light: 2.5.1 + es-toolkit: 1.46.0 + eventemitter3: 5.0.4 + immer: 10.2.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-is: 19.2.5 + react-redux: 9.2.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1) + reselect: 5.1.1 + tiny-invariant: 1.3.3 + use-sync-external-store: 1.6.0(react@18.3.1) + victory-vendor: 37.3.6 + transitivePeerDependencies: + - '@types/react' + - redux + + redux-thunk@3.1.0(redux@5.0.1): + dependencies: + redux: 5.0.1 + + redux@5.0.1: {} + + reselect@5.1.1: {} + resolve-pkg-maps@1.0.0: {} rollup@4.60.2: @@ -3244,12 +3843,16 @@ snapshots: semver@6.3.1: {} + semver@7.7.4: {} + seroval-plugins@1.5.2(seroval@1.5.2): dependencies: seroval: 1.5.2 seroval@1.5.2: {} + siginfo@2.0.0: {} + sonner@1.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 @@ -3257,17 +3860,33 @@ snapshots: source-map-js@1.2.1: {} + stackback@0.0.2: {} + + std-env@4.1.0: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + tailwind-merge@2.6.1: {} tailwindcss@4.2.4: {} tapable@2.3.3: {} + tiny-invariant@1.3.3: {} + + tinybench@2.9.0: {} + + tinyexec@1.1.1: {} + tinyglobby@0.2.16: dependencies: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinyrainbow@3.1.0: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -3317,6 +3936,23 @@ snapshots: dependencies: react: 18.3.1 + victory-vendor@37.3.6: + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-ease': 3.0.2 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0): dependencies: esbuild: 0.25.12 @@ -3332,8 +3968,41 @@ snapshots: lightningcss: 1.32.0 tsx: 4.21.0 + vitest@4.1.5(@types/node@22.19.17)(@vitest/coverage-v8@4.1.5)(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)): + dependencies: + '@vitest/expect': 4.1.5 + '@vitest/mocker': 4.1.5(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)) + '@vitest/pretty-format': 4.1.5 + '@vitest/runner': 4.1.5 + '@vitest/snapshot': 4.1.5 + '@vitest/spy': 4.1.5 + '@vitest/utils': 4.1.5 + es-module-lexer: 2.0.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.1.1 + tinyglobby: 0.2.16 + tinyrainbow: 3.1.0 + vite: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.19.17 + '@vitest/coverage-v8': 4.1.5(vitest@4.1.5) + transitivePeerDependencies: + - msw + webpack-virtual-modules@0.6.2: {} + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + yallist@3.1.1: {} zod@3.25.76: {} diff --git a/dashboard-next/vitest.config.ts b/dashboard-next/vitest.config.ts new file mode 100644 index 0000000..d58df91 --- /dev/null +++ b/dashboard-next/vitest.config.ts @@ -0,0 +1,17 @@ +import path from "node:path"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + }, + }, + test: { + environment: "node", + include: ["src/**/*.test.ts", "src/**/*.test.tsx"], + coverage: { + reporter: ["text", "json"], + }, + }, +}); From 99cab14b984491f47988b32f27f1ef710296f90b Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 01:28:00 +0530 Subject: [PATCH 18/42] feat(dashboard-next): queues with pause/resume mutations --- dashboard-next/src/features/overview/api.ts | 10 +- dashboard-next/src/features/overview/hooks.ts | 30 +--- dashboard-next/src/features/queues/api.ts | 18 ++ .../src/features/queues/components/index.ts | 1 + .../queues/components/queues-table.tsx | 161 ++++++++++++++++++ dashboard-next/src/features/queues/hooks.ts | 87 ++++++++++ dashboard-next/src/features/queues/index.ts | 2 + dashboard-next/src/routes/queues.tsx | 22 ++- 8 files changed, 293 insertions(+), 38 deletions(-) create mode 100644 dashboard-next/src/features/queues/api.ts create mode 100644 dashboard-next/src/features/queues/components/index.ts create mode 100644 dashboard-next/src/features/queues/components/queues-table.tsx create mode 100644 dashboard-next/src/features/queues/hooks.ts create mode 100644 dashboard-next/src/features/queues/index.ts diff --git a/dashboard-next/src/features/overview/api.ts b/dashboard-next/src/features/overview/api.ts index e1adbe2..f12e9e1 100644 --- a/dashboard-next/src/features/overview/api.ts +++ b/dashboard-next/src/features/overview/api.ts @@ -1,18 +1,10 @@ import { api } from "@/lib/api-client"; -import type { Job, QueueStats, QueueStatsMap, TimeseriesBucket } from "@/lib/api-types"; +import type { Job, QueueStats, TimeseriesBucket } from "@/lib/api-types"; export function fetchStats(signal?: AbortSignal): Promise { return api.get("/api/stats", { signal }); } -export function fetchQueueStats(signal?: AbortSignal): Promise { - return api.get("/api/stats/queues", { signal }); -} - -export function fetchPausedQueues(signal?: AbortSignal): Promise { - return api.get("/api/queues/paused", { signal }); -} - export function fetchRecentJobs(limit: number, signal?: AbortSignal): Promise { return api.get("/api/jobs", { signal, params: { limit } }); } diff --git a/dashboard-next/src/features/overview/hooks.ts b/dashboard-next/src/features/overview/hooks.ts index 6a296e4..edb1622 100644 --- a/dashboard-next/src/features/overview/hooks.ts +++ b/dashboard-next/src/features/overview/hooks.ts @@ -1,12 +1,10 @@ import { useQuery } from "@tanstack/react-query"; import { useRefreshInterval } from "@/providers/refresh-interval-provider"; -import { - fetchPausedQueues, - fetchQueueStats, - fetchRecentJobs, - fetchStats, - fetchThroughput, -} from "./api"; +import { fetchRecentJobs, fetchStats, fetchThroughput } from "./api"; + +// Re-export queue hooks so the Overview route can import everything it needs +// from one module without reaching into a sibling feature directly. +export { usePausedQueues, useQueueStats } from "../queues/hooks"; export function useStats() { const { intervalMs } = useRefreshInterval(); @@ -17,24 +15,6 @@ export function useStats() { }); } -export function useQueueStats() { - const { intervalMs } = useRefreshInterval(); - return useQuery({ - queryKey: ["stats", "queues"], - queryFn: ({ signal }) => fetchQueueStats(signal), - refetchInterval: intervalMs, - }); -} - -export function usePausedQueues() { - const { intervalMs } = useRefreshInterval(); - return useQuery({ - queryKey: ["queues", "paused"], - queryFn: ({ signal }) => fetchPausedQueues(signal), - refetchInterval: intervalMs, - }); -} - export function useRecentJobs(limit = 10) { const { intervalMs } = useRefreshInterval(); return useQuery({ diff --git a/dashboard-next/src/features/queues/api.ts b/dashboard-next/src/features/queues/api.ts new file mode 100644 index 0000000..01c8f03 --- /dev/null +++ b/dashboard-next/src/features/queues/api.ts @@ -0,0 +1,18 @@ +import { api } from "@/lib/api-client"; +import type { QueueStatsMap } from "@/lib/api-types"; + +export function fetchQueueStats(signal?: AbortSignal): Promise { + return api.get("/api/stats/queues", { signal }); +} + +export function fetchPausedQueues(signal?: AbortSignal): Promise { + return api.get("/api/queues/paused", { signal }); +} + +export function pauseQueue(name: string): Promise<{ paused: string }> { + return api.post<{ paused: string }>(`/api/queues/${encodeURIComponent(name)}/pause`); +} + +export function resumeQueue(name: string): Promise<{ resumed: string }> { + return api.post<{ resumed: string }>(`/api/queues/${encodeURIComponent(name)}/resume`); +} diff --git a/dashboard-next/src/features/queues/components/index.ts b/dashboard-next/src/features/queues/components/index.ts new file mode 100644 index 0000000..646e0c8 --- /dev/null +++ b/dashboard-next/src/features/queues/components/index.ts @@ -0,0 +1 @@ +export { QueuesTable } from "./queues-table"; diff --git a/dashboard-next/src/features/queues/components/queues-table.tsx b/dashboard-next/src/features/queues/components/queues-table.tsx new file mode 100644 index 0000000..ee61a18 --- /dev/null +++ b/dashboard-next/src/features/queues/components/queues-table.tsx @@ -0,0 +1,161 @@ +import type { ColumnDef } from "@tanstack/react-table"; +import { Box, Pause, Play } from "lucide-react"; +import { useMemo } from "react"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { DataTable } from "@/components/ui/data-table"; +import { EmptyState } from "@/components/ui/empty-state"; +import { ErrorState } from "@/components/ui/error-state"; +import { Skeleton } from "@/components/ui/skeleton"; +import type { QueueStatsMap } from "@/lib/api-types"; +import { formatCount } from "@/lib/number"; +import { usePauseQueue, useResumeQueue } from "../hooks"; + +interface QueueRow { + name: string; + paused: boolean; + pending: number; + running: number; + completed: number; + failed: number; + dead: number; +} + +interface QueuesTableProps { + stats: QueueStatsMap | undefined; + paused: string[] | undefined; + loading: boolean; + error: Error | null; + onRetry: () => void; +} + +export function QueuesTable({ stats, paused, loading, error, onRetry }: QueuesTableProps) { + const pauseMutation = usePauseQueue(); + const resumeMutation = useResumeQueue(); + + const rows = useMemo(() => { + if (!stats) return []; + const pausedSet = new Set(paused ?? []); + return Object.entries(stats) + .map(([name, s]) => ({ + name, + paused: pausedSet.has(name), + pending: s.pending ?? 0, + running: s.running ?? 0, + completed: s.completed ?? 0, + failed: s.failed ?? 0, + dead: s.dead ?? 0, + })) + .sort((a, b) => a.name.localeCompare(b.name)); + }, [stats, paused]); + + const columns = useMemo[]>( + () => [ + { + accessorKey: "name", + header: "Queue", + cell: ({ row }) => ( +
+ {row.original.name} + {row.original.paused ? Paused : null} +
+ ), + }, + { + accessorKey: "pending", + header: "Pending", + cell: ({ getValue }) => ( + {formatCount(getValue())} + ), + }, + { + accessorKey: "running", + header: "Running", + cell: ({ getValue }) => ( + {formatCount(getValue())} + ), + }, + { + accessorKey: "completed", + header: "Completed", + cell: ({ getValue }) => ( + + {formatCount(getValue())} + + ), + }, + { + accessorKey: "failed", + header: "Failed", + cell: ({ row }) => { + const total = row.original.failed + row.original.dead; + return ( + 0 ? "text-danger" : "text-[var(--fg-muted)]"}`} + > + {formatCount(total)} + + ); + }, + }, + { + id: "actions", + header: "", + cell: ({ row }) => { + const name = row.original.name; + if (row.original.paused) { + return ( + + ); + } + return ( + + ); + }, + }, + ], + [pauseMutation, resumeMutation], + ); + + if (error) { + return ( + + ); + } + + if (loading && rows.length === 0) { + return ; + } + + if (rows.length === 0) { + return ( + + ); + } + + return r.name} />; +} diff --git a/dashboard-next/src/features/queues/hooks.ts b/dashboard-next/src/features/queues/hooks.ts new file mode 100644 index 0000000..055df10 --- /dev/null +++ b/dashboard-next/src/features/queues/hooks.ts @@ -0,0 +1,87 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { toast } from "sonner"; +import { useRefreshInterval } from "@/providers/refresh-interval-provider"; +import { fetchPausedQueues, fetchQueueStats, pauseQueue, resumeQueue } from "./api"; + +const KEY = { + stats: ["queues", "stats"] as const, + paused: ["queues", "paused"] as const, + all: ["queues"] as const, +}; + +export function useQueueStats() { + const { intervalMs } = useRefreshInterval(); + return useQuery({ + queryKey: KEY.stats, + queryFn: ({ signal }) => fetchQueueStats(signal), + refetchInterval: intervalMs, + }); +} + +export function usePausedQueues() { + const { intervalMs } = useRefreshInterval(); + return useQuery({ + queryKey: KEY.paused, + queryFn: ({ signal }) => fetchPausedQueues(signal), + refetchInterval: intervalMs, + }); +} + +interface OptimisticContext { + prev: string[] | undefined; +} + +/** + * Pause a queue. + * + * Optimistically adds the queue name to the paused set so the UI reflects + * the change instantly; rolls back on error. + */ +export function usePauseQueue() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: (name: string) => pauseQueue(name), + onMutate: async (name) => { + await qc.cancelQueries({ queryKey: KEY.paused }); + const prev = qc.getQueryData(KEY.paused); + qc.setQueryData(KEY.paused, [...(prev ?? []), name]); + return { prev } satisfies OptimisticContext; + }, + onError: (error, _name, ctx) => { + if (ctx?.prev !== undefined) qc.setQueryData(KEY.paused, ctx.prev); + toast.error("Couldn't pause queue", { + description: error instanceof Error ? error.message : String(error), + }); + }, + onSuccess: (result) => toast.success(`Paused ${result.paused}`), + onSettled: () => qc.invalidateQueries({ queryKey: KEY.all }), + }); +} + +/** + * Resume a paused queue. Mirrors `usePauseQueue` with an inverted optimistic + * update. + */ +export function useResumeQueue() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: (name: string) => resumeQueue(name), + onMutate: async (name) => { + await qc.cancelQueries({ queryKey: KEY.paused }); + const prev = qc.getQueryData(KEY.paused); + qc.setQueryData( + KEY.paused, + (prev ?? []).filter((n) => n !== name), + ); + return { prev } satisfies OptimisticContext; + }, + onError: (error, _name, ctx) => { + if (ctx?.prev !== undefined) qc.setQueryData(KEY.paused, ctx.prev); + toast.error("Couldn't resume queue", { + description: error instanceof Error ? error.message : String(error), + }); + }, + onSuccess: (result) => toast.success(`Resumed ${result.resumed}`), + onSettled: () => qc.invalidateQueries({ queryKey: KEY.all }), + }); +} diff --git a/dashboard-next/src/features/queues/index.ts b/dashboard-next/src/features/queues/index.ts new file mode 100644 index 0000000..a234113 --- /dev/null +++ b/dashboard-next/src/features/queues/index.ts @@ -0,0 +1,2 @@ +export * from "./components"; +export * from "./hooks"; diff --git a/dashboard-next/src/routes/queues.tsx b/dashboard-next/src/routes/queues.tsx index d2db4db..09e5731 100644 --- a/dashboard-next/src/routes/queues.tsx +++ b/dashboard-next/src/routes/queues.tsx @@ -1,17 +1,31 @@ import { createFileRoute } from "@tanstack/react-router"; -import { Box } from "lucide-react"; import { PageHeader } from "@/components/layout"; -import { EmptyState } from "@/components/ui/empty-state"; +import { QueuesTable, usePausedQueues, useQueueStats } from "@/features/queues"; export const Route = createFileRoute("/queues")({ component: QueuesPage, }); function QueuesPage() { + const stats = useQueueStats(); + const paused = usePausedQueues(); + return ( <> - - + + { + stats.refetch(); + paused.refetch(); + }} + /> ); } From 0fb07d669bd30ea75a8cb446f1cfc9cb965c94ad Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 01:28:06 +0530 Subject: [PATCH 19/42] feat(dashboard-next): dead letters with grouping and typed purge --- .../ui/destructive-confirm-dialog.tsx | 93 ++++++++++++++ dashboard-next/src/components/ui/index.ts | 1 + .../src/features/dead-letters/api.ts | 25 ++++ .../components/dead-letter-group-row.tsx | 53 ++++++++ .../components/dead-letter-list.tsx | 68 ++++++++++ .../components/dead-letter-row.tsx | 54 ++++++++ .../features/dead-letters/components/index.ts | 3 + .../src/features/dead-letters/hooks.ts | 61 +++++++++ .../src/features/dead-letters/index.ts | 3 + .../src/features/dead-letters/utils.test.ts | 70 ++++++++++ .../src/features/dead-letters/utils.ts | 48 +++++++ dashboard-next/src/routes/dead-letters.tsx | 121 +++++++++++++++++- 12 files changed, 596 insertions(+), 4 deletions(-) create mode 100644 dashboard-next/src/components/ui/destructive-confirm-dialog.tsx create mode 100644 dashboard-next/src/features/dead-letters/api.ts create mode 100644 dashboard-next/src/features/dead-letters/components/dead-letter-group-row.tsx create mode 100644 dashboard-next/src/features/dead-letters/components/dead-letter-list.tsx create mode 100644 dashboard-next/src/features/dead-letters/components/dead-letter-row.tsx create mode 100644 dashboard-next/src/features/dead-letters/components/index.ts create mode 100644 dashboard-next/src/features/dead-letters/hooks.ts create mode 100644 dashboard-next/src/features/dead-letters/index.ts create mode 100644 dashboard-next/src/features/dead-letters/utils.test.ts create mode 100644 dashboard-next/src/features/dead-letters/utils.ts diff --git a/dashboard-next/src/components/ui/destructive-confirm-dialog.tsx b/dashboard-next/src/components/ui/destructive-confirm-dialog.tsx new file mode 100644 index 0000000..c7fc4d2 --- /dev/null +++ b/dashboard-next/src/components/ui/destructive-confirm-dialog.tsx @@ -0,0 +1,93 @@ +import { type ReactNode, useEffect, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; + +interface DestructiveConfirmDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + title: ReactNode; + description?: ReactNode; + /** + * Word the user must type to unlock the destructive button. Matched case + * sensitively so a trained eye has to confirm intent. + */ + confirmPhrase: string; + confirmLabel?: string; + cancelLabel?: string; + pending?: boolean; + onConfirm: () => void | Promise; +} + +/** + * Confirmation dialog for irreversible destructive actions. + * + * Instead of a single click, requires typing a specific phrase. Meant for + * actions like purge-all, drop-table, delete-workspace — the stakes are + * high enough that the friction is the feature. + */ +export function DestructiveConfirmDialog({ + open, + onOpenChange, + title, + description, + confirmPhrase, + confirmLabel = "Confirm", + cancelLabel = "Cancel", + pending = false, + onConfirm, +}: DestructiveConfirmDialogProps) { + const [typed, setTyped] = useState(""); + + useEffect(() => { + if (!open) setTyped(""); + }, [open]); + + const ready = typed === confirmPhrase; + + async function handleConfirm() { + if (!ready) return; + await onConfirm(); + onOpenChange(false); + } + + return ( + + + + {title} + {description ? {description} : null} + +
+ + setTyped(e.target.value)} + autoComplete="off" + autoFocus + disabled={pending} + /> +
+ + + + +
+
+ ); +} diff --git a/dashboard-next/src/components/ui/index.ts b/dashboard-next/src/components/ui/index.ts index dc98ac2..c8dcd7e 100644 --- a/dashboard-next/src/components/ui/index.ts +++ b/dashboard-next/src/components/ui/index.ts @@ -21,6 +21,7 @@ export { } from "./command"; export { ConfirmDialog } from "./confirm-dialog"; export { DataTable } from "./data-table"; +export { DestructiveConfirmDialog } from "./destructive-confirm-dialog"; export { Dialog, DialogClose, diff --git a/dashboard-next/src/features/dead-letters/api.ts b/dashboard-next/src/features/dead-letters/api.ts new file mode 100644 index 0000000..da8b748 --- /dev/null +++ b/dashboard-next/src/features/dead-letters/api.ts @@ -0,0 +1,25 @@ +import { api } from "@/lib/api-client"; +import type { DeadLetter } from "@/lib/api-types"; + +export interface DeadLettersPage { + items: DeadLetter[]; +} + +export function fetchDeadLetters( + page: number, + pageSize: number, + signal?: AbortSignal, +): Promise { + return api.get("/api/dead-letters", { + signal, + params: { limit: pageSize, offset: page * pageSize }, + }); +} + +export function retryDeadLetter(id: string): Promise<{ new_job_id: string }> { + return api.post<{ new_job_id: string }>(`/api/dead-letters/${encodeURIComponent(id)}/retry`); +} + +export function purgeDeadLetters(): Promise<{ purged: number }> { + return api.post<{ purged: number }>("/api/dead-letters/purge"); +} diff --git a/dashboard-next/src/features/dead-letters/components/dead-letter-group-row.tsx b/dashboard-next/src/features/dead-letters/components/dead-letter-group-row.tsx new file mode 100644 index 0000000..80c8331 --- /dev/null +++ b/dashboard-next/src/features/dead-letters/components/dead-letter-group-row.tsx @@ -0,0 +1,53 @@ +import { ChevronDown, ChevronRight } from "lucide-react"; +import { useState } from "react"; +import { Badge } from "@/components/ui/badge"; +import type { DeadLetterGroup } from "../utils"; +import { DeadLetterRow } from "./dead-letter-row"; + +interface DeadLetterGroupRowProps { + group: DeadLetterGroup; +} + +export function DeadLetterGroupRow({ group }: DeadLetterGroupRowProps) { + const [open, setOpen] = useState(false); + const count = group.entries.length; + return ( +
+ + {open ? ( +
    + {group.entries.map((entry) => ( +
  • + +
  • + ))} +
+ ) : null} +
+ ); +} diff --git a/dashboard-next/src/features/dead-letters/components/dead-letter-list.tsx b/dashboard-next/src/features/dead-letters/components/dead-letter-list.tsx new file mode 100644 index 0000000..62a3b79 --- /dev/null +++ b/dashboard-next/src/features/dead-letters/components/dead-letter-list.tsx @@ -0,0 +1,68 @@ +import { Skull } from "lucide-react"; +import { useMemo } from "react"; +import { EmptyState } from "@/components/ui/empty-state"; +import { ErrorState } from "@/components/ui/error-state"; +import { Skeleton } from "@/components/ui/skeleton"; +import type { DeadLetter } from "@/lib/api-types"; +import { groupByError } from "../utils"; +import { DeadLetterGroupRow } from "./dead-letter-group-row"; +import { DeadLetterRow } from "./dead-letter-row"; + +export type DeadLetterView = "flat" | "grouped"; + +interface DeadLetterListProps { + items: DeadLetter[] | undefined; + view: DeadLetterView; + loading: boolean; + error: Error | null; + onRetry: () => void; +} + +export function DeadLetterList({ items, view, loading, error, onRetry }: DeadLetterListProps) { + const groups = useMemo( + () => (view === "grouped" && items ? groupByError(items) : []), + [view, items], + ); + + if (error) { + return ( + + ); + } + + if (loading && !items) { + return ; + } + + if (!items || items.length === 0) { + return ( + + ); + } + + if (view === "grouped") { + return ( +
+ {groups.map((g) => ( + + ))} +
+ ); + } + + return ( +
+ {items.map((item) => ( + + ))} +
+ ); +} diff --git a/dashboard-next/src/features/dead-letters/components/dead-letter-row.tsx b/dashboard-next/src/features/dead-letters/components/dead-letter-row.tsx new file mode 100644 index 0000000..abf2f48 --- /dev/null +++ b/dashboard-next/src/features/dead-letters/components/dead-letter-row.tsx @@ -0,0 +1,54 @@ +import { Link } from "@tanstack/react-router"; +import { RotateCcw } from "lucide-react"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import type { DeadLetter } from "@/lib/api-types"; +import { formatRelative } from "@/lib/time"; +import { useRetryDeadLetter } from "../hooks"; + +interface DeadLetterRowProps { + item: DeadLetter; +} + +export function DeadLetterRow({ item }: DeadLetterRowProps) { + const retry = useRetryDeadLetter(); + return ( +
+
+
+ + {item.original_job_id.slice(0, 10)}… + + · + {item.task_name} + {item.queue} + {item.retry_count > 0 ? ( + + {item.retry_count} {item.retry_count === 1 ? "retry" : "retries"} + + ) : null} + + {formatRelative(item.failed_at * 1000)} + +
+ {item.error ? ( +
+            {item.error}
+          
+ ) : null} +
+ +
+ ); +} diff --git a/dashboard-next/src/features/dead-letters/components/index.ts b/dashboard-next/src/features/dead-letters/components/index.ts new file mode 100644 index 0000000..e1f897a --- /dev/null +++ b/dashboard-next/src/features/dead-letters/components/index.ts @@ -0,0 +1,3 @@ +export { DeadLetterGroupRow } from "./dead-letter-group-row"; +export { DeadLetterList, type DeadLetterView } from "./dead-letter-list"; +export { DeadLetterRow } from "./dead-letter-row"; diff --git a/dashboard-next/src/features/dead-letters/hooks.ts b/dashboard-next/src/features/dead-letters/hooks.ts new file mode 100644 index 0000000..9ad5d21 --- /dev/null +++ b/dashboard-next/src/features/dead-letters/hooks.ts @@ -0,0 +1,61 @@ +import { keepPreviousData, useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { toast } from "sonner"; +import { useRefreshInterval } from "@/providers/refresh-interval-provider"; +import { fetchDeadLetters, purgeDeadLetters, retryDeadLetter } from "./api"; + +const KEY = { + all: ["dead-letters"] as const, + list: (page: number, pageSize: number) => ["dead-letters", "list", page, pageSize] as const, +}; + +export function useDeadLetters(page: number, pageSize: number) { + const { intervalMs } = useRefreshInterval(); + return useQuery({ + queryKey: KEY.list(page, pageSize), + queryFn: ({ signal }) => fetchDeadLetters(page, pageSize, signal), + placeholderData: keepPreviousData, + refetchInterval: intervalMs, + }); +} + +export function useRetryDeadLetter() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: (id: string) => retryDeadLetter(id), + onError: (error) => { + toast.error("Couldn't retry dead letter", { + description: error instanceof Error ? error.message : String(error), + }); + }, + onSuccess: (result) => { + toast.success("Re-enqueued", { + description: `New job: ${result.new_job_id.slice(0, 8)}…`, + }); + }, + onSettled: () => { + qc.invalidateQueries({ queryKey: KEY.all }); + qc.invalidateQueries({ queryKey: ["stats"] }); + }, + }); +} + +export function usePurgeDeadLetters() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: () => purgeDeadLetters(), + onError: (error) => { + toast.error("Couldn't purge dead letters", { + description: error instanceof Error ? error.message : String(error), + }); + }, + onSuccess: (result) => { + toast.success("Dead letters purged", { + description: `${result.purged} entries removed`, + }); + }, + onSettled: () => { + qc.invalidateQueries({ queryKey: KEY.all }); + qc.invalidateQueries({ queryKey: ["stats"] }); + }, + }); +} diff --git a/dashboard-next/src/features/dead-letters/index.ts b/dashboard-next/src/features/dead-letters/index.ts new file mode 100644 index 0000000..bbb8e24 --- /dev/null +++ b/dashboard-next/src/features/dead-letters/index.ts @@ -0,0 +1,3 @@ +export * from "./components"; +export * from "./hooks"; +export { type DeadLetterGroup, groupByError } from "./utils"; diff --git a/dashboard-next/src/features/dead-letters/utils.test.ts b/dashboard-next/src/features/dead-letters/utils.test.ts new file mode 100644 index 0000000..886a250 --- /dev/null +++ b/dashboard-next/src/features/dead-letters/utils.test.ts @@ -0,0 +1,70 @@ +import { describe, expect, it } from "vitest"; +import type { DeadLetter } from "@/lib/api-types"; +import { groupByError } from "./utils"; + +function dl(overrides: Partial): DeadLetter { + return { + id: overrides.id ?? "id-1", + original_job_id: overrides.original_job_id ?? "job-1", + task_name: overrides.task_name ?? "send_email", + queue: overrides.queue ?? "default", + error: overrides.error ?? null, + retry_count: overrides.retry_count ?? 3, + failed_at: overrides.failed_at ?? 1_700_000_000, + }; +} + +describe("groupByError", () => { + it("returns empty array when no items", () => { + expect(groupByError([])).toEqual([]); + }); + + it("collapses entries with the same error into one group", () => { + const items = [ + dl({ id: "a", error: "timeout", failed_at: 1000 }), + dl({ id: "b", error: "timeout", failed_at: 2000 }), + dl({ id: "c", error: "divide by zero", failed_at: 1500 }), + ]; + const groups = groupByError(items); + expect(groups).toHaveLength(2); + const timeout = groups.find((g) => g.error === "timeout")!; + expect(timeout.entries).toHaveLength(2); + }); + + it("sorts entries within a group newest-first", () => { + const items = [ + dl({ id: "old", error: "oops", failed_at: 100 }), + dl({ id: "new", error: "oops", failed_at: 900 }), + dl({ id: "middle", error: "oops", failed_at: 500 }), + ]; + const groups = groupByError(items); + expect(groups[0]!.entries.map((e) => e.id)).toEqual(["new", "middle", "old"]); + }); + + it("orders groups by the freshest failure first", () => { + const items = [ + dl({ id: "a", error: "E1", failed_at: 10 }), + dl({ id: "b", error: "E2", failed_at: 50 }), + dl({ id: "c", error: "E3", failed_at: 30 }), + ]; + const groups = groupByError(items); + expect(groups.map((g) => g.error)).toEqual(["E2", "E3", "E1"]); + }); + + it("treats null and whitespace-only errors as one labeled group", () => { + const items = [dl({ id: "a", error: null }), dl({ id: "b", error: " " })]; + const groups = groupByError(items); + expect(groups).toHaveLength(1); + expect(groups[0]!.error).toMatch(/no error captured/i); + }); + + it("dedupes queues across entries in a group", () => { + const items = [ + dl({ id: "a", error: "boom", queue: "emails" }), + dl({ id: "b", error: "boom", queue: "emails" }), + dl({ id: "c", error: "boom", queue: "reports" }), + ]; + const [group] = groupByError(items); + expect(group!.queues).toEqual(["emails", "reports"]); + }); +}); diff --git a/dashboard-next/src/features/dead-letters/utils.ts b/dashboard-next/src/features/dead-letters/utils.ts new file mode 100644 index 0000000..839a6db --- /dev/null +++ b/dashboard-next/src/features/dead-letters/utils.ts @@ -0,0 +1,48 @@ +import type { DeadLetter } from "@/lib/api-types"; + +export interface DeadLetterGroup { + /** Canonical error message shared by every entry in the group. */ + error: string; + /** Sample task name used as the group label. */ + sampleTask: string; + /** Queues represented in the group (deduped). */ + queues: string[]; + /** All dead letters in this group, newest first. */ + entries: DeadLetter[]; +} + +const EMPTY_ERROR_LABEL = "(no error captured)"; + +/** + * Group dead letters by error message. + * + * Items with the same error are collapsed into a single group so an operator + * isn't drowning in 500 identical SQL timeouts. Groups are returned ordered + * by most recent failure first. + */ +export function groupByError(items: DeadLetter[]): DeadLetterGroup[] { + const byError = new Map(); + for (const item of items) { + const key = (item.error ?? "").trim() || EMPTY_ERROR_LABEL; + const existing = byError.get(key); + if (existing) { + existing.entries.push(item); + if (!existing.queues.includes(item.queue)) existing.queues.push(item.queue); + } else { + byError.set(key, { + error: key, + sampleTask: item.task_name, + queues: [item.queue], + entries: [item], + }); + } + } + // Ensure entries within a group are newest-first, then order groups by + // the most recent failure across all entries. + for (const group of byError.values()) { + group.entries.sort((a, b) => b.failed_at - a.failed_at); + } + return [...byError.values()].sort( + (a, b) => (b.entries[0]?.failed_at ?? 0) - (a.entries[0]?.failed_at ?? 0), + ); +} diff --git a/dashboard-next/src/routes/dead-letters.tsx b/dashboard-next/src/routes/dead-letters.tsx index fbfec2a..4df5bc7 100644 --- a/dashboard-next/src/routes/dead-letters.tsx +++ b/dashboard-next/src/routes/dead-letters.tsx @@ -1,17 +1,130 @@ import { createFileRoute } from "@tanstack/react-router"; -import { Skull } from "lucide-react"; +import { List, Rows3, Trash2 } from "lucide-react"; +import { useState } from "react"; import { PageHeader } from "@/components/layout"; -import { EmptyState } from "@/components/ui/empty-state"; +import { Button } from "@/components/ui/button"; +import { DestructiveConfirmDialog } from "@/components/ui/destructive-confirm-dialog"; +import { Pagination } from "@/components/ui/pagination"; +import { + DeadLetterList, + type DeadLetterView, + useDeadLetters, + usePurgeDeadLetters, +} from "@/features/dead-letters"; +import { cn } from "@/lib/cn"; + +const PAGE_SIZE = 25; export const Route = createFileRoute("/dead-letters")({ component: DeadLettersPage, }); function DeadLettersPage() { + const [page, setPage] = useState(0); + const [view, setView] = useState("grouped"); + const [confirmPurge, setConfirmPurge] = useState(false); + + const query = useDeadLetters(page, PAGE_SIZE); + const purge = usePurgeDeadLetters(); + + const items = query.data; + const hasMore = items ? items.length >= PAGE_SIZE : false; + return ( <> - - + + + + + } + /> + +
+ query.refetch()} + /> + +
+ + { + await purge.mutateAsync(); + }} + /> ); } + +function ViewToggle({ + value, + onChange, +}: { + value: DeadLetterView; + onChange: (v: DeadLetterView) => void; +}) { + return ( +
+ onChange("grouped")} label="Grouped"> + + + onChange("flat")} label="Flat"> + + +
+ ); +} + +function ToggleBtn({ + active, + onClick, + label, + children, +}: { + active: boolean; + onClick: () => void; + label: string; + children: React.ReactNode; +}) { + return ( + + ); +} From a0dce622a5fcede92ad6c47bfd4115bc66fd6648 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 01:28:10 +0530 Subject: [PATCH 20/42] feat(dashboard-next): metrics with lazy-loaded charts --- dashboard-next/src/features/metrics/api.ts | 37 +++++ .../src/features/metrics/components/index.ts | 5 + .../metrics/components/latency-chart.tsx | 106 ++++++++++++++ .../metrics/components/metrics-table.tsx | 128 +++++++++++++++++ .../metrics/components/task-selector.tsx | 34 +++++ .../metrics/components/throughput-chart.tsx | 135 ++++++++++++++++++ .../components/time-range-selector.tsx | 41 ++++++ dashboard-next/src/features/metrics/hooks.ts | 31 ++++ dashboard-next/src/features/metrics/index.ts | 4 + dashboard-next/src/features/metrics/page.tsx | 64 +++++++++ dashboard-next/src/features/metrics/types.ts | 24 ++++ dashboard-next/src/routes/metrics.lazy.tsx | 6 + dashboard-next/src/routes/metrics.tsx | 19 +-- 13 files changed, 619 insertions(+), 15 deletions(-) create mode 100644 dashboard-next/src/features/metrics/api.ts create mode 100644 dashboard-next/src/features/metrics/components/index.ts create mode 100644 dashboard-next/src/features/metrics/components/latency-chart.tsx create mode 100644 dashboard-next/src/features/metrics/components/metrics-table.tsx create mode 100644 dashboard-next/src/features/metrics/components/task-selector.tsx create mode 100644 dashboard-next/src/features/metrics/components/throughput-chart.tsx create mode 100644 dashboard-next/src/features/metrics/components/time-range-selector.tsx create mode 100644 dashboard-next/src/features/metrics/hooks.ts create mode 100644 dashboard-next/src/features/metrics/index.ts create mode 100644 dashboard-next/src/features/metrics/page.tsx create mode 100644 dashboard-next/src/features/metrics/types.ts create mode 100644 dashboard-next/src/routes/metrics.lazy.tsx diff --git a/dashboard-next/src/features/metrics/api.ts b/dashboard-next/src/features/metrics/api.ts new file mode 100644 index 0000000..df0e90d --- /dev/null +++ b/dashboard-next/src/features/metrics/api.ts @@ -0,0 +1,37 @@ +import { api } from "@/lib/api-client"; +import type { MetricsResponse, TimeseriesBucket } from "@/lib/api-types"; + +interface MetricsParams { + task?: string; + sinceSeconds: number; +} + +export function fetchMetrics( + params: MetricsParams, + signal?: AbortSignal, +): Promise { + return api.get("/api/metrics", { + signal, + params: { since: params.sinceSeconds, ...(params.task ? { task: params.task } : {}) }, + }); +} + +interface TimeseriesParams { + task?: string; + sinceSeconds: number; + bucketSeconds: number; +} + +export function fetchTimeseries( + params: TimeseriesParams, + signal?: AbortSignal, +): Promise { + return api.get("/api/metrics/timeseries", { + signal, + params: { + since: params.sinceSeconds, + bucket: params.bucketSeconds, + ...(params.task ? { task: params.task } : {}), + }, + }); +} diff --git a/dashboard-next/src/features/metrics/components/index.ts b/dashboard-next/src/features/metrics/components/index.ts new file mode 100644 index 0000000..7c55e25 --- /dev/null +++ b/dashboard-next/src/features/metrics/components/index.ts @@ -0,0 +1,5 @@ +export { LatencyChart } from "./latency-chart"; +export { MetricsTable } from "./metrics-table"; +export { TaskSelector } from "./task-selector"; +export { ThroughputChart } from "./throughput-chart"; +export { TimeRangeSelector } from "./time-range-selector"; diff --git a/dashboard-next/src/features/metrics/components/latency-chart.tsx b/dashboard-next/src/features/metrics/components/latency-chart.tsx new file mode 100644 index 0000000..1e73735 --- /dev/null +++ b/dashboard-next/src/features/metrics/components/latency-chart.tsx @@ -0,0 +1,106 @@ +import { useMemo } from "react"; +import { + CartesianGrid, + Line, + LineChart, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from "recharts"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Skeleton } from "@/components/ui/skeleton"; +import type { TimeseriesBucket } from "@/lib/api-types"; + +interface LatencyChartProps { + buckets: TimeseriesBucket[] | undefined; + loading: boolean; +} + +interface Row { + t: number; + avg_ms: number; +} + +export function LatencyChart({ buckets, loading }: LatencyChartProps) { + const data = useMemo( + () => (buckets ?? []).map((b) => ({ t: b.timestamp * 1000, avg_ms: b.avg_ms })), + [buckets], + ); + + return ( + + + Average latency + + + {loading && data.length === 0 ? ( + + ) : data.length === 0 ? ( +
+ No data in the selected window +
+ ) : ( + + + + + `${v}ms`} + width={54} + /> + } /> + + + + )} +
+
+ ); +} + +interface LatencyTooltipPayload { + value?: number; +} + +function LatencyTooltip({ + active, + payload, + label, +}: { + active?: boolean; + payload?: LatencyTooltipPayload[]; + label?: unknown; +}) { + if (!active || !payload || payload.length === 0) return null; + const ts = typeof label === "number" ? label : Number(label); + const avg = payload[0]?.value ?? 0; + return ( +
+
{new Date(ts).toLocaleTimeString()}
+
{avg.toFixed(1)} ms avg
+
+ ); +} + +function formatAxisTime(value: number): string { + return new Date(value).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); +} diff --git a/dashboard-next/src/features/metrics/components/metrics-table.tsx b/dashboard-next/src/features/metrics/components/metrics-table.tsx new file mode 100644 index 0000000..b4a4f7f --- /dev/null +++ b/dashboard-next/src/features/metrics/components/metrics-table.tsx @@ -0,0 +1,128 @@ +import type { ColumnDef } from "@tanstack/react-table"; +import { useMemo } from "react"; +import { DataTable } from "@/components/ui/data-table"; +import { EmptyState } from "@/components/ui/empty-state"; +import { ErrorState } from "@/components/ui/error-state"; +import { Skeleton } from "@/components/ui/skeleton"; +import type { MetricsResponse, TaskMetrics } from "@/lib/api-types"; +import { formatCount, formatPercent } from "@/lib/number"; + +interface Row extends TaskMetrics { + task: string; + successRate: number; +} + +interface MetricsTableProps { + metrics: MetricsResponse | undefined; + loading: boolean; + error: Error | null; + onRetry: () => void; +} + +export function MetricsTable({ metrics, loading, error, onRetry }: MetricsTableProps) { + const rows = useMemo(() => { + if (!metrics) return []; + return Object.entries(metrics) + .map(([task, m]) => ({ + ...m, + task, + successRate: m.count > 0 ? m.success_count / m.count : 0, + })) + .sort((a, b) => b.count - a.count); + }, [metrics]); + + const columns = useMemo[]>( + () => [ + { + accessorKey: "task", + header: "Task", + cell: ({ getValue }) => ( + {getValue()} + ), + }, + { + accessorKey: "count", + header: "Runs", + cell: ({ getValue }) => ( + {formatCount(getValue())} + ), + }, + { + accessorKey: "successRate", + header: "Success", + cell: ({ row }) => { + const rate = row.original.successRate; + const tone = rate >= 0.99 ? "text-success" : rate >= 0.9 ? "text-warning" : "text-danger"; + return {formatPercent(rate, 1)}; + }, + }, + { + accessorKey: "failure_count", + header: "Failures", + cell: ({ getValue }) => { + const n = getValue(); + return ( + 0 ? "text-danger" : "text-[var(--fg-muted)]"}`}> + {formatCount(n)} + + ); + }, + }, + { + accessorKey: "p50_ms", + header: "p50", + cell: ({ getValue }) => ()} />, + }, + { + accessorKey: "p95_ms", + header: "p95", + cell: ({ getValue }) => ()} />, + }, + { + accessorKey: "p99_ms", + header: "p99", + cell: ({ getValue }) => ()} />, + }, + { + accessorKey: "avg_ms", + header: "avg", + cell: ({ getValue }) => ()} />, + }, + { + accessorKey: "max_ms", + header: "max", + cell: ({ getValue }) => ()} />, + }, + ], + [], + ); + + if (error) { + return ( + + ); + } + + if (loading && rows.length === 0) { + return ; + } + + if (rows.length === 0) { + return ( + + ); + } + + return r.task} />; +} + +function Ms({ value }: { value: number }) { + return ( + + {value > 0 ? `${value.toFixed(1)}ms` : "—"} + + ); +} diff --git a/dashboard-next/src/features/metrics/components/task-selector.tsx b/dashboard-next/src/features/metrics/components/task-selector.tsx new file mode 100644 index 0000000..5b7c314 --- /dev/null +++ b/dashboard-next/src/features/metrics/components/task-selector.tsx @@ -0,0 +1,34 @@ +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; + +const ALL = "__all__"; + +interface TaskSelectorProps { + value: string | undefined; + tasks: string[]; + onChange: (v: string | undefined) => void; + className?: string; +} + +export function TaskSelector({ value, tasks, onChange, className }: TaskSelectorProps) { + return ( + + ); +} diff --git a/dashboard-next/src/features/metrics/components/throughput-chart.tsx b/dashboard-next/src/features/metrics/components/throughput-chart.tsx new file mode 100644 index 0000000..f9969a8 --- /dev/null +++ b/dashboard-next/src/features/metrics/components/throughput-chart.tsx @@ -0,0 +1,135 @@ +import { useMemo } from "react"; +import { + Area, + AreaChart, + CartesianGrid, + Legend, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from "recharts"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Skeleton } from "@/components/ui/skeleton"; +import type { TimeseriesBucket } from "@/lib/api-types"; + +interface ThroughputChartProps { + buckets: TimeseriesBucket[] | undefined; + loading: boolean; +} + +interface Row { + t: number; + success: number; + failure: number; +} + +export function ThroughputChart({ buckets, loading }: ThroughputChartProps) { + const data = useMemo( + () => + (buckets ?? []).map((b) => ({ + t: b.timestamp * 1000, + success: b.success, + failure: b.failure, + })), + [buckets], + ); + + return ( + + + Throughput + + + {loading && data.length === 0 ? ( + + ) : data.length === 0 ? ( +
+ No data in the selected window +
+ ) : ( + + + + + + + + + + + + + + + + } /> + + + + + + )} +
+
+ ); +} + +interface TooltipPayload { + name?: string; + value?: number; + color?: string; +} + +function ChartTooltip({ + active, + payload, + label, +}: { + active?: boolean; + payload?: TooltipPayload[]; + label?: unknown; +}) { + if (!active || !payload || payload.length === 0) return null; + const ts = typeof label === "number" ? label : Number(label); + return ( +
+
{new Date(ts).toLocaleTimeString()}
+ {payload.map((entry) => ( +
+ + {entry.name} + {entry.value} +
+ ))} +
+ ); +} + +function formatAxisTime(value: number): string { + return new Date(value).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); +} diff --git a/dashboard-next/src/features/metrics/components/time-range-selector.tsx b/dashboard-next/src/features/metrics/components/time-range-selector.tsx new file mode 100644 index 0000000..618807e --- /dev/null +++ b/dashboard-next/src/features/metrics/components/time-range-selector.tsx @@ -0,0 +1,41 @@ +import { cn } from "@/lib/cn"; +import { TIME_RANGES, type TimeRange } from "../types"; + +interface TimeRangeSelectorProps { + value: TimeRange; + onChange: (v: TimeRange) => void; + className?: string; +} + +export function TimeRangeSelector({ value, onChange, className }: TimeRangeSelectorProps) { + return ( +
+ {TIME_RANGES.map((r) => { + const active = r.value === value; + return ( + + ); + })} +
+ ); +} diff --git a/dashboard-next/src/features/metrics/hooks.ts b/dashboard-next/src/features/metrics/hooks.ts new file mode 100644 index 0000000..ebf1995 --- /dev/null +++ b/dashboard-next/src/features/metrics/hooks.ts @@ -0,0 +1,31 @@ +import { useQuery } from "@tanstack/react-query"; +import { useRefreshInterval } from "@/providers/refresh-interval-provider"; +import { fetchMetrics, fetchTimeseries } from "./api"; +import { type TimeRange, timeRangeConfig } from "./types"; + +const KEY = { + summary: (task: string | undefined, range: TimeRange) => + ["metrics", "summary", task ?? "", range] as const, + timeseries: (task: string | undefined, range: TimeRange) => + ["metrics", "timeseries", task ?? "", range] as const, +}; + +export function useMetricsSummary(range: TimeRange, task?: string) { + const { intervalMs } = useRefreshInterval(); + const { sinceSeconds } = timeRangeConfig(range); + return useQuery({ + queryKey: KEY.summary(task, range), + queryFn: ({ signal }) => fetchMetrics({ task, sinceSeconds }, signal), + refetchInterval: intervalMs, + }); +} + +export function useMetricsTimeseries(range: TimeRange, task?: string) { + const { intervalMs } = useRefreshInterval(); + const { sinceSeconds, bucketSeconds } = timeRangeConfig(range); + return useQuery({ + queryKey: KEY.timeseries(task, range), + queryFn: ({ signal }) => fetchTimeseries({ task, sinceSeconds, bucketSeconds }, signal), + refetchInterval: intervalMs, + }); +} diff --git a/dashboard-next/src/features/metrics/index.ts b/dashboard-next/src/features/metrics/index.ts new file mode 100644 index 0000000..6e9b3aa --- /dev/null +++ b/dashboard-next/src/features/metrics/index.ts @@ -0,0 +1,4 @@ +export * from "./components"; +export * from "./hooks"; +export { default } from "./page"; +export { DEFAULT_TIME_RANGE, TIME_RANGES, type TimeRange, timeRangeConfig } from "./types"; diff --git a/dashboard-next/src/features/metrics/page.tsx b/dashboard-next/src/features/metrics/page.tsx new file mode 100644 index 0000000..c602ca2 --- /dev/null +++ b/dashboard-next/src/features/metrics/page.tsx @@ -0,0 +1,64 @@ +import { useMemo, useState } from "react"; +import { PageHeader } from "@/components/layout"; +import { + LatencyChart, + MetricsTable, + TaskSelector, + ThroughputChart, + TimeRangeSelector, +} from "./components"; +import { useMetricsSummary, useMetricsTimeseries } from "./hooks"; +import { DEFAULT_TIME_RANGE, type TimeRange } from "./types"; + +/** + * The metrics dashboard. + * + * Exported as a default component so the route can dynamically import it + * and keep Recharts off the main bundle. Time range + task filter live as + * local state; if they turn out to be shareable we can promote to URL. + */ +export default function MetricsPage() { + const [range, setRange] = useState(DEFAULT_TIME_RANGE); + const [task, setTask] = useState(undefined); + + const summary = useMetricsSummary(range, task); + const series = useMetricsTimeseries(range, task); + + const taskOptions = useMemo(() => { + // When a task filter is set the summary will only contain that one key, + // so we keep a stale list in state to avoid the selector collapsing. + return summary.data ? Object.keys(summary.data).sort() : []; + }, [summary.data]); + + return ( + <> + + + + + } + /> +
+
+ + +
+ summary.refetch()} + /> +
+ + ); +} diff --git a/dashboard-next/src/features/metrics/types.ts b/dashboard-next/src/features/metrics/types.ts new file mode 100644 index 0000000..3815f30 --- /dev/null +++ b/dashboard-next/src/features/metrics/types.ts @@ -0,0 +1,24 @@ +export type TimeRange = "15m" | "1h" | "6h" | "24h"; + +export const DEFAULT_TIME_RANGE: TimeRange = "1h"; + +export interface TimeRangeConfig { + value: TimeRange; + sinceSeconds: number; + bucketSeconds: number; +} + +/** + * Bucket sizes chosen so every range produces ~60 points on the chart — the + * sweet spot for resolution vs. readability. + */ +export const TIME_RANGES: TimeRangeConfig[] = [ + { value: "15m", sinceSeconds: 15 * 60, bucketSeconds: 15 }, + { value: "1h", sinceSeconds: 60 * 60, bucketSeconds: 60 }, + { value: "6h", sinceSeconds: 6 * 60 * 60, bucketSeconds: 6 * 60 }, + { value: "24h", sinceSeconds: 24 * 60 * 60, bucketSeconds: 24 * 60 }, +]; + +export function timeRangeConfig(value: TimeRange): TimeRangeConfig { + return TIME_RANGES.find((r) => r.value === value) ?? TIME_RANGES[1]!; +} diff --git a/dashboard-next/src/routes/metrics.lazy.tsx b/dashboard-next/src/routes/metrics.lazy.tsx new file mode 100644 index 0000000..51aa50f --- /dev/null +++ b/dashboard-next/src/routes/metrics.lazy.tsx @@ -0,0 +1,6 @@ +import { createLazyFileRoute } from "@tanstack/react-router"; +import MetricsPage from "@/features/metrics"; + +export const Route = createLazyFileRoute("/metrics")({ + component: MetricsPage, +}); diff --git a/dashboard-next/src/routes/metrics.tsx b/dashboard-next/src/routes/metrics.tsx index 1d7d432..3113294 100644 --- a/dashboard-next/src/routes/metrics.tsx +++ b/dashboard-next/src/routes/metrics.tsx @@ -1,17 +1,6 @@ import { createFileRoute } from "@tanstack/react-router"; -import { BarChart3 } from "lucide-react"; -import { PageHeader } from "@/components/layout"; -import { EmptyState } from "@/components/ui/empty-state"; -export const Route = createFileRoute("/metrics")({ - component: MetricsPage, -}); - -function MetricsPage() { - return ( - <> - - - - ); -} +// Component lives in the `.lazy.tsx` counterpart so Recharts only loads when +// the user actually opens the metrics screen. Keeping this file +// component-less makes TanStack's lazy-route convention kick in. +export const Route = createFileRoute("/metrics")({}); From a76fc9cdb4329b353b97f34f46503a06e00fec7a Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 01:28:18 +0530 Subject: [PATCH 21/42] feat(dashboard-next): virtualized live-tail logs --- dashboard-next/src/features/logs/api.ts | 23 +++ .../src/features/logs/components/index.ts | 2 + .../features/logs/components/log-filters.tsx | 66 +++++++++ .../features/logs/components/log-stream.tsx | 133 ++++++++++++++++++ dashboard-next/src/features/logs/hooks.ts | 15 ++ dashboard-next/src/features/logs/index.ts | 4 + dashboard-next/src/features/logs/page.tsx | 46 ++++++ dashboard-next/src/routes/logs.lazy.tsx | 6 + dashboard-next/src/routes/logs.tsx | 18 +-- 9 files changed, 298 insertions(+), 15 deletions(-) create mode 100644 dashboard-next/src/features/logs/api.ts create mode 100644 dashboard-next/src/features/logs/components/index.ts create mode 100644 dashboard-next/src/features/logs/components/log-filters.tsx create mode 100644 dashboard-next/src/features/logs/components/log-stream.tsx create mode 100644 dashboard-next/src/features/logs/hooks.ts create mode 100644 dashboard-next/src/features/logs/index.ts create mode 100644 dashboard-next/src/features/logs/page.tsx create mode 100644 dashboard-next/src/routes/logs.lazy.tsx diff --git a/dashboard-next/src/features/logs/api.ts b/dashboard-next/src/features/logs/api.ts new file mode 100644 index 0000000..c52731f --- /dev/null +++ b/dashboard-next/src/features/logs/api.ts @@ -0,0 +1,23 @@ +import { api } from "@/lib/api-client"; +import type { TaskLog } from "@/lib/api-types"; + +export const LOG_LEVELS = ["debug", "info", "warning", "error"] as const; + +export type LogLevel = (typeof LOG_LEVELS)[number]; + +export interface LogsQuery { + task?: string; + level?: LogLevel; + sinceSeconds: number; + limit: number; +} + +export function fetchLogs(query: LogsQuery, signal?: AbortSignal): Promise { + const params: Record = { + since: query.sinceSeconds, + limit: query.limit, + }; + if (query.task) params.task = query.task; + if (query.level) params.level = query.level; + return api.get("/api/logs", { signal, params }); +} diff --git a/dashboard-next/src/features/logs/components/index.ts b/dashboard-next/src/features/logs/components/index.ts new file mode 100644 index 0000000..c545b1d --- /dev/null +++ b/dashboard-next/src/features/logs/components/index.ts @@ -0,0 +1,2 @@ +export { LogFilters } from "./log-filters"; +export { LogStream } from "./log-stream"; diff --git a/dashboard-next/src/features/logs/components/log-filters.tsx b/dashboard-next/src/features/logs/components/log-filters.tsx new file mode 100644 index 0000000..e5a26ff --- /dev/null +++ b/dashboard-next/src/features/logs/components/log-filters.tsx @@ -0,0 +1,66 @@ +import { useEffect, useState } from "react"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { useDebouncedValue } from "@/hooks/use-debounced-value"; +import { cn } from "@/lib/cn"; +import { LOG_LEVELS, type LogLevel } from "../api"; + +const ALL_LEVELS = "__all__"; + +interface LogFiltersProps { + task: string | undefined; + level: LogLevel | undefined; + onChange: (next: { task?: string; level?: LogLevel }) => void; + className?: string; +} + +export function LogFilters({ task, level, onChange, className }: LogFiltersProps) { + const [localTask, setLocalTask] = useState(task ?? ""); + const debounced = useDebouncedValue(localTask, 300); + + useEffect(() => { + setLocalTask(task ?? ""); + }, [task]); + + useEffect(() => { + const next = debounced.trim() || undefined; + if (next !== task) { + onChange({ task: next, level }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps -- propagate only debounced value + }, [debounced]); + + return ( +
+ setLocalTask(e.target.value)} + placeholder="Filter by task name…" + /> + +
+ ); +} diff --git a/dashboard-next/src/features/logs/components/log-stream.tsx b/dashboard-next/src/features/logs/components/log-stream.tsx new file mode 100644 index 0000000..11998de --- /dev/null +++ b/dashboard-next/src/features/logs/components/log-stream.tsx @@ -0,0 +1,133 @@ +import { useVirtualizer } from "@tanstack/react-virtual"; +import { ScrollText } from "lucide-react"; +import { useEffect, useRef, useState } from "react"; +import { EmptyState } from "@/components/ui/empty-state"; +import { ErrorState } from "@/components/ui/error-state"; +import { Skeleton } from "@/components/ui/skeleton"; +import type { TaskLog } from "@/lib/api-types"; +import { cn } from "@/lib/cn"; +import { formatAbsolute } from "@/lib/time"; + +interface LogStreamProps { + logs: TaskLog[] | undefined; + loading: boolean; + error: Error | null; + onRetry: () => void; + className?: string; +} + +const LEVEL_TONE: Record = { + error: "text-danger", + warning: "text-warning", + warn: "text-warning", + info: "text-info", + debug: "text-[var(--fg-subtle)]", +}; + +const ROW_ESTIMATE = 28; +const AUTO_SCROLL_THRESHOLD_PX = 40; + +/** + * Live-tail log stream. + * + * Rows are virtualized via `@tanstack/react-virtual` — only the visible slice + * is mounted, so rendering 5000+ log lines stays smooth. Auto-scroll sticks + * the viewport to the newest row; if the user scrolls up past a threshold + * we disengage auto-scroll until they return to the bottom. + */ +export function LogStream({ logs, loading, error, onRetry, className }: LogStreamProps) { + const parentRef = useRef(null); + const [autoScroll, setAutoScroll] = useState(true); + + const count = logs?.length ?? 0; + const virtualizer = useVirtualizer({ + count, + getScrollElement: () => parentRef.current, + estimateSize: () => ROW_ESTIMATE, + overscan: 12, + }); + + // Stick to the bottom whenever new rows arrive and the user hasn't + // scrolled away from it. + useEffect(() => { + if (!autoScroll || count === 0) return; + virtualizer.scrollToIndex(count - 1, { align: "end" }); + }, [count, autoScroll, virtualizer]); + + const handleScroll = () => { + const el = parentRef.current; + if (!el) return; + const distanceFromBottom = el.scrollHeight - (el.scrollTop + el.clientHeight); + setAutoScroll(distanceFromBottom < AUTO_SCROLL_THRESHOLD_PX); + }; + + if (error) { + return ; + } + + if (loading && !logs) { + return ; + } + + if (!logs || logs.length === 0) { + return ( + + ); + } + + return ( +
+
+
+ {virtualizer.getVirtualItems().map((item) => { + const log = logs[item.index]; + if (!log) return null; + const levelClass = LEVEL_TONE[log.level.toLowerCase()] ?? "text-[var(--fg-muted)]"; + return ( +
+ + {formatAbsolute(log.logged_at * 1000)} + + {log.level} + + {log.task_name} + + {log.message} +
+ ); + })} +
+
+ {!autoScroll ? ( + + ) : null} +
+ ); +} diff --git a/dashboard-next/src/features/logs/hooks.ts b/dashboard-next/src/features/logs/hooks.ts new file mode 100644 index 0000000..63aa27b --- /dev/null +++ b/dashboard-next/src/features/logs/hooks.ts @@ -0,0 +1,15 @@ +import { keepPreviousData, useQuery } from "@tanstack/react-query"; +import { useRefreshInterval } from "@/providers/refresh-interval-provider"; +import { fetchLogs, type LogsQuery } from "./api"; + +const KEY = (q: LogsQuery) => ["logs", q.task ?? "", q.level ?? "", q.sinceSeconds, q.limit]; + +export function useLogs(query: LogsQuery) { + const { intervalMs } = useRefreshInterval(); + return useQuery({ + queryKey: KEY(query), + queryFn: ({ signal }) => fetchLogs(query, signal), + placeholderData: keepPreviousData, + refetchInterval: intervalMs, + }); +} diff --git a/dashboard-next/src/features/logs/index.ts b/dashboard-next/src/features/logs/index.ts new file mode 100644 index 0000000..0a97b09 --- /dev/null +++ b/dashboard-next/src/features/logs/index.ts @@ -0,0 +1,4 @@ +export { LOG_LEVELS, type LogLevel } from "./api"; +export * from "./components"; +export * from "./hooks"; +export { default } from "./page"; diff --git a/dashboard-next/src/features/logs/page.tsx b/dashboard-next/src/features/logs/page.tsx new file mode 100644 index 0000000..4fd2369 --- /dev/null +++ b/dashboard-next/src/features/logs/page.tsx @@ -0,0 +1,46 @@ +import { useState } from "react"; +import { PageHeader } from "@/components/layout"; +import type { LogLevel } from "./api"; +import { LogFilters, LogStream } from "./components"; +import { useLogs } from "./hooks"; + +const DEFAULT_SINCE_SECONDS = 3_600; +const PAGE_SIZE = 500; + +/** + * Logs route body. Default-exported so the route file can lazy-load the + * entire logs surface (including the virtual list dependency) on demand. + */ +export default function LogsPage() { + const [task, setTask] = useState(undefined); + const [level, setLevel] = useState(undefined); + + const logs = useLogs({ + task, + level, + sinceSeconds: DEFAULT_SINCE_SECONDS, + limit: PAGE_SIZE, + }); + + return ( + <> + +
+ { + setTask(next.task); + setLevel(next.level); + }} + /> + logs.refetch()} + /> +
+ + ); +} diff --git a/dashboard-next/src/routes/logs.lazy.tsx b/dashboard-next/src/routes/logs.lazy.tsx new file mode 100644 index 0000000..fd35b8f --- /dev/null +++ b/dashboard-next/src/routes/logs.lazy.tsx @@ -0,0 +1,6 @@ +import { createLazyFileRoute } from "@tanstack/react-router"; +import LogsPage from "@/features/logs"; + +export const Route = createLazyFileRoute("/logs")({ + component: LogsPage, +}); diff --git a/dashboard-next/src/routes/logs.tsx b/dashboard-next/src/routes/logs.tsx index a11706b..c968a40 100644 --- a/dashboard-next/src/routes/logs.tsx +++ b/dashboard-next/src/routes/logs.tsx @@ -1,17 +1,5 @@ import { createFileRoute } from "@tanstack/react-router"; -import { ScrollText } from "lucide-react"; -import { PageHeader } from "@/components/layout"; -import { EmptyState } from "@/components/ui/empty-state"; -export const Route = createFileRoute("/logs")({ - component: LogsPage, -}); - -function LogsPage() { - return ( - <> - - - - ); -} +// Lazy route: the virtual-list + log stream load only on demand. See +// `logs.lazy.tsx` for the actual component. +export const Route = createFileRoute("/logs")({}); From d476813eb494e1daa085e280a341dd5d71474be3 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 01:28:22 +0530 Subject: [PATCH 22/42] test(dashboard-next): vitest coverage for jobs utils --- .../src/features/jobs/utils.test.ts | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 dashboard-next/src/features/jobs/utils.test.ts diff --git a/dashboard-next/src/features/jobs/utils.test.ts b/dashboard-next/src/features/jobs/utils.test.ts new file mode 100644 index 0000000..703556e --- /dev/null +++ b/dashboard-next/src/features/jobs/utils.test.ts @@ -0,0 +1,110 @@ +import { describe, expect, it } from "vitest"; +import { + canCancel, + canReplay, + countActiveFilters, + isTerminalStatus, + parseJobListSearch, + toApiParams, +} from "./utils"; + +describe("parseJobListSearch", () => { + it("returns defaults when search is empty", () => { + const result = parseJobListSearch({}); + expect(result.page).toBe(0); + expect(result.pageSize).toBe(25); + expect(result.status).toBeUndefined(); + }); + + it("drops invalid status values silently", () => { + const result = parseJobListSearch({ status: "bogus" }); + expect(result.status).toBeUndefined(); + }); + + it("preserves valid status values", () => { + const result = parseJobListSearch({ status: "running" }); + expect(result.status).toBe("running"); + }); + + it("trims and drops empty strings", () => { + const result = parseJobListSearch({ queue: " ", task: "send_email" }); + expect(result.queue).toBeUndefined(); + expect(result.task).toBe("send_email"); + }); + + it("clamps page size", () => { + expect(parseJobListSearch({ pageSize: 5 }).pageSize).toBe(10); + expect(parseJobListSearch({ pageSize: 9999 }).pageSize).toBe(200); + }); + + it("rejects negative page numbers", () => { + const result = parseJobListSearch({ page: -5 }); + expect(result.page).toBe(0); + }); +}); + +describe("toApiParams", () => { + it("computes offset from page + pageSize", () => { + const p = toApiParams({ page: 2, pageSize: 25 }); + expect(p.offset).toBe(50); + expect(p.limit).toBe(25); + }); + + it("omits absent filters", () => { + const p = toApiParams({ page: 0, pageSize: 20 }); + expect(p).not.toHaveProperty("status"); + expect(p).not.toHaveProperty("queue"); + }); + + it("maps camelCase filter names to snake_case params", () => { + const p = toApiParams({ + page: 0, + pageSize: 20, + createdAfter: 1700, + createdBefore: 1800, + }); + expect(p).toMatchObject({ created_after: 1700, created_before: 1800 }); + }); +}); + +describe("countActiveFilters", () => { + it("counts only populated filters", () => { + expect(countActiveFilters({})).toBe(0); + expect(countActiveFilters({ status: "running" })).toBe(1); + expect( + countActiveFilters({ + status: "failed", + queue: "emails", + createdAfter: 123, + }), + ).toBe(3); + }); +}); + +describe("status helpers", () => { + it("treats complete/failed/dead/cancelled as terminal", () => { + expect(isTerminalStatus("complete")).toBe(true); + expect(isTerminalStatus("failed")).toBe(true); + expect(isTerminalStatus("dead")).toBe(true); + expect(isTerminalStatus("cancelled")).toBe(true); + expect(isTerminalStatus("pending")).toBe(false); + expect(isTerminalStatus("running")).toBe(false); + }); + + it("canCancel only for pending/running", () => { + expect(canCancel("pending")).toBe(true); + expect(canCancel("running")).toBe(true); + expect(canCancel("complete")).toBe(false); + expect(canCancel(undefined)).toBe(false); + }); + + it("canReplay only for terminal states", () => { + expect(canReplay("complete")).toBe(true); + expect(canReplay("failed")).toBe(true); + expect(canReplay("dead")).toBe(true); + expect(canReplay("cancelled")).toBe(true); + expect(canReplay("pending")).toBe(false); + expect(canReplay("running")).toBe(false); + expect(canReplay(undefined)).toBe(false); + }); +}); From af5e530488e99aa63c3348d5f3ca51a737af9712 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 02:06:12 +0530 Subject: [PATCH 23/42] chore: gitignore built dashboard assets --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 931ca15..570310c 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,9 @@ junk/ # Dashboard node_modules/ +# Built dashboard assets: regenerated by `pnpm -C dashboard-next build` and +# bundled into wheels by CI. Not tracked so every rebuild doesn't churn git. +py_src/taskito/static/ # Docs site/ From a5ffd5d545799837d23588953cb9cb9ff4d95b0b Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 02:06:18 +0530 Subject: [PATCH 24/42] =?UTF-8?q?chore(dashboard-next):=20rename=20TanStac?= =?UTF-8?q?kRouterVite=20=E2=86=92=20tanstackRouter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard-next/vite.config.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dashboard-next/vite.config.ts b/dashboard-next/vite.config.ts index f428239..ab0bd06 100644 --- a/dashboard-next/vite.config.ts +++ b/dashboard-next/vite.config.ts @@ -1,5 +1,5 @@ import path from "node:path"; -import { TanStackRouterVite } from "@tanstack/router-plugin/vite"; +import { tanstackRouter } from "@tanstack/router-plugin/vite"; import tailwindcss from "@tailwindcss/vite"; import react from "@vitejs/plugin-react"; import { defineConfig } from "vite"; @@ -8,7 +8,8 @@ const backend = "http://127.0.0.1:8080"; export default defineConfig({ plugins: [ - TanStackRouterVite({ + tanstackRouter({ + target: "react", routesDirectory: "./src/routes", generatedRouteTree: "./src/route-tree.gen.ts", quoteStyle: "double", @@ -23,7 +24,7 @@ export default defineConfig({ }, }, build: { - outDir: "dist", + outDir: "../py_src/taskito/static/dashboard", emptyOutDir: true, sourcemap: false, target: "es2022", From 4d3a07e10eb4391780d170411e386a16ea114e13 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 02:06:28 +0530 Subject: [PATCH 25/42] fix(dashboard-next): timestamps are ms, not seconds --- .../components/dead-letter-row.tsx | 2 +- .../jobs/components/job-errors-tab.tsx | 2 +- .../features/jobs/components/job-filters.tsx | 10 ++-- .../features/jobs/components/job-logs-tab.tsx | 2 +- .../jobs/components/job-overview-tab.tsx | 13 +++-- .../features/logs/components/log-stream.tsx | 2 +- .../metrics/components/latency-chart.tsx | 2 +- .../overview/components/recent-jobs.tsx | 2 +- .../workers/components/workers-table.tsx | 6 +-- dashboard-next/src/lib/time.test.ts | 54 +++++++++++++++++++ dashboard-next/src/lib/time.ts | 27 ++++++++-- 11 files changed, 96 insertions(+), 26 deletions(-) create mode 100644 dashboard-next/src/lib/time.test.ts diff --git a/dashboard-next/src/features/dead-letters/components/dead-letter-row.tsx b/dashboard-next/src/features/dead-letters/components/dead-letter-row.tsx index abf2f48..4d75e03 100644 --- a/dashboard-next/src/features/dead-letters/components/dead-letter-row.tsx +++ b/dashboard-next/src/features/dead-letters/components/dead-letter-row.tsx @@ -32,7 +32,7 @@ export function DeadLetterRow({ item }: DeadLetterRowProps) { ) : null} - {formatRelative(item.failed_at * 1000)} + {formatRelative(item.failed_at)}
{item.error ? ( diff --git a/dashboard-next/src/features/jobs/components/job-errors-tab.tsx b/dashboard-next/src/features/jobs/components/job-errors-tab.tsx index 342ef3a..6c51582 100644 --- a/dashboard-next/src/features/jobs/components/job-errors-tab.tsx +++ b/dashboard-next/src/features/jobs/components/job-errors-tab.tsx @@ -43,7 +43,7 @@ export function JobErrorsTab({ errors, loading, error, onRetry }: JobErrorsTabPr
Attempt {err.attempt} - {formatAbsolute(err.failed_at * 1000)} + {formatAbsolute(err.failed_at)}
diff --git a/dashboard-next/src/features/jobs/components/job-filters.tsx b/dashboard-next/src/features/jobs/components/job-filters.tsx
index c7db126..fca505c 100644
--- a/dashboard-next/src/features/jobs/components/job-filters.tsx
+++ b/dashboard-next/src/features/jobs/components/job-filters.tsx
@@ -165,8 +165,8 @@ function DateInput({
   value: number | undefined;
   onChange: (ts: number | undefined) => void;
 }) {
-  // Backend stores unix seconds;  uses "YYYY-MM-DDTHH:mm".
-  const localValue = value ? unixToLocalDatetime(value) : "";
+  // Backend stores unix milliseconds;  uses "YYYY-MM-DDTHH:mm".
+  const localValue = value ? msToLocalDatetime(value) : "";
   return (
     
{open ? (
    {group.entries.map((entry) => ( diff --git a/dashboard-next/src/features/dead-letters/components/dead-letter-list.tsx b/dashboard-next/src/features/dead-letters/components/dead-letter-list.tsx index 62a3b79..5b30a24 100644 --- a/dashboard-next/src/features/dead-letters/components/dead-letter-list.tsx +++ b/dashboard-next/src/features/dead-letters/components/dead-letter-list.tsx @@ -52,7 +52,7 @@ export function DeadLetterList({ items, view, loading, error, onRetry }: DeadLet return (
    {groups.map((g) => ( - + ))}
    ); diff --git a/dashboard-next/src/features/dead-letters/index.ts b/dashboard-next/src/features/dead-letters/index.ts index bbb8e24..7c2fb52 100644 --- a/dashboard-next/src/features/dead-letters/index.ts +++ b/dashboard-next/src/features/dead-letters/index.ts @@ -1,3 +1,8 @@ export * from "./components"; export * from "./hooks"; -export { type DeadLetterGroup, groupByError } from "./utils"; +export { + type DeadLetterGroup, + extractExceptionClass, + extractReason, + groupByError, +} from "./utils"; diff --git a/dashboard-next/src/features/dead-letters/utils.test.ts b/dashboard-next/src/features/dead-letters/utils.test.ts index 886a250..5e95d2b 100644 --- a/dashboard-next/src/features/dead-letters/utils.test.ts +++ b/dashboard-next/src/features/dead-letters/utils.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; import type { DeadLetter } from "@/lib/api-types"; -import { groupByError } from "./utils"; +import { extractExceptionClass, extractReason, groupByError } from "./utils"; function dl(overrides: Partial): DeadLetter { return { @@ -10,59 +10,119 @@ function dl(overrides: Partial): DeadLetter { queue: overrides.queue ?? "default", error: overrides.error ?? null, retry_count: overrides.retry_count ?? 3, - failed_at: overrides.failed_at ?? 1_700_000_000, + failed_at: overrides.failed_at ?? 1_700_000_000_000, }; } +const TRACEBACK_VALUE_ERROR = `Traceback (most recent call last): + File "demo.py", line 100, in hard_fail + raise ValueError(f"permanent failure: {reason}") +ValueError: permanent failure: db timeout`; + +const TRACEBACK_BAD_PAYLOAD = `Traceback (most recent call last): + File "demo.py", line 100, in hard_fail + raise ValueError(f"permanent failure: {reason}") +ValueError: permanent failure: bad payload`; + +const TRACEBACK_CONNECTION_ERROR = `Traceback (most recent call last): + File "demo.py", line 115, in external_api_call + raise ConnectionError(f"upstream {endpoint} unreachable") +ConnectionError: upstream /v1/users unreachable`; + +describe("extractExceptionClass", () => { + it("pulls the class name from a standard traceback", () => { + expect(extractExceptionClass(TRACEBACK_VALUE_ERROR)).toBe("ValueError"); + expect(extractExceptionClass(TRACEBACK_CONNECTION_ERROR)).toBe("ConnectionError"); + }); + + it("handles custom exception class names", () => { + const traceback = "something\nMyCustomError: oops"; + expect(extractExceptionClass(traceback)).toBe("MyCustomError"); + }); + + it("falls back to Error on unparseable input", () => { + expect(extractExceptionClass(null)).toBe("Error"); + expect(extractExceptionClass("")).toBe("Error"); + expect(extractExceptionClass("some random text")).toBe("Error"); + }); +}); + +describe("extractReason", () => { + it("returns the message after the class on the last line", () => { + expect(extractReason(TRACEBACK_VALUE_ERROR)).toBe("permanent failure: db timeout"); + }); + + it("returns the whole line when there's no colon", () => { + expect(extractReason("something broke")).toBe("something broke"); + }); + + it("placeholders null/empty", () => { + expect(extractReason(null)).toMatch(/no error captured/i); + expect(extractReason(" ")).toMatch(/no error captured/i); + }); +}); + describe("groupByError", () => { - it("returns empty array when no items", () => { + it("returns empty for empty input", () => { expect(groupByError([])).toEqual([]); }); - it("collapses entries with the same error into one group", () => { + it("groups same-task same-exception-class failures together", () => { const items = [ - dl({ id: "a", error: "timeout", failed_at: 1000 }), - dl({ id: "b", error: "timeout", failed_at: 2000 }), - dl({ id: "c", error: "divide by zero", failed_at: 1500 }), + dl({ id: "a", task_name: "hard_fail", error: TRACEBACK_VALUE_ERROR }), + dl({ id: "b", task_name: "hard_fail", error: TRACEBACK_BAD_PAYLOAD }), ]; const groups = groupByError(items); - expect(groups).toHaveLength(2); - const timeout = groups.find((g) => g.error === "timeout")!; - expect(timeout.entries).toHaveLength(2); + expect(groups).toHaveLength(1); + expect(groups[0]!.key).toBe("hard_fail::ValueError"); + expect(groups[0]!.entries).toHaveLength(2); + expect(groups[0]!.reasons).toContain("permanent failure: db timeout"); + expect(groups[0]!.reasons).toContain("permanent failure: bad payload"); }); - it("sorts entries within a group newest-first", () => { + it("separates different tasks even when the exception class matches", () => { const items = [ - dl({ id: "old", error: "oops", failed_at: 100 }), - dl({ id: "new", error: "oops", failed_at: 900 }), - dl({ id: "middle", error: "oops", failed_at: 500 }), + dl({ id: "a", task_name: "hard_fail", error: TRACEBACK_VALUE_ERROR }), + dl({ id: "b", task_name: "other_task", error: TRACEBACK_VALUE_ERROR }), ]; const groups = groupByError(items); - expect(groups[0]!.entries.map((e) => e.id)).toEqual(["new", "middle", "old"]); + expect(groups).toHaveLength(2); }); - it("orders groups by the freshest failure first", () => { + it("separates different exception classes in the same task", () => { const items = [ - dl({ id: "a", error: "E1", failed_at: 10 }), - dl({ id: "b", error: "E2", failed_at: 50 }), - dl({ id: "c", error: "E3", failed_at: 30 }), + dl({ id: "a", task_name: "f", error: TRACEBACK_VALUE_ERROR }), + dl({ id: "b", task_name: "f", error: TRACEBACK_CONNECTION_ERROR }), ]; const groups = groupByError(items); - expect(groups.map((g) => g.error)).toEqual(["E2", "E3", "E1"]); + expect(groups).toHaveLength(2); }); - it("treats null and whitespace-only errors as one labeled group", () => { - const items = [dl({ id: "a", error: null }), dl({ id: "b", error: " " })]; + it("sorts entries within a group newest-first", () => { + const items = [ + dl({ id: "old", error: TRACEBACK_VALUE_ERROR, failed_at: 100 }), + dl({ id: "new", error: TRACEBACK_VALUE_ERROR, failed_at: 900 }), + ]; + const [group] = groupByError(items); + expect(group!.entries.map((e) => e.id)).toEqual(["new", "old"]); + expect(group!.latestFailedAt).toBe(900); + }); + + it("orders groups by freshest failure first", () => { + const items = [ + dl({ id: "a", task_name: "t1", error: TRACEBACK_VALUE_ERROR, failed_at: 10 }), + dl({ id: "b", task_name: "t2", error: TRACEBACK_VALUE_ERROR, failed_at: 50 }), + dl({ id: "c", task_name: "t3", error: TRACEBACK_VALUE_ERROR, failed_at: 30 }), + ]; const groups = groupByError(items); - expect(groups).toHaveLength(1); - expect(groups[0]!.error).toMatch(/no error captured/i); + expect(groups.map((g) => g.taskName)).toEqual(["t2", "t3", "t1"]); }); it("dedupes queues across entries in a group", () => { const items = [ - dl({ id: "a", error: "boom", queue: "emails" }), - dl({ id: "b", error: "boom", queue: "emails" }), - dl({ id: "c", error: "boom", queue: "reports" }), + dl({ id: "a", queue: "emails", error: TRACEBACK_VALUE_ERROR }), + dl({ id: "b", queue: "emails", error: TRACEBACK_VALUE_ERROR }), + dl({ id: "c", queue: "reports", error: TRACEBACK_VALUE_ERROR }), ]; const [group] = groupByError(items); expect(group!.queues).toEqual(["emails", "reports"]); diff --git a/dashboard-next/src/features/dead-letters/utils.ts b/dashboard-next/src/features/dead-letters/utils.ts index 839a6db..ab7294e 100644 --- a/dashboard-next/src/features/dead-letters/utils.ts +++ b/dashboard-next/src/features/dead-letters/utils.ts @@ -1,48 +1,110 @@ import type { DeadLetter } from "@/lib/api-types"; export interface DeadLetterGroup { - /** Canonical error message shared by every entry in the group. */ - error: string; - /** Sample task name used as the group label. */ - sampleTask: string; + /** Stable key: `taskName::ExceptionClass`. */ + key: string; + /** Task name shared across the group (distinct tasks go in distinct groups). */ + taskName: string; + /** Exception class extracted from the traceback, or "Error" if unparseable. */ + exceptionClass: string; /** Queues represented in the group (deduped). */ queues: string[]; + /** Distinct error messages seen across this group (deduped, newest-first). */ + reasons: string[]; + /** Most-recent failure timestamp in the group. */ + latestFailedAt: number; /** All dead letters in this group, newest first. */ entries: DeadLetter[]; } const EMPTY_ERROR_LABEL = "(no error captured)"; +const UNKNOWN_EXCEPTION = "Error"; /** - * Group dead letters by error message. + * Extract the exception class from a Python traceback string. * - * Items with the same error are collapsed into a single group so an operator - * isn't drowning in 500 identical SQL timeouts. Groups are returned ordered - * by most recent failure first. + * Python tracebacks end with a line like ``ValueError: permanent failure…``. + * We read the last non-blank line and pull the token before the first `:`. + * Falls back to "Error" if the input isn't a recognisable traceback. + */ +export function extractExceptionClass(error: string | null | undefined): string { + if (!error) return UNKNOWN_EXCEPTION; + const trimmed = error.trim(); + if (!trimmed) return UNKNOWN_EXCEPTION; + const lines = trimmed.split("\n"); + for (let i = lines.length - 1; i >= 0; i--) { + const line = (lines[i] ?? "").trim(); + if (!line) continue; + const match = line.match(/^([A-Za-z_][A-Za-z0-9_.]*(?:Error|Exception|Warning))(?::|$)/); + if (match?.[1]) return match[1]; + // Any colon-delimited identifier on the last non-blank line (best-effort). + const fallback = line.match(/^([A-Za-z_][A-Za-z0-9_.]*)\s*:/); + if (fallback?.[1]) return fallback[1]; + break; + } + return UNKNOWN_EXCEPTION; +} + +/** + * Extract the one-line reason from a traceback — the text after the exception + * class on the final traceback line. Useful for a group's sub-summary. + */ +export function extractReason(error: string | null | undefined): string { + if (!error) return EMPTY_ERROR_LABEL; + const trimmed = error.trim(); + if (!trimmed) return EMPTY_ERROR_LABEL; + const lines = trimmed.split("\n"); + for (let i = lines.length - 1; i >= 0; i--) { + const line = (lines[i] ?? "").trim(); + if (!line) continue; + const idx = line.indexOf(":"); + if (idx >= 0 && idx < line.length - 1) { + return line.slice(idx + 1).trim() || line; + } + return line; + } + return EMPTY_ERROR_LABEL; +} + +/** + * Group dead letters by ``(task_name, exception class)``. + * + * Python tracebacks differ in their message text but share an exception + * class — grouping on the class collapses "db timeout" / "bad payload" / + * "schema drift" into a single actionable bucket. Individual reason strings + * are surfaced as a sub-summary so the information isn't lost. */ export function groupByError(items: DeadLetter[]): DeadLetterGroup[] { - const byError = new Map(); + const groups = new Map(); + for (const item of items) { - const key = (item.error ?? "").trim() || EMPTY_ERROR_LABEL; - const existing = byError.get(key); + const exceptionClass = extractExceptionClass(item.error); + const key = `${item.task_name}::${exceptionClass}`; + const reason = extractReason(item.error); + + const existing = groups.get(key); if (existing) { existing.entries.push(item); if (!existing.queues.includes(item.queue)) existing.queues.push(item.queue); + if (!existing.reasons.includes(reason)) existing.reasons.push(reason); + if (item.failed_at > existing.latestFailedAt) existing.latestFailedAt = item.failed_at; } else { - byError.set(key, { - error: key, - sampleTask: item.task_name, + groups.set(key, { + key, + taskName: item.task_name, + exceptionClass, queues: [item.queue], + reasons: [reason], + latestFailedAt: item.failed_at, entries: [item], }); } } - // Ensure entries within a group are newest-first, then order groups by - // the most recent failure across all entries. - for (const group of byError.values()) { + + // Sort entries within each group newest-first, then sort groups by their + // most-recent failure so the freshest problems surface first. + for (const group of groups.values()) { group.entries.sort((a, b) => b.failed_at - a.failed_at); } - return [...byError.values()].sort( - (a, b) => (b.entries[0]?.failed_at ?? 0) - (a.entries[0]?.failed_at ?? 0), - ); + return [...groups.values()].sort((a, b) => b.latestFailedAt - a.latestFailedAt); } From f05723b7da6d8d82fde97b908da1adc49fa32de9 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 02:06:50 +0530 Subject: [PATCH 28/42] fix(dashboard-next): queue failed/dead column + per-row pause state --- .../overview/components/queue-breakdown.tsx | 32 ++--- .../queues/components/queues-table.tsx | 109 ++++++++++-------- 2 files changed, 80 insertions(+), 61 deletions(-) diff --git a/dashboard-next/src/features/overview/components/queue-breakdown.tsx b/dashboard-next/src/features/overview/components/queue-breakdown.tsx index 6ca568e..e016276 100644 --- a/dashboard-next/src/features/overview/components/queue-breakdown.tsx +++ b/dashboard-next/src/features/overview/components/queue-breakdown.tsx @@ -14,6 +14,7 @@ interface Row { completed: number; failed: number; dead: number; + failedTotal: number; paused: boolean; } @@ -36,15 +37,20 @@ export function QueueBreakdown({ if (!queueStats) return []; const pausedSet = new Set(paused ?? []); return Object.entries(queueStats) - .map(([name, s]) => ({ - name, - pending: s.pending ?? 0, - running: s.running ?? 0, - completed: s.completed ?? 0, - failed: s.failed ?? 0, - dead: s.dead ?? 0, - paused: pausedSet.has(name), - })) + .map(([name, s]) => { + const failed = s.failed ?? 0; + const dead = s.dead ?? 0; + return { + name, + pending: s.pending ?? 0, + running: s.running ?? 0, + completed: s.completed ?? 0, + failed, + dead, + failedTotal: failed + dead, + paused: pausedSet.has(name), + }; + }) .sort((a, b) => a.name.localeCompare(b.name)); }, [queueStats, paused]); @@ -84,10 +90,10 @@ export function QueueBreakdown({ ), }, { - accessorKey: "failed", - header: "Failed", - cell: ({ row }) => { - const total = row.original.failed + row.original.dead; + accessorKey: "failedTotal", + header: "Failed / dead", + cell: ({ getValue }) => { + const total = getValue(); return ( 0 ? "text-danger" : "text-[var(--fg-muted)]"}`} diff --git a/dashboard-next/src/features/queues/components/queues-table.tsx b/dashboard-next/src/features/queues/components/queues-table.tsx index ee61a18..90a164f 100644 --- a/dashboard-next/src/features/queues/components/queues-table.tsx +++ b/dashboard-next/src/features/queues/components/queues-table.tsx @@ -19,6 +19,7 @@ interface QueueRow { completed: number; failed: number; dead: number; + failedTotal: number; } interface QueuesTableProps { @@ -30,22 +31,24 @@ interface QueuesTableProps { } export function QueuesTable({ stats, paused, loading, error, onRetry }: QueuesTableProps) { - const pauseMutation = usePauseQueue(); - const resumeMutation = useResumeQueue(); - const rows = useMemo(() => { if (!stats) return []; const pausedSet = new Set(paused ?? []); return Object.entries(stats) - .map(([name, s]) => ({ - name, - paused: pausedSet.has(name), - pending: s.pending ?? 0, - running: s.running ?? 0, - completed: s.completed ?? 0, - failed: s.failed ?? 0, - dead: s.dead ?? 0, - })) + .map(([name, s]) => { + const failed = s.failed ?? 0; + const dead = s.dead ?? 0; + return { + name, + paused: pausedSet.has(name), + pending: s.pending ?? 0, + running: s.running ?? 0, + completed: s.completed ?? 0, + failed, + dead, + failedTotal: failed + dead, + }; + }) .sort((a, b) => a.name.localeCompare(b.name)); }, [stats, paused]); @@ -85,10 +88,10 @@ export function QueuesTable({ stats, paused, loading, error, onRetry }: QueuesTa ), }, { - accessorKey: "failed", - header: "Failed", - cell: ({ row }) => { - const total = row.original.failed + row.original.dead; + accessorKey: "failedTotal", + header: "Failed / dead", + cell: ({ getValue }) => { + const total = getValue(); return ( 0 ? "text-danger" : "text-[var(--fg-muted)]"}`} @@ -101,40 +104,12 @@ export function QueuesTable({ stats, paused, loading, error, onRetry }: QueuesTa { id: "actions", header: "", - cell: ({ row }) => { - const name = row.original.name; - if (row.original.paused) { - return ( - - ); - } - return ( - - ); - }, + cell: ({ row }) => ( + + ), }, ], - [pauseMutation, resumeMutation], + [], ); if (error) { @@ -159,3 +134,41 @@ export function QueuesTable({ stats, paused, loading, error, onRetry }: QueuesTa return r.name} />; } + +/** + * Per-row pause/resume button. Owning the mutation locally ensures only + * the clicked row is disabled while its request is in flight — other rows + * stay clickable. + */ +function PauseResumeCell({ name, paused }: { name: string; paused: boolean }) { + const pause = usePauseQueue(); + const resume = useResumeQueue(); + if (paused) { + return ( + + ); + } + return ( + + ); +} From a124b0f0ca4f418dc649cb408775f56d07f73fdc Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 02:06:56 +0530 Subject: [PATCH 29/42] fix(dashboard-next): CB age formatter, Ms zero, DataTable keyboard --- dashboard-next/src/components/ui/data-table.tsx | 16 +++++++++++++++- .../components/circuit-breakers-table.tsx | 7 ++----- .../metrics/components/metrics-table.tsx | 11 +++++------ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/dashboard-next/src/components/ui/data-table.tsx b/dashboard-next/src/components/ui/data-table.tsx index 85d8d0f..4fb8437 100644 --- a/dashboard-next/src/components/ui/data-table.tsx +++ b/dashboard-next/src/components/ui/data-table.tsx @@ -103,8 +103,22 @@ export function DataTable({ table.getRowModel().rows.map((row: Row, index) => ( onRowClick(row.original) : undefined} + onKeyDown={ + onRowClick + ? (event) => { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + onRowClick(row.original); + } + } + : undefined + } > {row.getVisibleCells().map((cell) => ( diff --git a/dashboard-next/src/features/circuit-breakers/components/circuit-breakers-table.tsx b/dashboard-next/src/features/circuit-breakers/components/circuit-breakers-table.tsx index 87d4d05..7e6cdca 100644 --- a/dashboard-next/src/features/circuit-breakers/components/circuit-breakers-table.tsx +++ b/dashboard-next/src/features/circuit-breakers/components/circuit-breakers-table.tsx @@ -8,7 +8,7 @@ import { ErrorState } from "@/components/ui/error-state"; import { Skeleton } from "@/components/ui/skeleton"; import type { CircuitBreaker } from "@/lib/api-types"; import { CIRCUIT_LABEL, CIRCUIT_TONE } from "@/lib/status"; -import { formatDuration } from "@/lib/time"; +import { formatDuration, formatRelative } from "@/lib/time"; interface CircuitBreakersTableProps { breakers: CircuitBreaker[] | undefined; @@ -76,11 +76,8 @@ export function CircuitBreakersTable({ cell: ({ getValue }) => { const v = getValue(); if (!v) return ; - const ago = Math.round((Date.now() - v * 1000) / 1000); return ( - - {ago < 60 ? `${ago}s ago` : `${Math.round(ago / 60)}m ago`} - + {formatRelative(v)} ); }, }, diff --git a/dashboard-next/src/features/metrics/components/metrics-table.tsx b/dashboard-next/src/features/metrics/components/metrics-table.tsx index b4a4f7f..f68ee76 100644 --- a/dashboard-next/src/features/metrics/components/metrics-table.tsx +++ b/dashboard-next/src/features/metrics/components/metrics-table.tsx @@ -119,10 +119,9 @@ export function MetricsTable({ metrics, loading, error, onRetry }: MetricsTableP return r.task} />; } -function Ms({ value }: { value: number }) { - return ( - - {value > 0 ? `${value.toFixed(1)}ms` : "—"} - - ); +function Ms({ value }: { value: number | null | undefined }) { + if (value == null || !Number.isFinite(value)) { + return ; + } + return {`${value.toFixed(1)}ms`}; } From 1a501c94bf7e8c5171ebd436ea37b4f7a3849b8f Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 02:11:16 +0530 Subject: [PATCH 30/42] refactor(dashboard-next): use barrel imports across the app --- .../src/components/layout/mobile-menu.tsx | 2 +- dashboard-next/src/components/ui/command.tsx | 2 +- .../src/components/ui/confirm-dialog.tsx | 4 ++-- .../src/components/ui/data-table.tsx | 9 +-------- .../ui/destructive-confirm-dialog.tsx | 6 +++--- .../src/components/ui/error-state.tsx | 2 +- .../src/components/ui/pagination.tsx | 2 +- dashboard-next/src/components/ui/stat-card.tsx | 2 +- .../components/circuit-breakers-table.tsx | 6 +----- .../src/features/circuit-breakers/hooks.ts | 2 +- .../components/dead-letter-group-row.tsx | 3 +-- .../components/dead-letter-list.tsx | 4 +--- .../components/dead-letter-row.tsx | 3 +-- .../src/features/dead-letters/hooks.ts | 2 +- .../features/jobs/components/job-actions.tsx | 3 +-- .../features/jobs/components/job-dag-tab.tsx | 4 +--- .../jobs/components/job-errors-tab.tsx | 4 +--- .../features/jobs/components/job-filters.tsx | 18 +++++++++++++----- .../features/jobs/components/job-logs-tab.tsx | 4 +--- .../jobs/components/job-overview-tab.tsx | 2 +- .../jobs/components/job-replay-tab.tsx | 4 +--- .../jobs/components/job-search-bar.tsx | 2 +- .../src/features/jobs/components/job-table.tsx | 6 +----- dashboard-next/src/features/jobs/hooks.ts | 2 +- .../features/logs/components/log-filters.tsx | 8 ++++---- .../features/logs/components/log-stream.tsx | 4 +--- dashboard-next/src/features/logs/hooks.ts | 2 +- .../metrics/components/latency-chart.tsx | 3 +-- .../metrics/components/metrics-table.tsx | 5 +---- .../metrics/components/task-selector.tsx | 8 +------- .../metrics/components/throughput-chart.tsx | 3 +-- dashboard-next/src/features/metrics/hooks.ts | 2 +- .../overview/components/queue-breakdown.tsx | 5 +---- .../overview/components/recent-jobs.tsx | 5 +---- .../overview/components/stats-grid.tsx | 3 +-- .../components/throughput-sparkline.tsx | 3 +-- dashboard-next/src/features/overview/hooks.ts | 2 +- .../queues/components/queues-table.tsx | 7 +------ dashboard-next/src/features/queues/hooks.ts | 2 +- .../resources/components/resources-table.tsx | 6 +----- dashboard-next/src/features/resources/hooks.ts | 2 +- .../system/components/interception-table.tsx | 4 +--- .../features/system/components/proxy-table.tsx | 4 +--- dashboard-next/src/features/system/hooks.ts | 2 +- .../workers/components/workers-table.tsx | 6 +----- dashboard-next/src/features/workers/hooks.ts | 2 +- dashboard-next/src/routes/__root.tsx | 2 +- dashboard-next/src/routes/dead-letters.tsx | 4 +--- dashboard-next/src/routes/jobs/$id.tsx | 13 +++++++++---- dashboard-next/src/routes/jobs/index.tsx | 2 +- 50 files changed, 76 insertions(+), 131 deletions(-) diff --git a/dashboard-next/src/components/layout/mobile-menu.tsx b/dashboard-next/src/components/layout/mobile-menu.tsx index 9b67ae9..5141219 100644 --- a/dashboard-next/src/components/layout/mobile-menu.tsx +++ b/dashboard-next/src/components/layout/mobile-menu.tsx @@ -60,7 +60,7 @@ export function MobileMenu() { // Close on navigation useEffect(() => { setOpen(false); - }, [pathname]); + }, []); return ( diff --git a/dashboard-next/src/components/ui/command.tsx b/dashboard-next/src/components/ui/command.tsx index 04e116a..727da07 100644 --- a/dashboard-next/src/components/ui/command.tsx +++ b/dashboard-next/src/components/ui/command.tsx @@ -1,8 +1,8 @@ import { Command as CommandPrimitive } from "cmdk"; import { Search } from "lucide-react"; import { type ComponentPropsWithoutRef, forwardRef } from "react"; -import { Dialog, DialogContent } from "@/components/ui/dialog"; import { cn } from "@/lib/cn"; +import { Dialog, DialogContent } from "./dialog"; const Command = forwardRef>( ({ className, ...props }, ref) => ( diff --git a/dashboard-next/src/components/ui/confirm-dialog.tsx b/dashboard-next/src/components/ui/confirm-dialog.tsx index 1b8122e..5466c8a 100644 --- a/dashboard-next/src/components/ui/confirm-dialog.tsx +++ b/dashboard-next/src/components/ui/confirm-dialog.tsx @@ -1,5 +1,5 @@ import type { ReactNode } from "react"; -import { Button } from "@/components/ui/button"; +import { Button } from "./button"; import { Dialog, DialogContent, @@ -7,7 +7,7 @@ import { DialogFooter, DialogHeader, DialogTitle, -} from "@/components/ui/dialog"; +} from "./dialog"; interface ConfirmDialogProps { open: boolean; diff --git a/dashboard-next/src/components/ui/data-table.tsx b/dashboard-next/src/components/ui/data-table.tsx index 4fb8437..9e9ae81 100644 --- a/dashboard-next/src/components/ui/data-table.tsx +++ b/dashboard-next/src/components/ui/data-table.tsx @@ -9,15 +9,8 @@ import { } from "@tanstack/react-table"; import { ArrowDown, ArrowUp, ChevronsUpDown } from "lucide-react"; import { type ReactNode, useState } from "react"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; import { cn } from "@/lib/cn"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./table"; interface DataTableProps { columns: ColumnDef[]; diff --git a/dashboard-next/src/components/ui/destructive-confirm-dialog.tsx b/dashboard-next/src/components/ui/destructive-confirm-dialog.tsx index c7fc4d2..901595c 100644 --- a/dashboard-next/src/components/ui/destructive-confirm-dialog.tsx +++ b/dashboard-next/src/components/ui/destructive-confirm-dialog.tsx @@ -1,5 +1,5 @@ import { type ReactNode, useEffect, useState } from "react"; -import { Button } from "@/components/ui/button"; +import { Button } from "./button"; import { Dialog, DialogContent, @@ -7,8 +7,8 @@ import { DialogFooter, DialogHeader, DialogTitle, -} from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; +} from "./dialog"; +import { Input } from "./input"; interface DestructiveConfirmDialogProps { open: boolean; diff --git a/dashboard-next/src/components/ui/error-state.tsx b/dashboard-next/src/components/ui/error-state.tsx index 3822ece..718ac6b 100644 --- a/dashboard-next/src/components/ui/error-state.tsx +++ b/dashboard-next/src/components/ui/error-state.tsx @@ -1,7 +1,7 @@ import { AlertTriangle, type LucideIcon } from "lucide-react"; import type { ReactNode } from "react"; -import { Button } from "@/components/ui/button"; import { cn } from "@/lib/cn"; +import { Button } from "./button"; interface ErrorStateProps { icon?: LucideIcon; diff --git a/dashboard-next/src/components/ui/pagination.tsx b/dashboard-next/src/components/ui/pagination.tsx index e6238fe..34bff4d 100644 --- a/dashboard-next/src/components/ui/pagination.tsx +++ b/dashboard-next/src/components/ui/pagination.tsx @@ -1,6 +1,6 @@ import { ChevronLeft, ChevronRight } from "lucide-react"; -import { Button } from "@/components/ui/button"; import { cn } from "@/lib/cn"; +import { Button } from "./button"; interface PaginationProps { page: number; diff --git a/dashboard-next/src/components/ui/stat-card.tsx b/dashboard-next/src/components/ui/stat-card.tsx index 33f8bb8..e4db07e 100644 --- a/dashboard-next/src/components/ui/stat-card.tsx +++ b/dashboard-next/src/components/ui/stat-card.tsx @@ -1,6 +1,6 @@ import { forwardRef, type HTMLAttributes, type ReactNode } from "react"; -import { Card } from "@/components/ui/card"; import { cn } from "@/lib/cn"; +import { Card } from "./card"; interface StatCardProps extends HTMLAttributes { label: string; diff --git a/dashboard-next/src/features/circuit-breakers/components/circuit-breakers-table.tsx b/dashboard-next/src/features/circuit-breakers/components/circuit-breakers-table.tsx index 7e6cdca..25985f7 100644 --- a/dashboard-next/src/features/circuit-breakers/components/circuit-breakers-table.tsx +++ b/dashboard-next/src/features/circuit-breakers/components/circuit-breakers-table.tsx @@ -1,11 +1,7 @@ import type { ColumnDef } from "@tanstack/react-table"; import { CircuitBoard } from "lucide-react"; import { useMemo } from "react"; -import { Badge } from "@/components/ui/badge"; -import { DataTable } from "@/components/ui/data-table"; -import { EmptyState } from "@/components/ui/empty-state"; -import { ErrorState } from "@/components/ui/error-state"; -import { Skeleton } from "@/components/ui/skeleton"; +import { Badge, DataTable, EmptyState, ErrorState, Skeleton } from "@/components/ui"; import type { CircuitBreaker } from "@/lib/api-types"; import { CIRCUIT_LABEL, CIRCUIT_TONE } from "@/lib/status"; import { formatDuration, formatRelative } from "@/lib/time"; diff --git a/dashboard-next/src/features/circuit-breakers/hooks.ts b/dashboard-next/src/features/circuit-breakers/hooks.ts index 546447e..52214b3 100644 --- a/dashboard-next/src/features/circuit-breakers/hooks.ts +++ b/dashboard-next/src/features/circuit-breakers/hooks.ts @@ -1,5 +1,5 @@ import { useQuery } from "@tanstack/react-query"; -import { useRefreshInterval } from "@/providers/refresh-interval-provider"; +import { useRefreshInterval } from "@/providers"; import { fetchCircuitBreakers } from "./api"; export function useCircuitBreakers() { diff --git a/dashboard-next/src/features/dead-letters/components/dead-letter-group-row.tsx b/dashboard-next/src/features/dead-letters/components/dead-letter-group-row.tsx index 016a509..17b1e44 100644 --- a/dashboard-next/src/features/dead-letters/components/dead-letter-group-row.tsx +++ b/dashboard-next/src/features/dead-letters/components/dead-letter-group-row.tsx @@ -1,7 +1,6 @@ import { ChevronDown, ChevronRight, RotateCcw } from "lucide-react"; import { useState } from "react"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; +import { Badge, Button } from "@/components/ui"; import { formatRelative } from "@/lib/time"; import { useRetryDeadLetter } from "../hooks"; import type { DeadLetterGroup } from "../utils"; diff --git a/dashboard-next/src/features/dead-letters/components/dead-letter-list.tsx b/dashboard-next/src/features/dead-letters/components/dead-letter-list.tsx index 5b30a24..dcca3a6 100644 --- a/dashboard-next/src/features/dead-letters/components/dead-letter-list.tsx +++ b/dashboard-next/src/features/dead-letters/components/dead-letter-list.tsx @@ -1,8 +1,6 @@ import { Skull } from "lucide-react"; import { useMemo } from "react"; -import { EmptyState } from "@/components/ui/empty-state"; -import { ErrorState } from "@/components/ui/error-state"; -import { Skeleton } from "@/components/ui/skeleton"; +import { EmptyState, ErrorState, Skeleton } from "@/components/ui"; import type { DeadLetter } from "@/lib/api-types"; import { groupByError } from "../utils"; import { DeadLetterGroupRow } from "./dead-letter-group-row"; diff --git a/dashboard-next/src/features/dead-letters/components/dead-letter-row.tsx b/dashboard-next/src/features/dead-letters/components/dead-letter-row.tsx index 4d75e03..8a94ee2 100644 --- a/dashboard-next/src/features/dead-letters/components/dead-letter-row.tsx +++ b/dashboard-next/src/features/dead-letters/components/dead-letter-row.tsx @@ -1,7 +1,6 @@ import { Link } from "@tanstack/react-router"; import { RotateCcw } from "lucide-react"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; +import { Badge, Button } from "@/components/ui"; import type { DeadLetter } from "@/lib/api-types"; import { formatRelative } from "@/lib/time"; import { useRetryDeadLetter } from "../hooks"; diff --git a/dashboard-next/src/features/dead-letters/hooks.ts b/dashboard-next/src/features/dead-letters/hooks.ts index 9ad5d21..eee3211 100644 --- a/dashboard-next/src/features/dead-letters/hooks.ts +++ b/dashboard-next/src/features/dead-letters/hooks.ts @@ -1,6 +1,6 @@ import { keepPreviousData, useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; -import { useRefreshInterval } from "@/providers/refresh-interval-provider"; +import { useRefreshInterval } from "@/providers"; import { fetchDeadLetters, purgeDeadLetters, retryDeadLetter } from "./api"; const KEY = { diff --git a/dashboard-next/src/features/jobs/components/job-actions.tsx b/dashboard-next/src/features/jobs/components/job-actions.tsx index 767ef6a..0292ddc 100644 --- a/dashboard-next/src/features/jobs/components/job-actions.tsx +++ b/dashboard-next/src/features/jobs/components/job-actions.tsx @@ -2,8 +2,7 @@ import { useNavigate } from "@tanstack/react-router"; import { Ban, Copy, RotateCcw } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; -import { Button } from "@/components/ui/button"; -import { ConfirmDialog } from "@/components/ui/confirm-dialog"; +import { Button, ConfirmDialog } from "@/components/ui"; import type { Job } from "@/lib/api-types"; import { useCancelJob, useReplayJob } from "../hooks"; import { canCancel, canReplay } from "../utils"; diff --git a/dashboard-next/src/features/jobs/components/job-dag-tab.tsx b/dashboard-next/src/features/jobs/components/job-dag-tab.tsx index 1c5a5c3..cfe578b 100644 --- a/dashboard-next/src/features/jobs/components/job-dag-tab.tsx +++ b/dashboard-next/src/features/jobs/components/job-dag-tab.tsx @@ -1,9 +1,7 @@ import { Link } from "@tanstack/react-router"; import { Workflow } from "lucide-react"; import { useMemo } from "react"; -import { EmptyState } from "@/components/ui/empty-state"; -import { ErrorState } from "@/components/ui/error-state"; -import { Skeleton } from "@/components/ui/skeleton"; +import { EmptyState, ErrorState, Skeleton } from "@/components/ui"; import type { DagData, DagEdge, DagNode, JobStatus } from "@/lib/api-types"; import { JOB_STATUS_LABEL } from "@/lib/status"; diff --git a/dashboard-next/src/features/jobs/components/job-errors-tab.tsx b/dashboard-next/src/features/jobs/components/job-errors-tab.tsx index 6c51582..9418aa0 100644 --- a/dashboard-next/src/features/jobs/components/job-errors-tab.tsx +++ b/dashboard-next/src/features/jobs/components/job-errors-tab.tsx @@ -1,7 +1,5 @@ import { AlertOctagon } from "lucide-react"; -import { EmptyState } from "@/components/ui/empty-state"; -import { ErrorState } from "@/components/ui/error-state"; -import { Skeleton } from "@/components/ui/skeleton"; +import { EmptyState, ErrorState, Skeleton } from "@/components/ui"; import type { JobError } from "@/lib/api-types"; import { formatAbsolute } from "@/lib/time"; diff --git a/dashboard-next/src/features/jobs/components/job-filters.tsx b/dashboard-next/src/features/jobs/components/job-filters.tsx index fca505c..72ee5f9 100644 --- a/dashboard-next/src/features/jobs/components/job-filters.tsx +++ b/dashboard-next/src/features/jobs/components/job-filters.tsx @@ -1,15 +1,15 @@ import { X } from "lucide-react"; import { useEffect, useState } from "react"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; import { + Button, + Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@/components/ui/select"; -import { useDebouncedValue } from "@/hooks/use-debounced-value"; +} from "@/components/ui"; +import { useDebouncedValue } from "@/hooks"; import type { JobStatus } from "@/lib/api-types"; import { cn } from "@/lib/cn"; import { JOB_STATUS_LABEL } from "@/lib/status"; @@ -79,7 +79,15 @@ export function JobFiltersBar({ filters, onChange, className }: JobFiltersBarPro onChange(next); } // eslint-disable-next-line react-hooks/exhaustive-deps -- intentional: propagate only debounced values - }, [debouncedQueue, debouncedTask, debouncedMetadata, debouncedError]); + }, [ + debouncedQueue, + debouncedTask, + debouncedMetadata, + debouncedError, + filters.task, + filters, + onChange, + ]); const activeCount = countActiveFilters(filters); diff --git a/dashboard-next/src/features/jobs/components/job-logs-tab.tsx b/dashboard-next/src/features/jobs/components/job-logs-tab.tsx index c8ec0f8..6877766 100644 --- a/dashboard-next/src/features/jobs/components/job-logs-tab.tsx +++ b/dashboard-next/src/features/jobs/components/job-logs-tab.tsx @@ -1,7 +1,5 @@ import { ScrollText } from "lucide-react"; -import { EmptyState } from "@/components/ui/empty-state"; -import { ErrorState } from "@/components/ui/error-state"; -import { Skeleton } from "@/components/ui/skeleton"; +import { EmptyState, ErrorState, Skeleton } from "@/components/ui"; import type { TaskLog } from "@/lib/api-types"; import { cn } from "@/lib/cn"; import { formatAbsolute } from "@/lib/time"; diff --git a/dashboard-next/src/features/jobs/components/job-overview-tab.tsx b/dashboard-next/src/features/jobs/components/job-overview-tab.tsx index 5401498..bfe2fe8 100644 --- a/dashboard-next/src/features/jobs/components/job-overview-tab.tsx +++ b/dashboard-next/src/features/jobs/components/job-overview-tab.tsx @@ -1,5 +1,5 @@ import type { ReactNode } from "react"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui"; import type { Job } from "@/lib/api-types"; import { formatAbsolute, formatDuration, formatRelative } from "@/lib/time"; diff --git a/dashboard-next/src/features/jobs/components/job-replay-tab.tsx b/dashboard-next/src/features/jobs/components/job-replay-tab.tsx index 76063e0..de9bd0c 100644 --- a/dashboard-next/src/features/jobs/components/job-replay-tab.tsx +++ b/dashboard-next/src/features/jobs/components/job-replay-tab.tsx @@ -1,8 +1,6 @@ import { Link } from "@tanstack/react-router"; import { RotateCcw } from "lucide-react"; -import { EmptyState } from "@/components/ui/empty-state"; -import { ErrorState } from "@/components/ui/error-state"; -import { Skeleton } from "@/components/ui/skeleton"; +import { EmptyState, ErrorState, Skeleton } from "@/components/ui"; import type { ReplayEntry } from "@/lib/api-types"; import { formatAbsolute, formatRelative } from "@/lib/time"; diff --git a/dashboard-next/src/features/jobs/components/job-search-bar.tsx b/dashboard-next/src/features/jobs/components/job-search-bar.tsx index b4e65f1..273b463 100644 --- a/dashboard-next/src/features/jobs/components/job-search-bar.tsx +++ b/dashboard-next/src/features/jobs/components/job-search-bar.tsx @@ -1,7 +1,7 @@ import { useNavigate } from "@tanstack/react-router"; import { ArrowRight, Search } from "lucide-react"; import { type FormEvent, useState } from "react"; -import { Input } from "@/components/ui/input"; +import { Input } from "@/components/ui"; import { cn } from "@/lib/cn"; interface JobSearchBarProps { diff --git a/dashboard-next/src/features/jobs/components/job-table.tsx b/dashboard-next/src/features/jobs/components/job-table.tsx index cd46b3f..63c3ea0 100644 --- a/dashboard-next/src/features/jobs/components/job-table.tsx +++ b/dashboard-next/src/features/jobs/components/job-table.tsx @@ -1,11 +1,7 @@ import { useNavigate } from "@tanstack/react-router"; import type { ColumnDef } from "@tanstack/react-table"; import { useMemo } from "react"; -import { Badge } from "@/components/ui/badge"; -import { DataTable } from "@/components/ui/data-table"; -import { EmptyState } from "@/components/ui/empty-state"; -import { ErrorState } from "@/components/ui/error-state"; -import { Skeleton } from "@/components/ui/skeleton"; +import { Badge, DataTable, EmptyState, ErrorState, Skeleton } from "@/components/ui"; import type { Job } from "@/lib/api-types"; import { JOB_STATUS_LABEL, JOB_STATUS_TONE } from "@/lib/status"; import { formatRelative } from "@/lib/time"; diff --git a/dashboard-next/src/features/jobs/hooks.ts b/dashboard-next/src/features/jobs/hooks.ts index e696ea7..4de7264 100644 --- a/dashboard-next/src/features/jobs/hooks.ts +++ b/dashboard-next/src/features/jobs/hooks.ts @@ -7,7 +7,7 @@ import { } from "@tanstack/react-query"; import { toast } from "sonner"; import type { Job } from "@/lib/api-types"; -import { useRefreshInterval } from "@/providers/refresh-interval-provider"; +import { useRefreshInterval } from "@/providers"; import { cancelJob, fetchJob, diff --git a/dashboard-next/src/features/logs/components/log-filters.tsx b/dashboard-next/src/features/logs/components/log-filters.tsx index e5a26ff..4fc0b88 100644 --- a/dashboard-next/src/features/logs/components/log-filters.tsx +++ b/dashboard-next/src/features/logs/components/log-filters.tsx @@ -1,13 +1,13 @@ import { useEffect, useState } from "react"; -import { Input } from "@/components/ui/input"; import { + Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@/components/ui/select"; -import { useDebouncedValue } from "@/hooks/use-debounced-value"; +} from "@/components/ui"; +import { useDebouncedValue } from "@/hooks"; import { cn } from "@/lib/cn"; import { LOG_LEVELS, type LogLevel } from "../api"; @@ -34,7 +34,7 @@ export function LogFilters({ task, level, onChange, className }: LogFiltersProps onChange({ task: next, level }); } // eslint-disable-next-line react-hooks/exhaustive-deps -- propagate only debounced value - }, [debounced]); + }, [debounced, task, onChange, level]); return (
    diff --git a/dashboard-next/src/features/logs/components/log-stream.tsx b/dashboard-next/src/features/logs/components/log-stream.tsx index fed1f3f..2431bfd 100644 --- a/dashboard-next/src/features/logs/components/log-stream.tsx +++ b/dashboard-next/src/features/logs/components/log-stream.tsx @@ -1,9 +1,7 @@ import { useVirtualizer } from "@tanstack/react-virtual"; import { ScrollText } from "lucide-react"; import { useEffect, useRef, useState } from "react"; -import { EmptyState } from "@/components/ui/empty-state"; -import { ErrorState } from "@/components/ui/error-state"; -import { Skeleton } from "@/components/ui/skeleton"; +import { EmptyState, ErrorState, Skeleton } from "@/components/ui"; import type { TaskLog } from "@/lib/api-types"; import { cn } from "@/lib/cn"; import { formatAbsolute } from "@/lib/time"; diff --git a/dashboard-next/src/features/logs/hooks.ts b/dashboard-next/src/features/logs/hooks.ts index 63aa27b..6a158d4 100644 --- a/dashboard-next/src/features/logs/hooks.ts +++ b/dashboard-next/src/features/logs/hooks.ts @@ -1,5 +1,5 @@ import { keepPreviousData, useQuery } from "@tanstack/react-query"; -import { useRefreshInterval } from "@/providers/refresh-interval-provider"; +import { useRefreshInterval } from "@/providers"; import { fetchLogs, type LogsQuery } from "./api"; const KEY = (q: LogsQuery) => ["logs", q.task ?? "", q.level ?? "", q.sinceSeconds, q.limit]; diff --git a/dashboard-next/src/features/metrics/components/latency-chart.tsx b/dashboard-next/src/features/metrics/components/latency-chart.tsx index b53cb99..447c51f 100644 --- a/dashboard-next/src/features/metrics/components/latency-chart.tsx +++ b/dashboard-next/src/features/metrics/components/latency-chart.tsx @@ -8,8 +8,7 @@ import { XAxis, YAxis, } from "recharts"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Skeleton } from "@/components/ui/skeleton"; +import { Card, CardContent, CardHeader, CardTitle, Skeleton } from "@/components/ui"; import type { TimeseriesBucket } from "@/lib/api-types"; interface LatencyChartProps { diff --git a/dashboard-next/src/features/metrics/components/metrics-table.tsx b/dashboard-next/src/features/metrics/components/metrics-table.tsx index f68ee76..85fb326 100644 --- a/dashboard-next/src/features/metrics/components/metrics-table.tsx +++ b/dashboard-next/src/features/metrics/components/metrics-table.tsx @@ -1,9 +1,6 @@ import type { ColumnDef } from "@tanstack/react-table"; import { useMemo } from "react"; -import { DataTable } from "@/components/ui/data-table"; -import { EmptyState } from "@/components/ui/empty-state"; -import { ErrorState } from "@/components/ui/error-state"; -import { Skeleton } from "@/components/ui/skeleton"; +import { DataTable, EmptyState, ErrorState, Skeleton } from "@/components/ui"; import type { MetricsResponse, TaskMetrics } from "@/lib/api-types"; import { formatCount, formatPercent } from "@/lib/number"; diff --git a/dashboard-next/src/features/metrics/components/task-selector.tsx b/dashboard-next/src/features/metrics/components/task-selector.tsx index 5b7c314..d79fa2c 100644 --- a/dashboard-next/src/features/metrics/components/task-selector.tsx +++ b/dashboard-next/src/features/metrics/components/task-selector.tsx @@ -1,10 +1,4 @@ -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui"; const ALL = "__all__"; diff --git a/dashboard-next/src/features/metrics/components/throughput-chart.tsx b/dashboard-next/src/features/metrics/components/throughput-chart.tsx index f9969a8..915e4dc 100644 --- a/dashboard-next/src/features/metrics/components/throughput-chart.tsx +++ b/dashboard-next/src/features/metrics/components/throughput-chart.tsx @@ -9,8 +9,7 @@ import { XAxis, YAxis, } from "recharts"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Skeleton } from "@/components/ui/skeleton"; +import { Card, CardContent, CardHeader, CardTitle, Skeleton } from "@/components/ui"; import type { TimeseriesBucket } from "@/lib/api-types"; interface ThroughputChartProps { diff --git a/dashboard-next/src/features/metrics/hooks.ts b/dashboard-next/src/features/metrics/hooks.ts index ebf1995..29d49ad 100644 --- a/dashboard-next/src/features/metrics/hooks.ts +++ b/dashboard-next/src/features/metrics/hooks.ts @@ -1,5 +1,5 @@ import { useQuery } from "@tanstack/react-query"; -import { useRefreshInterval } from "@/providers/refresh-interval-provider"; +import { useRefreshInterval } from "@/providers"; import { fetchMetrics, fetchTimeseries } from "./api"; import { type TimeRange, timeRangeConfig } from "./types"; diff --git a/dashboard-next/src/features/overview/components/queue-breakdown.tsx b/dashboard-next/src/features/overview/components/queue-breakdown.tsx index e016276..484d906 100644 --- a/dashboard-next/src/features/overview/components/queue-breakdown.tsx +++ b/dashboard-next/src/features/overview/components/queue-breakdown.tsx @@ -1,9 +1,6 @@ import type { ColumnDef } from "@tanstack/react-table"; import { useMemo } from "react"; -import { Badge } from "@/components/ui/badge"; -import { DataTable } from "@/components/ui/data-table"; -import { ErrorState } from "@/components/ui/error-state"; -import { Skeleton } from "@/components/ui/skeleton"; +import { Badge, DataTable, ErrorState, Skeleton } from "@/components/ui"; import type { QueueStatsMap } from "@/lib/api-types"; import { formatCount } from "@/lib/number"; diff --git a/dashboard-next/src/features/overview/components/recent-jobs.tsx b/dashboard-next/src/features/overview/components/recent-jobs.tsx index 55fad93..8ff4374 100644 --- a/dashboard-next/src/features/overview/components/recent-jobs.tsx +++ b/dashboard-next/src/features/overview/components/recent-jobs.tsx @@ -1,10 +1,7 @@ import { Link, useNavigate } from "@tanstack/react-router"; import type { ColumnDef } from "@tanstack/react-table"; import { useMemo } from "react"; -import { Badge } from "@/components/ui/badge"; -import { DataTable } from "@/components/ui/data-table"; -import { ErrorState } from "@/components/ui/error-state"; -import { Skeleton } from "@/components/ui/skeleton"; +import { Badge, DataTable, ErrorState, Skeleton } from "@/components/ui"; import type { Job } from "@/lib/api-types"; import { JOB_STATUS_LABEL, JOB_STATUS_TONE } from "@/lib/status"; import { formatRelative } from "@/lib/time"; diff --git a/dashboard-next/src/features/overview/components/stats-grid.tsx b/dashboard-next/src/features/overview/components/stats-grid.tsx index 00ba7d8..56397a7 100644 --- a/dashboard-next/src/features/overview/components/stats-grid.tsx +++ b/dashboard-next/src/features/overview/components/stats-grid.tsx @@ -1,6 +1,5 @@ import { CheckCircle2, Clock, Pause, Play, Skull } from "lucide-react"; -import { Skeleton } from "@/components/ui/skeleton"; -import { StatCard } from "@/components/ui/stat-card"; +import { Skeleton, StatCard } from "@/components/ui"; import type { QueueStats } from "@/lib/api-types"; import { formatCount } from "@/lib/number"; diff --git a/dashboard-next/src/features/overview/components/throughput-sparkline.tsx b/dashboard-next/src/features/overview/components/throughput-sparkline.tsx index c4c66e3..c95c00e 100644 --- a/dashboard-next/src/features/overview/components/throughput-sparkline.tsx +++ b/dashboard-next/src/features/overview/components/throughput-sparkline.tsx @@ -1,5 +1,4 @@ -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Skeleton } from "@/components/ui/skeleton"; +import { Card, CardContent, CardHeader, CardTitle, Skeleton } from "@/components/ui"; import type { TimeseriesBucket } from "@/lib/api-types"; import { formatCount } from "@/lib/number"; diff --git a/dashboard-next/src/features/overview/hooks.ts b/dashboard-next/src/features/overview/hooks.ts index edb1622..aba099d 100644 --- a/dashboard-next/src/features/overview/hooks.ts +++ b/dashboard-next/src/features/overview/hooks.ts @@ -1,5 +1,5 @@ import { useQuery } from "@tanstack/react-query"; -import { useRefreshInterval } from "@/providers/refresh-interval-provider"; +import { useRefreshInterval } from "@/providers"; import { fetchRecentJobs, fetchStats, fetchThroughput } from "./api"; // Re-export queue hooks so the Overview route can import everything it needs diff --git a/dashboard-next/src/features/queues/components/queues-table.tsx b/dashboard-next/src/features/queues/components/queues-table.tsx index 90a164f..dc242a7 100644 --- a/dashboard-next/src/features/queues/components/queues-table.tsx +++ b/dashboard-next/src/features/queues/components/queues-table.tsx @@ -1,12 +1,7 @@ import type { ColumnDef } from "@tanstack/react-table"; import { Box, Pause, Play } from "lucide-react"; import { useMemo } from "react"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { DataTable } from "@/components/ui/data-table"; -import { EmptyState } from "@/components/ui/empty-state"; -import { ErrorState } from "@/components/ui/error-state"; -import { Skeleton } from "@/components/ui/skeleton"; +import { Badge, Button, DataTable, EmptyState, ErrorState, Skeleton } from "@/components/ui"; import type { QueueStatsMap } from "@/lib/api-types"; import { formatCount } from "@/lib/number"; import { usePauseQueue, useResumeQueue } from "../hooks"; diff --git a/dashboard-next/src/features/queues/hooks.ts b/dashboard-next/src/features/queues/hooks.ts index 055df10..e842669 100644 --- a/dashboard-next/src/features/queues/hooks.ts +++ b/dashboard-next/src/features/queues/hooks.ts @@ -1,6 +1,6 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; -import { useRefreshInterval } from "@/providers/refresh-interval-provider"; +import { useRefreshInterval } from "@/providers"; import { fetchPausedQueues, fetchQueueStats, pauseQueue, resumeQueue } from "./api"; const KEY = { diff --git a/dashboard-next/src/features/resources/components/resources-table.tsx b/dashboard-next/src/features/resources/components/resources-table.tsx index 7cce0d9..f98e281 100644 --- a/dashboard-next/src/features/resources/components/resources-table.tsx +++ b/dashboard-next/src/features/resources/components/resources-table.tsx @@ -1,11 +1,7 @@ import type { ColumnDef } from "@tanstack/react-table"; import { Activity } from "lucide-react"; import { useMemo } from "react"; -import { Badge } from "@/components/ui/badge"; -import { DataTable } from "@/components/ui/data-table"; -import { EmptyState } from "@/components/ui/empty-state"; -import { ErrorState } from "@/components/ui/error-state"; -import { Skeleton } from "@/components/ui/skeleton"; +import { Badge, DataTable, EmptyState, ErrorState, Skeleton } from "@/components/ui"; import type { ResourceStatus } from "@/lib/api-types"; import { resourceTone } from "@/lib/status"; import { formatDuration } from "@/lib/time"; diff --git a/dashboard-next/src/features/resources/hooks.ts b/dashboard-next/src/features/resources/hooks.ts index ac7a822..f497a14 100644 --- a/dashboard-next/src/features/resources/hooks.ts +++ b/dashboard-next/src/features/resources/hooks.ts @@ -1,5 +1,5 @@ import { useQuery } from "@tanstack/react-query"; -import { useRefreshInterval } from "@/providers/refresh-interval-provider"; +import { useRefreshInterval } from "@/providers"; import { fetchResources } from "./api"; export function useResources() { diff --git a/dashboard-next/src/features/system/components/interception-table.tsx b/dashboard-next/src/features/system/components/interception-table.tsx index 22160c3..68a071a 100644 --- a/dashboard-next/src/features/system/components/interception-table.tsx +++ b/dashboard-next/src/features/system/components/interception-table.tsx @@ -1,8 +1,6 @@ import type { ColumnDef } from "@tanstack/react-table"; import { useMemo } from "react"; -import { DataTable } from "@/components/ui/data-table"; -import { ErrorState } from "@/components/ui/error-state"; -import { Skeleton } from "@/components/ui/skeleton"; +import { DataTable, ErrorState, Skeleton } from "@/components/ui"; import type { InterceptionStats } from "@/lib/api-types"; import { formatCount } from "@/lib/number"; diff --git a/dashboard-next/src/features/system/components/proxy-table.tsx b/dashboard-next/src/features/system/components/proxy-table.tsx index b7002f7..b8af8a0 100644 --- a/dashboard-next/src/features/system/components/proxy-table.tsx +++ b/dashboard-next/src/features/system/components/proxy-table.tsx @@ -1,8 +1,6 @@ import type { ColumnDef } from "@tanstack/react-table"; import { useMemo } from "react"; -import { DataTable } from "@/components/ui/data-table"; -import { ErrorState } from "@/components/ui/error-state"; -import { Skeleton } from "@/components/ui/skeleton"; +import { DataTable, ErrorState, Skeleton } from "@/components/ui"; import type { ProxyStats } from "@/lib/api-types"; import { formatCount } from "@/lib/number"; diff --git a/dashboard-next/src/features/system/hooks.ts b/dashboard-next/src/features/system/hooks.ts index a0f2e4f..f04b1d0 100644 --- a/dashboard-next/src/features/system/hooks.ts +++ b/dashboard-next/src/features/system/hooks.ts @@ -1,5 +1,5 @@ import { useQuery } from "@tanstack/react-query"; -import { useRefreshInterval } from "@/providers/refresh-interval-provider"; +import { useRefreshInterval } from "@/providers"; import { fetchInterceptionStats, fetchProxyStats } from "./api"; export function useProxyStats() { diff --git a/dashboard-next/src/features/workers/components/workers-table.tsx b/dashboard-next/src/features/workers/components/workers-table.tsx index edac045..d277530 100644 --- a/dashboard-next/src/features/workers/components/workers-table.tsx +++ b/dashboard-next/src/features/workers/components/workers-table.tsx @@ -1,11 +1,7 @@ import type { ColumnDef } from "@tanstack/react-table"; import { Server } from "lucide-react"; import { useMemo } from "react"; -import { Badge } from "@/components/ui/badge"; -import { DataTable } from "@/components/ui/data-table"; -import { EmptyState } from "@/components/ui/empty-state"; -import { ErrorState } from "@/components/ui/error-state"; -import { Skeleton } from "@/components/ui/skeleton"; +import { Badge, DataTable, EmptyState, ErrorState, Skeleton } from "@/components/ui"; import type { Worker } from "@/lib/api-types"; import { formatRelative } from "@/lib/time"; diff --git a/dashboard-next/src/features/workers/hooks.ts b/dashboard-next/src/features/workers/hooks.ts index 4ab8baf..27fbdad 100644 --- a/dashboard-next/src/features/workers/hooks.ts +++ b/dashboard-next/src/features/workers/hooks.ts @@ -1,5 +1,5 @@ import { useQuery } from "@tanstack/react-query"; -import { useRefreshInterval } from "@/providers/refresh-interval-provider"; +import { useRefreshInterval } from "@/providers"; import { fetchWorkers } from "./api"; export function useWorkers() { diff --git a/dashboard-next/src/routes/__root.tsx b/dashboard-next/src/routes/__root.tsx index 8392366..cecea3c 100644 --- a/dashboard-next/src/routes/__root.tsx +++ b/dashboard-next/src/routes/__root.tsx @@ -1,7 +1,7 @@ import { createRootRoute, Link, Outlet } from "@tanstack/react-router"; import { AlertTriangle, ArrowLeft, Home } from "lucide-react"; import { AppShell } from "@/components/layout"; -import { Button, buttonVariants } from "@/components/ui/button"; +import { Button, buttonVariants } from "@/components/ui"; import { cn } from "@/lib/cn"; export const Route = createRootRoute({ diff --git a/dashboard-next/src/routes/dead-letters.tsx b/dashboard-next/src/routes/dead-letters.tsx index 4df5bc7..fed91c9 100644 --- a/dashboard-next/src/routes/dead-letters.tsx +++ b/dashboard-next/src/routes/dead-letters.tsx @@ -2,9 +2,7 @@ import { createFileRoute } from "@tanstack/react-router"; import { List, Rows3, Trash2 } from "lucide-react"; import { useState } from "react"; import { PageHeader } from "@/components/layout"; -import { Button } from "@/components/ui/button"; -import { DestructiveConfirmDialog } from "@/components/ui/destructive-confirm-dialog"; -import { Pagination } from "@/components/ui/pagination"; +import { Button, DestructiveConfirmDialog, Pagination } from "@/components/ui"; import { DeadLetterList, type DeadLetterView, diff --git a/dashboard-next/src/routes/jobs/$id.tsx b/dashboard-next/src/routes/jobs/$id.tsx index 7d22b26..211cd32 100644 --- a/dashboard-next/src/routes/jobs/$id.tsx +++ b/dashboard-next/src/routes/jobs/$id.tsx @@ -1,9 +1,14 @@ import { createFileRoute } from "@tanstack/react-router"; import { PageHeader } from "@/components/layout"; -import { Badge } from "@/components/ui/badge"; -import { ErrorState } from "@/components/ui/error-state"; -import { Skeleton } from "@/components/ui/skeleton"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { + Badge, + ErrorState, + Skeleton, + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from "@/components/ui"; import { JobActions, JobDagTab, diff --git a/dashboard-next/src/routes/jobs/index.tsx b/dashboard-next/src/routes/jobs/index.tsx index abe676b..666f691 100644 --- a/dashboard-next/src/routes/jobs/index.tsx +++ b/dashboard-next/src/routes/jobs/index.tsx @@ -1,6 +1,6 @@ import { createFileRoute } from "@tanstack/react-router"; import { PageHeader } from "@/components/layout"; -import { Pagination } from "@/components/ui/pagination"; +import { Pagination } from "@/components/ui"; import { JobFiltersBar, JobSearchBar, JobTable, useJobs } from "@/features/jobs"; import type { JobFilters, JobListQuery } from "@/features/jobs/types"; import { parseJobListSearch } from "@/features/jobs/utils"; From 8e1cb4b47d7aa598539885c072361d85dee1bde5 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 25 Apr 2026 02:25:32 +0530 Subject: [PATCH 31/42] =?UTF-8?q?feat(dashboard):=20cutover=20=E2=80=94=20?= =?UTF-8?q?rename=20dashboard-next=20=E2=86=92=20dashboard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Old Preact single-file dashboard is gone; the new React + Vite SPA (formerly dashboard-next/) now lives at the canonical path. dashboard.py drops the legacy templates/dashboard.html fallback and serves a 503 with clear rebuild instructions if assets aren't bundled. Docs and CI updated. --- .github/workflows/ci.yml | 32 +- dashboard-next/biome.json | 45 - dashboard-next/index.html | 21 - dashboard-next/package.json | 58 - dashboard-next/pnpm-lock.yaml | 4008 ----------------- .../src/components/layout/header.tsx | 46 - dashboard-next/src/components/layout/index.ts | 12 - .../src/components/layout/sidebar.tsx | 108 - dashboard-next/src/components/ui/badge.tsx | 33 - dashboard-next/src/components/ui/button.tsx | 49 - .../src/components/ui/confirm-dialog.tsx | 62 - .../src/components/ui/data-table.tsx | 128 - .../src/components/ui/empty-state.tsx | 35 - .../src/components/ui/error-state.tsx | 49 - dashboard-next/src/components/ui/index.ts | 98 - .../src/components/ui/pagination.tsx | 49 - .../src/components/ui/stat-card.tsx | 37 - dashboard-next/src/hooks/index.ts | 3 - dashboard-next/src/main.tsx | 28 - dashboard-next/src/vite-env.d.ts | 1 - dashboard-next/tsconfig.json | 26 - dashboard-next/vite.config.ts | 40 - {dashboard-next => dashboard}/.gitignore | 0 dashboard/biome.json | 8 +- {dashboard-next => dashboard}/components.json | 0 dashboard/index.html | 13 +- dashboard/package.json | 45 +- dashboard/pnpm-lock.yaml | 2971 ++++++++++-- dashboard/src/api/client.ts | 24 - dashboard/src/api/index.ts | 22 - dashboard/src/api/types.ts | 161 - dashboard/src/app.tsx | 35 - dashboard/src/charts/dag-viewer.tsx | 143 - dashboard/src/charts/index.ts | 3 - dashboard/src/charts/throughput-chart.tsx | 125 - dashboard/src/charts/timeseries-chart.tsx | 119 - .../src/components/layout/app-shell.tsx | 0 .../src/components/layout/breadcrumbs.tsx | 0 .../src/components/layout/command-palette.tsx | 0 dashboard/src/components/layout/header.tsx | 129 +- dashboard/src/components/layout/index.ts | 11 +- .../src/components/layout/last-refreshed.tsx | 0 .../src/components/layout/mobile-menu.tsx | 0 .../src/components/layout/page-header.tsx | 0 .../src/components/layout/refresh-control.tsx | 0 .../layout/route-error-boundary.tsx | 0 dashboard/src/components/layout/shell.tsx | 21 - dashboard/src/components/layout/sidebar.tsx | 127 +- .../src/components/layout/theme-toggle.tsx | 0 dashboard/src/components/ui/badge.tsx | 75 +- dashboard/src/components/ui/button.tsx | 78 +- .../src/components/ui/card.tsx | 0 .../src/components/ui/command.tsx | 0 .../src/components/ui/confirm-dialog.tsx | 79 +- dashboard/src/components/ui/data-table.tsx | 216 +- .../ui/destructive-confirm-dialog.tsx | 0 .../src/components/ui/dialog.tsx | 0 .../src/components/ui/dropdown-menu.tsx | 0 dashboard/src/components/ui/empty-state.tsx | 35 +- dashboard/src/components/ui/error-state.tsx | 52 +- dashboard/src/components/ui/index.ts | 100 +- .../src/components/ui/input.tsx | 0 .../src/components/ui/kbd.tsx | 0 dashboard/src/components/ui/loading.tsx | 39 - dashboard/src/components/ui/pagination.tsx | 61 +- dashboard/src/components/ui/progress-bar.tsx | 20 - .../src/components/ui/scroll-area.tsx | 0 .../src/components/ui/select.tsx | 0 .../src/components/ui/separator.tsx | 0 .../src/components/ui/sheet.tsx | 0 .../src/components/ui/skeleton.tsx | 0 dashboard/src/components/ui/stat-card.tsx | 78 +- dashboard/src/components/ui/stats-grid.tsx | 25 - .../src/components/ui/table.tsx | 0 .../src/components/ui/tabs.tsx | 0 dashboard/src/components/ui/toast.tsx | 42 - .../src/components/ui/toaster.tsx | 0 .../src/components/ui/tooltip.tsx | 0 .../src/features/circuit-breakers/api.ts | 0 .../components/circuit-breakers-table.tsx | 0 .../circuit-breakers/components/index.ts | 0 .../src/features/circuit-breakers/hooks.ts | 0 .../src/features/circuit-breakers/index.ts | 0 .../src/features/dead-letters/api.ts | 0 .../components/dead-letter-group-row.tsx | 0 .../components/dead-letter-list.tsx | 0 .../components/dead-letter-row.tsx | 0 .../features/dead-letters/components/index.ts | 0 .../src/features/dead-letters/hooks.ts | 0 .../src/features/dead-letters/index.ts | 0 .../src/features/dead-letters/utils.test.ts | 0 .../src/features/dead-letters/utils.ts | 0 .../src/features/jobs/api.ts | 0 .../src/features/jobs/components/index.ts | 0 .../features/jobs/components/job-actions.tsx | 0 .../features/jobs/components/job-dag-tab.tsx | 0 .../jobs/components/job-errors-tab.tsx | 0 .../features/jobs/components/job-filters.tsx | 0 .../features/jobs/components/job-logs-tab.tsx | 0 .../jobs/components/job-overview-tab.tsx | 0 .../jobs/components/job-replay-tab.tsx | 0 .../jobs/components/job-search-bar.tsx | 0 .../features/jobs/components/job-table.tsx | 0 .../src/features/jobs/hooks.ts | 0 .../src/features/jobs/index.ts | 0 .../src/features/jobs/types.ts | 0 .../src/features/jobs/utils.test.ts | 0 .../src/features/jobs/utils.ts | 0 .../src/features/logs/api.ts | 0 .../src/features/logs/components/index.ts | 0 .../features/logs/components/log-filters.tsx | 0 .../features/logs/components/log-stream.tsx | 0 .../src/features/logs/hooks.ts | 0 .../src/features/logs/index.ts | 0 .../src/features/logs/page.tsx | 0 .../src/features/metrics/api.ts | 0 .../src/features/metrics/components/index.ts | 0 .../metrics/components/latency-chart.tsx | 0 .../metrics/components/metrics-table.tsx | 0 .../metrics/components/task-selector.tsx | 0 .../metrics/components/throughput-chart.tsx | 0 .../components/time-range-selector.tsx | 0 .../src/features/metrics/hooks.ts | 0 .../src/features/metrics/index.ts | 0 .../src/features/metrics/page.tsx | 0 .../src/features/metrics/types.ts | 0 .../src/features/overview/api.ts | 0 .../src/features/overview/components/index.ts | 0 .../overview/components/queue-breakdown.tsx | 0 .../overview/components/recent-jobs.tsx | 0 .../overview/components/stats-grid.tsx | 0 .../components/throughput-sparkline.tsx | 0 .../src/features/overview/hooks.ts | 0 .../src/features/overview/index.ts | 0 .../src/features/queues/api.ts | 0 .../src/features/queues/components/index.ts | 0 .../queues/components/queues-table.tsx | 0 .../src/features/queues/hooks.ts | 0 .../src/features/queues/index.ts | 0 .../src/features/resources/api.ts | 0 .../features/resources/components/index.ts | 0 .../resources/components/resources-table.tsx | 0 .../src/features/resources/hooks.ts | 0 .../src/features/resources/index.ts | 0 .../src/features/system/api.ts | 0 .../src/features/system/components/index.ts | 0 .../system/components/interception-table.tsx | 0 .../system/components/proxy-table.tsx | 0 .../src/features/system/hooks.ts | 0 .../src/features/system/index.ts | 0 .../src/features/workers/api.ts | 0 .../src/features/workers/components/index.ts | 0 .../workers/components/workers-table.tsx | 0 .../src/features/workers/hooks.ts | 0 .../src/features/workers/index.ts | 0 {dashboard-next => dashboard}/src/globals.css | 0 dashboard/src/hooks/index.ts | 12 +- dashboard/src/hooks/use-api.ts | 68 - dashboard/src/hooks/use-auto-refresh.ts | 12 - .../src/hooks/use-debounced-value.ts | 0 .../src/hooks/use-last-refreshed.ts | 0 .../src/hooks/use-media-query.ts | 0 dashboard/src/hooks/use-theme.ts | 23 - dashboard/src/hooks/use-toast.ts | 23 - dashboard/src/index.css | 139 - .../src/lib/api-client.ts | 0 .../src/lib/api-types.ts | 0 {dashboard-next => dashboard}/src/lib/cn.ts | 0 dashboard/src/lib/format.ts | 27 - dashboard/src/lib/index.ts | 2 - .../src/lib/number.ts | 0 dashboard/src/lib/routes.ts | 18 - {dashboard-next => dashboard}/src/lib/site.ts | 0 .../src/lib/status.ts | 0 .../src/lib/time.test.ts | 0 {dashboard-next => dashboard}/src/lib/time.ts | 0 dashboard/src/main.tsx | 31 +- dashboard/src/pages/circuit-breakers.tsx | 55 - dashboard/src/pages/dead-letters.tsx | 268 -- dashboard/src/pages/job-detail.tsx | 241 - dashboard/src/pages/jobs.tsx | 278 -- dashboard/src/pages/logs.tsx | 92 - dashboard/src/pages/metrics.tsx | 136 - dashboard/src/pages/overview.tsx | 80 - dashboard/src/pages/queues.tsx | 109 - dashboard/src/pages/resources.tsx | 74 - dashboard/src/pages/system.tsx | 121 - dashboard/src/pages/workers.tsx | 69 - .../providers/command-palette-provider.tsx | 0 .../src/providers/index.tsx | 0 .../src/providers/query-provider.tsx | 0 .../providers/refresh-interval-provider.tsx | 0 .../src/providers/theme-provider.tsx | 0 .../src/routes/__root.tsx | 0 .../src/routes/circuit-breakers.tsx | 0 .../src/routes/dead-letters.tsx | 0 .../src/routes/index.tsx | 0 .../src/routes/jobs/$id.tsx | 0 .../src/routes/jobs/index.tsx | 0 .../src/routes/logs.lazy.tsx | 0 .../src/routes/logs.tsx | 0 .../src/routes/metrics.lazy.tsx | 0 .../src/routes/metrics.tsx | 0 .../src/routes/queues.tsx | 0 .../src/routes/resources.tsx | 0 .../src/routes/system.tsx | 0 .../src/routes/workers.tsx | 0 dashboard/tsconfig.json | 18 +- dashboard/vite.config.ts | 38 +- .../vitest.config.ts | 0 docs/changelog.md | 14 + docs/guide/observability/dashboard.md | 61 +- py_src/taskito/dashboard.py | 78 +- py_src/taskito/templates/dashboard.html | 233 - tests/python/test_dashboard.py | 5 +- 215 files changed, 3365 insertions(+), 8785 deletions(-) delete mode 100644 dashboard-next/biome.json delete mode 100644 dashboard-next/index.html delete mode 100644 dashboard-next/package.json delete mode 100644 dashboard-next/pnpm-lock.yaml delete mode 100644 dashboard-next/src/components/layout/header.tsx delete mode 100644 dashboard-next/src/components/layout/index.ts delete mode 100644 dashboard-next/src/components/layout/sidebar.tsx delete mode 100644 dashboard-next/src/components/ui/badge.tsx delete mode 100644 dashboard-next/src/components/ui/button.tsx delete mode 100644 dashboard-next/src/components/ui/confirm-dialog.tsx delete mode 100644 dashboard-next/src/components/ui/data-table.tsx delete mode 100644 dashboard-next/src/components/ui/empty-state.tsx delete mode 100644 dashboard-next/src/components/ui/error-state.tsx delete mode 100644 dashboard-next/src/components/ui/index.ts delete mode 100644 dashboard-next/src/components/ui/pagination.tsx delete mode 100644 dashboard-next/src/components/ui/stat-card.tsx delete mode 100644 dashboard-next/src/hooks/index.ts delete mode 100644 dashboard-next/src/main.tsx delete mode 100644 dashboard-next/src/vite-env.d.ts delete mode 100644 dashboard-next/tsconfig.json delete mode 100644 dashboard-next/vite.config.ts rename {dashboard-next => dashboard}/.gitignore (100%) rename {dashboard-next => dashboard}/components.json (100%) delete mode 100644 dashboard/src/api/client.ts delete mode 100644 dashboard/src/api/index.ts delete mode 100644 dashboard/src/api/types.ts delete mode 100644 dashboard/src/app.tsx delete mode 100644 dashboard/src/charts/dag-viewer.tsx delete mode 100644 dashboard/src/charts/index.ts delete mode 100644 dashboard/src/charts/throughput-chart.tsx delete mode 100644 dashboard/src/charts/timeseries-chart.tsx rename {dashboard-next => dashboard}/src/components/layout/app-shell.tsx (100%) rename {dashboard-next => dashboard}/src/components/layout/breadcrumbs.tsx (100%) rename {dashboard-next => dashboard}/src/components/layout/command-palette.tsx (100%) rename {dashboard-next => dashboard}/src/components/layout/last-refreshed.tsx (100%) rename {dashboard-next => dashboard}/src/components/layout/mobile-menu.tsx (100%) rename {dashboard-next => dashboard}/src/components/layout/page-header.tsx (100%) rename {dashboard-next => dashboard}/src/components/layout/refresh-control.tsx (100%) rename {dashboard-next => dashboard}/src/components/layout/route-error-boundary.tsx (100%) delete mode 100644 dashboard/src/components/layout/shell.tsx rename {dashboard-next => dashboard}/src/components/layout/theme-toggle.tsx (100%) rename {dashboard-next => dashboard}/src/components/ui/card.tsx (100%) rename {dashboard-next => dashboard}/src/components/ui/command.tsx (100%) rename {dashboard-next => dashboard}/src/components/ui/destructive-confirm-dialog.tsx (100%) rename {dashboard-next => dashboard}/src/components/ui/dialog.tsx (100%) rename {dashboard-next => dashboard}/src/components/ui/dropdown-menu.tsx (100%) rename {dashboard-next => dashboard}/src/components/ui/input.tsx (100%) rename {dashboard-next => dashboard}/src/components/ui/kbd.tsx (100%) delete mode 100644 dashboard/src/components/ui/loading.tsx delete mode 100644 dashboard/src/components/ui/progress-bar.tsx rename {dashboard-next => dashboard}/src/components/ui/scroll-area.tsx (100%) rename {dashboard-next => dashboard}/src/components/ui/select.tsx (100%) rename {dashboard-next => dashboard}/src/components/ui/separator.tsx (100%) rename {dashboard-next => dashboard}/src/components/ui/sheet.tsx (100%) rename {dashboard-next => dashboard}/src/components/ui/skeleton.tsx (100%) delete mode 100644 dashboard/src/components/ui/stats-grid.tsx rename {dashboard-next => dashboard}/src/components/ui/table.tsx (100%) rename {dashboard-next => dashboard}/src/components/ui/tabs.tsx (100%) delete mode 100644 dashboard/src/components/ui/toast.tsx rename {dashboard-next => dashboard}/src/components/ui/toaster.tsx (100%) rename {dashboard-next => dashboard}/src/components/ui/tooltip.tsx (100%) rename {dashboard-next => dashboard}/src/features/circuit-breakers/api.ts (100%) rename {dashboard-next => dashboard}/src/features/circuit-breakers/components/circuit-breakers-table.tsx (100%) rename {dashboard-next => dashboard}/src/features/circuit-breakers/components/index.ts (100%) rename {dashboard-next => dashboard}/src/features/circuit-breakers/hooks.ts (100%) rename {dashboard-next => dashboard}/src/features/circuit-breakers/index.ts (100%) rename {dashboard-next => dashboard}/src/features/dead-letters/api.ts (100%) rename {dashboard-next => dashboard}/src/features/dead-letters/components/dead-letter-group-row.tsx (100%) rename {dashboard-next => dashboard}/src/features/dead-letters/components/dead-letter-list.tsx (100%) rename {dashboard-next => dashboard}/src/features/dead-letters/components/dead-letter-row.tsx (100%) rename {dashboard-next => dashboard}/src/features/dead-letters/components/index.ts (100%) rename {dashboard-next => dashboard}/src/features/dead-letters/hooks.ts (100%) rename {dashboard-next => dashboard}/src/features/dead-letters/index.ts (100%) rename {dashboard-next => dashboard}/src/features/dead-letters/utils.test.ts (100%) rename {dashboard-next => dashboard}/src/features/dead-letters/utils.ts (100%) rename {dashboard-next => dashboard}/src/features/jobs/api.ts (100%) rename {dashboard-next => dashboard}/src/features/jobs/components/index.ts (100%) rename {dashboard-next => dashboard}/src/features/jobs/components/job-actions.tsx (100%) rename {dashboard-next => dashboard}/src/features/jobs/components/job-dag-tab.tsx (100%) rename {dashboard-next => dashboard}/src/features/jobs/components/job-errors-tab.tsx (100%) rename {dashboard-next => dashboard}/src/features/jobs/components/job-filters.tsx (100%) rename {dashboard-next => dashboard}/src/features/jobs/components/job-logs-tab.tsx (100%) rename {dashboard-next => dashboard}/src/features/jobs/components/job-overview-tab.tsx (100%) rename {dashboard-next => dashboard}/src/features/jobs/components/job-replay-tab.tsx (100%) rename {dashboard-next => dashboard}/src/features/jobs/components/job-search-bar.tsx (100%) rename {dashboard-next => dashboard}/src/features/jobs/components/job-table.tsx (100%) rename {dashboard-next => dashboard}/src/features/jobs/hooks.ts (100%) rename {dashboard-next => dashboard}/src/features/jobs/index.ts (100%) rename {dashboard-next => dashboard}/src/features/jobs/types.ts (100%) rename {dashboard-next => dashboard}/src/features/jobs/utils.test.ts (100%) rename {dashboard-next => dashboard}/src/features/jobs/utils.ts (100%) rename {dashboard-next => dashboard}/src/features/logs/api.ts (100%) rename {dashboard-next => dashboard}/src/features/logs/components/index.ts (100%) rename {dashboard-next => dashboard}/src/features/logs/components/log-filters.tsx (100%) rename {dashboard-next => dashboard}/src/features/logs/components/log-stream.tsx (100%) rename {dashboard-next => dashboard}/src/features/logs/hooks.ts (100%) rename {dashboard-next => dashboard}/src/features/logs/index.ts (100%) rename {dashboard-next => dashboard}/src/features/logs/page.tsx (100%) rename {dashboard-next => dashboard}/src/features/metrics/api.ts (100%) rename {dashboard-next => dashboard}/src/features/metrics/components/index.ts (100%) rename {dashboard-next => dashboard}/src/features/metrics/components/latency-chart.tsx (100%) rename {dashboard-next => dashboard}/src/features/metrics/components/metrics-table.tsx (100%) rename {dashboard-next => dashboard}/src/features/metrics/components/task-selector.tsx (100%) rename {dashboard-next => dashboard}/src/features/metrics/components/throughput-chart.tsx (100%) rename {dashboard-next => dashboard}/src/features/metrics/components/time-range-selector.tsx (100%) rename {dashboard-next => dashboard}/src/features/metrics/hooks.ts (100%) rename {dashboard-next => dashboard}/src/features/metrics/index.ts (100%) rename {dashboard-next => dashboard}/src/features/metrics/page.tsx (100%) rename {dashboard-next => dashboard}/src/features/metrics/types.ts (100%) rename {dashboard-next => dashboard}/src/features/overview/api.ts (100%) rename {dashboard-next => dashboard}/src/features/overview/components/index.ts (100%) rename {dashboard-next => dashboard}/src/features/overview/components/queue-breakdown.tsx (100%) rename {dashboard-next => dashboard}/src/features/overview/components/recent-jobs.tsx (100%) rename {dashboard-next => dashboard}/src/features/overview/components/stats-grid.tsx (100%) rename {dashboard-next => dashboard}/src/features/overview/components/throughput-sparkline.tsx (100%) rename {dashboard-next => dashboard}/src/features/overview/hooks.ts (100%) rename {dashboard-next => dashboard}/src/features/overview/index.ts (100%) rename {dashboard-next => dashboard}/src/features/queues/api.ts (100%) rename {dashboard-next => dashboard}/src/features/queues/components/index.ts (100%) rename {dashboard-next => dashboard}/src/features/queues/components/queues-table.tsx (100%) rename {dashboard-next => dashboard}/src/features/queues/hooks.ts (100%) rename {dashboard-next => dashboard}/src/features/queues/index.ts (100%) rename {dashboard-next => dashboard}/src/features/resources/api.ts (100%) rename {dashboard-next => dashboard}/src/features/resources/components/index.ts (100%) rename {dashboard-next => dashboard}/src/features/resources/components/resources-table.tsx (100%) rename {dashboard-next => dashboard}/src/features/resources/hooks.ts (100%) rename {dashboard-next => dashboard}/src/features/resources/index.ts (100%) rename {dashboard-next => dashboard}/src/features/system/api.ts (100%) rename {dashboard-next => dashboard}/src/features/system/components/index.ts (100%) rename {dashboard-next => dashboard}/src/features/system/components/interception-table.tsx (100%) rename {dashboard-next => dashboard}/src/features/system/components/proxy-table.tsx (100%) rename {dashboard-next => dashboard}/src/features/system/hooks.ts (100%) rename {dashboard-next => dashboard}/src/features/system/index.ts (100%) rename {dashboard-next => dashboard}/src/features/workers/api.ts (100%) rename {dashboard-next => dashboard}/src/features/workers/components/index.ts (100%) rename {dashboard-next => dashboard}/src/features/workers/components/workers-table.tsx (100%) rename {dashboard-next => dashboard}/src/features/workers/hooks.ts (100%) rename {dashboard-next => dashboard}/src/features/workers/index.ts (100%) rename {dashboard-next => dashboard}/src/globals.css (100%) delete mode 100644 dashboard/src/hooks/use-api.ts delete mode 100644 dashboard/src/hooks/use-auto-refresh.ts rename {dashboard-next => dashboard}/src/hooks/use-debounced-value.ts (100%) rename {dashboard-next => dashboard}/src/hooks/use-last-refreshed.ts (100%) rename {dashboard-next => dashboard}/src/hooks/use-media-query.ts (100%) delete mode 100644 dashboard/src/hooks/use-theme.ts delete mode 100644 dashboard/src/hooks/use-toast.ts delete mode 100644 dashboard/src/index.css rename {dashboard-next => dashboard}/src/lib/api-client.ts (100%) rename {dashboard-next => dashboard}/src/lib/api-types.ts (100%) rename {dashboard-next => dashboard}/src/lib/cn.ts (100%) delete mode 100644 dashboard/src/lib/format.ts delete mode 100644 dashboard/src/lib/index.ts rename {dashboard-next => dashboard}/src/lib/number.ts (100%) delete mode 100644 dashboard/src/lib/routes.ts rename {dashboard-next => dashboard}/src/lib/site.ts (100%) rename {dashboard-next => dashboard}/src/lib/status.ts (100%) rename {dashboard-next => dashboard}/src/lib/time.test.ts (100%) rename {dashboard-next => dashboard}/src/lib/time.ts (100%) delete mode 100644 dashboard/src/pages/circuit-breakers.tsx delete mode 100644 dashboard/src/pages/dead-letters.tsx delete mode 100644 dashboard/src/pages/job-detail.tsx delete mode 100644 dashboard/src/pages/jobs.tsx delete mode 100644 dashboard/src/pages/logs.tsx delete mode 100644 dashboard/src/pages/metrics.tsx delete mode 100644 dashboard/src/pages/overview.tsx delete mode 100644 dashboard/src/pages/queues.tsx delete mode 100644 dashboard/src/pages/resources.tsx delete mode 100644 dashboard/src/pages/system.tsx delete mode 100644 dashboard/src/pages/workers.tsx rename {dashboard-next => dashboard}/src/providers/command-palette-provider.tsx (100%) rename {dashboard-next => dashboard}/src/providers/index.tsx (100%) rename {dashboard-next => dashboard}/src/providers/query-provider.tsx (100%) rename {dashboard-next => dashboard}/src/providers/refresh-interval-provider.tsx (100%) rename {dashboard-next => dashboard}/src/providers/theme-provider.tsx (100%) rename {dashboard-next => dashboard}/src/routes/__root.tsx (100%) rename {dashboard-next => dashboard}/src/routes/circuit-breakers.tsx (100%) rename {dashboard-next => dashboard}/src/routes/dead-letters.tsx (100%) rename {dashboard-next => dashboard}/src/routes/index.tsx (100%) rename {dashboard-next => dashboard}/src/routes/jobs/$id.tsx (100%) rename {dashboard-next => dashboard}/src/routes/jobs/index.tsx (100%) rename {dashboard-next => dashboard}/src/routes/logs.lazy.tsx (100%) rename {dashboard-next => dashboard}/src/routes/logs.tsx (100%) rename {dashboard-next => dashboard}/src/routes/metrics.lazy.tsx (100%) rename {dashboard-next => dashboard}/src/routes/metrics.tsx (100%) rename {dashboard-next => dashboard}/src/routes/queues.tsx (100%) rename {dashboard-next => dashboard}/src/routes/resources.tsx (100%) rename {dashboard-next => dashboard}/src/routes/system.tsx (100%) rename {dashboard-next => dashboard}/src/routes/workers.tsx (100%) rename {dashboard-next => dashboard}/vitest.config.ts (100%) delete mode 100644 py_src/taskito/templates/dashboard.html diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c0e5b8d..5f2c6e6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,37 +79,11 @@ jobs: - name: TypeScript check run: cd dashboard && pnpm exec tsc --noEmit - - name: Build check - run: cd dashboard && pnpm exec vite build - - dashboard-next-lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Set up pnpm - uses: pnpm/action-setup@v4 - with: - version: "10.30.3" - - - name: Set up Node.js - uses: actions/setup-node@v6 - with: - node-version: "22" - cache: "pnpm" - cache-dependency-path: dashboard-next/pnpm-lock.yaml - - - name: Install dependencies - run: cd dashboard-next && pnpm install --frozen-lockfile - - - name: Biome lint + format check - run: cd dashboard-next && pnpm exec biome ci src/ - - - name: TypeScript check - run: cd dashboard-next && pnpm exec tsc --noEmit + - name: Unit tests + run: cd dashboard && pnpm exec vitest run - name: Build check - run: cd dashboard-next && pnpm exec vite build + run: cd dashboard && pnpm exec vite build rust-test: runs-on: ubuntu-latest diff --git a/dashboard-next/biome.json b/dashboard-next/biome.json deleted file mode 100644 index 0a95199..0000000 --- a/dashboard-next/biome.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "$schema": "https://biomejs.dev/schemas/2.4.8/schema.json", - "linter": { - "enabled": true, - "rules": { - "recommended": true, - "a11y": { - "noStaticElementInteractions": "off", - "useKeyWithClickEvents": "off" - }, - "complexity": { - "noForEach": "off" - }, - "correctness": { - "useExhaustiveDependencies": "warn" - }, - "style": { - "noNonNullAssertion": "off" - } - } - }, - "formatter": { - "enabled": true, - "indentStyle": "space", - "indentWidth": 2, - "lineWidth": 100 - }, - "javascript": { - "formatter": { - "quoteStyle": "double", - "semicolons": "always" - } - }, - "css": { - "linter": { - "enabled": false - }, - "formatter": { - "enabled": false - } - }, - "files": { - "includes": ["src/**/*.ts", "src/**/*.tsx", "!**/*.gen.ts"] - } -} diff --git a/dashboard-next/index.html b/dashboard-next/index.html deleted file mode 100644 index 249a8f3..0000000 --- a/dashboard-next/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - Taskito - - - -
    - - - diff --git a/dashboard-next/package.json b/dashboard-next/package.json deleted file mode 100644 index 5c893d3..0000000 --- a/dashboard-next/package.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "name": "taskito-dashboard-next", - "private": true, - "version": "0.11.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview", - "typecheck": "tsc --noEmit", - "lint": "biome check src/", - "lint:fix": "biome check --fix src/", - "format": "biome format --write src/", - "format:check": "biome format src/", - "test": "vitest run", - "test:watch": "vitest", - "ci": "biome ci src/ && tsc --noEmit && vitest run && vite build" - }, - "dependencies": { - "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-scroll-area": "^1.2.10", - "@radix-ui/react-select": "^2.2.6", - "@radix-ui/react-slot": "^1.2.4", - "@radix-ui/react-tabs": "^1.1.13", - "@radix-ui/react-tooltip": "^1.2.8", - "@tanstack/react-query": "^5.62.7", - "@tanstack/react-query-devtools": "^5.62.7", - "@tanstack/react-router": "^1.95.1", - "@tanstack/react-table": "^8.21.3", - "@tanstack/react-virtual": "^3.13.24", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "cmdk": "^1.1.1", - "lucide-react": "^0.577.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-error-boundary": "^6.1.1", - "recharts": "^3.8.1", - "sonner": "^1.7.1", - "tailwind-merge": "^2.6.0" - }, - "devDependencies": { - "@biomejs/biome": "^2.4.8", - "@tailwindcss/vite": "^4.0.0", - "@tanstack/router-plugin": "^1.95.1", - "@types/node": "^22.10.5", - "@types/react": "^18.3.18", - "@types/react-dom": "^18.3.5", - "@vitejs/plugin-react": "^4.3.4", - "@vitest/coverage-v8": "^4.1.5", - "tailwindcss": "^4.0.0", - "typescript": "^5.7.0", - "vite": "^6.4.2", - "vitest": "^4.1.5" - }, - "packageManager": "pnpm@10.30.3" -} diff --git a/dashboard-next/pnpm-lock.yaml b/dashboard-next/pnpm-lock.yaml deleted file mode 100644 index 7eeca12..0000000 --- a/dashboard-next/pnpm-lock.yaml +++ /dev/null @@ -1,4008 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@radix-ui/react-dialog': - specifier: ^1.1.15 - version: 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-dropdown-menu': - specifier: ^2.1.16 - version: 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-scroll-area': - specifier: ^1.2.10 - version: 1.2.10(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-select': - specifier: ^2.2.6 - version: 2.2.6(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': - specifier: ^1.2.4 - version: 1.2.4(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-tabs': - specifier: ^1.1.13 - version: 1.1.13(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-tooltip': - specifier: ^1.2.8 - version: 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@tanstack/react-query': - specifier: ^5.62.7 - version: 5.100.1(react@18.3.1) - '@tanstack/react-query-devtools': - specifier: ^5.62.7 - version: 5.100.1(@tanstack/react-query@5.100.1(react@18.3.1))(react@18.3.1) - '@tanstack/react-router': - specifier: ^1.95.1 - version: 1.168.23(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@tanstack/react-table': - specifier: ^8.21.3 - version: 8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@tanstack/react-virtual': - specifier: ^3.13.24 - version: 3.13.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - class-variance-authority: - specifier: ^0.7.1 - version: 0.7.1 - clsx: - specifier: ^2.1.1 - version: 2.1.1 - cmdk: - specifier: ^1.1.1 - version: 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - lucide-react: - specifier: ^0.577.0 - version: 0.577.0(react@18.3.1) - react: - specifier: ^18.3.1 - version: 18.3.1 - react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) - react-error-boundary: - specifier: ^6.1.1 - version: 6.1.1(react@18.3.1) - recharts: - specifier: ^3.8.1 - version: 3.8.1(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react-is@19.2.5)(react@18.3.1)(redux@5.0.1) - sonner: - specifier: ^1.7.1 - version: 1.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - tailwind-merge: - specifier: ^2.6.0 - version: 2.6.1 - devDependencies: - '@biomejs/biome': - specifier: ^2.4.8 - version: 2.4.13 - '@tailwindcss/vite': - specifier: ^4.0.0 - version: 4.2.4(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)) - '@tanstack/router-plugin': - specifier: ^1.95.1 - version: 1.167.23(@tanstack/react-router@1.168.23(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)) - '@types/node': - specifier: ^22.10.5 - version: 22.19.17 - '@types/react': - specifier: ^18.3.18 - version: 18.3.28 - '@types/react-dom': - specifier: ^18.3.5 - version: 18.3.7(@types/react@18.3.28) - '@vitejs/plugin-react': - specifier: ^4.3.4 - version: 4.7.0(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)) - '@vitest/coverage-v8': - specifier: ^4.1.5 - version: 4.1.5(vitest@4.1.5) - tailwindcss: - specifier: ^4.0.0 - version: 4.2.4 - typescript: - specifier: ^5.7.0 - version: 5.9.3 - vite: - specifier: ^6.4.2 - version: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0) - vitest: - specifier: ^4.1.5 - version: 4.1.5(@types/node@22.19.17)(@vitest/coverage-v8@4.1.5)(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)) - -packages: - - '@babel/code-frame@7.29.0': - resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.29.0': - resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.29.0': - resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.29.1': - resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.28.6': - resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.28.6': - resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.28.6': - resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-plugin-utils@7.28.6': - resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.29.2': - resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.29.2': - resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/plugin-syntax-jsx@7.28.6': - resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-typescript@7.28.6': - resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-jsx-self@7.27.1': - resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-jsx-source@7.27.1': - resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/template@7.28.6': - resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.29.0': - resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.29.0': - resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} - engines: {node: '>=6.9.0'} - - '@bcoe/v8-coverage@1.0.2': - resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} - engines: {node: '>=18'} - - '@biomejs/biome@2.4.13': - resolution: {integrity: sha512-gLXOwkOBBg0tr7bDsqlkIh4uFeKuMjxvqsrb1Tukww1iDmHcfr4Uu8MoQxp0Rcte+69+osRNWXwHsu/zxT6XqA==} - engines: {node: '>=14.21.3'} - hasBin: true - - '@biomejs/cli-darwin-arm64@2.4.13': - resolution: {integrity: sha512-2KImO1jhNFBa2oWConyr0x6flxbQpGKv6902uGXpYM62Xyem8U80j441SyUJ8KyngsmKbQjeIv1q2CQfDkNnYg==} - engines: {node: '>=14.21.3'} - cpu: [arm64] - os: [darwin] - - '@biomejs/cli-darwin-x64@2.4.13': - resolution: {integrity: sha512-BKrJklbaFN4p1Ts4kPBczo+PkbsHQg57kmJ+vON9u2t6uN5okYHaSr7h/MutPCWQgg2lglaWoSmm+zhYW+oOkg==} - engines: {node: '>=14.21.3'} - cpu: [x64] - os: [darwin] - - '@biomejs/cli-linux-arm64-musl@2.4.13': - resolution: {integrity: sha512-U5MsuBQW25dXaYtqWWSPM3P96H6Y+fHuja3TQpMNnylocHW0tEbtFTDlUj6oM+YJLntvEkQy4grBvQNUD4+RCg==} - engines: {node: '>=14.21.3'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@biomejs/cli-linux-arm64@2.4.13': - resolution: {integrity: sha512-NzkUDSqfvMBrPplKgVr3aXLHZ2NEELvvF4vZxXulEylKWIGqlvNEcwUcj9OLrn75TD3lJ/GIqCVlBwd1MZCuYQ==} - engines: {node: '>=14.21.3'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@biomejs/cli-linux-x64-musl@2.4.13': - resolution: {integrity: sha512-Z601MienRgTBDza/+u2CH3RSrWoXo9rtr8NK6A4KJzqGgfxx+H3VlyLgTJ4sRo40T3pIsqpTmiOQEvYzQvBRvQ==} - engines: {node: '>=14.21.3'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@biomejs/cli-linux-x64@2.4.13': - resolution: {integrity: sha512-Az3ZZedYRBo9EQzNnD9SxFcR1G5QsGo6VEc2hIyVPZ1rdKwee/7E9oeBBZFpE8Z44ekxsDQBqbiWGW5ShOhUSQ==} - engines: {node: '>=14.21.3'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@biomejs/cli-win32-arm64@2.4.13': - resolution: {integrity: sha512-Px9PS2B5/Q183bUwy/5VHqp3J2lzdOCeVGzMpphYfl8oSa7VDCqenBdqWpy6DCy/en4Rbf/Y1RieZF6dJPcc9A==} - engines: {node: '>=14.21.3'} - cpu: [arm64] - os: [win32] - - '@biomejs/cli-win32-x64@2.4.13': - resolution: {integrity: sha512-tTcMkXyBrmHi9BfrD2VNHs/5rYIUKETqsBlYOvSAABwBkJhSDVb5e7wPukftsQbO3WzQkXe6kaztC6WtUOXSoQ==} - engines: {node: '>=14.21.3'} - cpu: [x64] - os: [win32] - - '@esbuild/aix-ppc64@0.25.12': - resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/aix-ppc64@0.27.7': - resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.25.12': - resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm64@0.27.7': - resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.25.12': - resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-arm@0.27.7': - resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.25.12': - resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/android-x64@0.27.7': - resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.25.12': - resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-arm64@0.27.7': - resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.25.12': - resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/darwin-x64@0.27.7': - resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.25.12': - resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-arm64@0.27.7': - resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.25.12': - resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.27.7': - resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.25.12': - resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm64@0.27.7': - resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.25.12': - resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-arm@0.27.7': - resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.25.12': - resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-ia32@0.27.7': - resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.25.12': - resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.27.7': - resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.25.12': - resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-mips64el@0.27.7': - resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.25.12': - resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-ppc64@0.27.7': - resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.25.12': - resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-riscv64@0.27.7': - resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.25.12': - resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-s390x@0.27.7': - resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.25.12': - resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/linux-x64@0.27.7': - resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.25.12': - resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-arm64@0.27.7': - resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.25.12': - resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.27.7': - resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.25.12': - resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-arm64@0.27.7': - resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.25.12': - resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.27.7': - resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.25.12': - resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/openharmony-arm64@0.27.7': - resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.25.12': - resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.27.7': - resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.25.12': - resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.27.7': - resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.25.12': - resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.27.7': - resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.25.12': - resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.27.7': - resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@floating-ui/core@1.7.5': - resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} - - '@floating-ui/dom@1.7.6': - resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} - - '@floating-ui/react-dom@2.1.8': - resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - - '@floating-ui/utils@0.2.11': - resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - - '@radix-ui/number@1.1.1': - resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} - - '@radix-ui/primitive@1.1.3': - resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} - - '@radix-ui/react-arrow@1.1.7': - resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-collection@1.1.7': - resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-compose-refs@1.1.2': - resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-context@1.1.2': - resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-dialog@1.1.15': - resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-direction@1.1.1': - resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-dismissable-layer@1.1.11': - resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-dropdown-menu@2.1.16': - resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-focus-guards@1.1.3': - resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-focus-scope@1.1.7': - resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-id@1.1.1': - resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-menu@2.1.16': - resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-popper@1.2.8': - resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-portal@1.1.9': - resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-presence@1.1.5': - resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-primitive@2.1.3': - resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-primitive@2.1.4': - resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-roving-focus@1.1.11': - resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-scroll-area@1.2.10': - resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-select@2.2.6': - resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-slot@1.2.3': - resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-slot@1.2.4': - resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-tabs@1.1.13': - resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-tooltip@1.2.8': - resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-use-callback-ref@1.1.1': - resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-controllable-state@1.2.2': - resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-effect-event@0.0.2': - resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-escape-keydown@1.1.1': - resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-layout-effect@1.1.1': - resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-previous@1.1.1': - resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-rect@1.1.1': - resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-size@1.1.1': - resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-visually-hidden@1.2.3': - resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/rect@1.1.1': - resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - - '@reduxjs/toolkit@2.11.2': - resolution: {integrity: sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==} - peerDependencies: - react: ^16.9.0 || ^17.0.0 || ^18 || ^19 - react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 - peerDependenciesMeta: - react: - optional: true - react-redux: - optional: true - - '@rolldown/pluginutils@1.0.0-beta.27': - resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} - - '@rollup/rollup-android-arm-eabi@4.60.2': - resolution: {integrity: sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.60.2': - resolution: {integrity: sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.60.2': - resolution: {integrity: sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.60.2': - resolution: {integrity: sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.60.2': - resolution: {integrity: sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.60.2': - resolution: {integrity: sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.60.2': - resolution: {integrity: sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-arm-musleabihf@4.60.2': - resolution: {integrity: sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==} - cpu: [arm] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-arm64-gnu@4.60.2': - resolution: {integrity: sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-arm64-musl@4.60.2': - resolution: {integrity: sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-loong64-gnu@4.60.2': - resolution: {integrity: sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==} - cpu: [loong64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-loong64-musl@4.60.2': - resolution: {integrity: sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==} - cpu: [loong64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-ppc64-gnu@4.60.2': - resolution: {integrity: sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-ppc64-musl@4.60.2': - resolution: {integrity: sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==} - cpu: [ppc64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-riscv64-gnu@4.60.2': - resolution: {integrity: sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-riscv64-musl@4.60.2': - resolution: {integrity: sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==} - cpu: [riscv64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-s390x-gnu@4.60.2': - resolution: {integrity: sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-x64-gnu@4.60.2': - resolution: {integrity: sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-x64-musl@4.60.2': - resolution: {integrity: sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rollup/rollup-openbsd-x64@4.60.2': - resolution: {integrity: sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==} - cpu: [x64] - os: [openbsd] - - '@rollup/rollup-openharmony-arm64@4.60.2': - resolution: {integrity: sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==} - cpu: [arm64] - os: [openharmony] - - '@rollup/rollup-win32-arm64-msvc@4.60.2': - resolution: {integrity: sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.60.2': - resolution: {integrity: sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-gnu@4.60.2': - resolution: {integrity: sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==} - cpu: [x64] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.60.2': - resolution: {integrity: sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==} - cpu: [x64] - os: [win32] - - '@standard-schema/spec@1.1.0': - resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - - '@standard-schema/utils@0.3.0': - resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} - - '@tailwindcss/node@4.2.4': - resolution: {integrity: sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==} - - '@tailwindcss/oxide-android-arm64@4.2.4': - resolution: {integrity: sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [android] - - '@tailwindcss/oxide-darwin-arm64@4.2.4': - resolution: {integrity: sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [darwin] - - '@tailwindcss/oxide-darwin-x64@4.2.4': - resolution: {integrity: sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==} - engines: {node: '>= 20'} - cpu: [x64] - os: [darwin] - - '@tailwindcss/oxide-freebsd-x64@4.2.4': - resolution: {integrity: sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==} - engines: {node: '>= 20'} - cpu: [x64] - os: [freebsd] - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': - resolution: {integrity: sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==} - engines: {node: '>= 20'} - cpu: [arm] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': - resolution: {integrity: sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@tailwindcss/oxide-linux-arm64-musl@4.2.4': - resolution: {integrity: sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@tailwindcss/oxide-linux-x64-gnu@4.2.4': - resolution: {integrity: sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==} - engines: {node: '>= 20'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@tailwindcss/oxide-linux-x64-musl@4.2.4': - resolution: {integrity: sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==} - engines: {node: '>= 20'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@tailwindcss/oxide-wasm32-wasi@4.2.4': - resolution: {integrity: sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - bundledDependencies: - - '@napi-rs/wasm-runtime' - - '@emnapi/core' - - '@emnapi/runtime' - - '@tybys/wasm-util' - - '@emnapi/wasi-threads' - - tslib - - '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': - resolution: {integrity: sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [win32] - - '@tailwindcss/oxide-win32-x64-msvc@4.2.4': - resolution: {integrity: sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==} - engines: {node: '>= 20'} - cpu: [x64] - os: [win32] - - '@tailwindcss/oxide@4.2.4': - resolution: {integrity: sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==} - engines: {node: '>= 20'} - - '@tailwindcss/vite@4.2.4': - resolution: {integrity: sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw==} - peerDependencies: - vite: ^5.2.0 || ^6 || ^7 || ^8 - - '@tanstack/history@1.161.6': - resolution: {integrity: sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg==} - engines: {node: '>=20.19'} - - '@tanstack/query-core@5.100.1': - resolution: {integrity: sha512-awvQhOO/2TrSCHE5LKKsXcvvj6WSBncwEcMFCB/ez0Qs0b17iyyivoGArNV3HFfXryZwCpnb/olsaBBKrIbtSw==} - - '@tanstack/query-devtools@5.100.1': - resolution: {integrity: sha512-jZLV2l7XjYxXCrXHj9pj15gZuY8Te+idoSPS2hIh3+SxOd20Gn0rfUoqEw9vc+us/b16hi0/DWqpzx9O1ZsyIQ==} - - '@tanstack/react-query-devtools@5.100.1': - resolution: {integrity: sha512-JuLinBUl/BlZhm0WVX83fJgE2a3YSbuEdxf3fgP+THg92hX7YfwuH5DzT35a6sL/rifZsPr0yJ9itB6jDOcdRg==} - peerDependencies: - '@tanstack/react-query': ^5.100.1 - react: ^18 || ^19 - - '@tanstack/react-query@5.100.1': - resolution: {integrity: sha512-UgWRLhQKprC37SsO6y1zRabOqDmM2gsdTNPbqTT35yl7kOOhwXU4nyfOiGHXPwoEFJV1IpSk85hjIFjNFWVpzw==} - peerDependencies: - react: ^18 || ^19 - - '@tanstack/react-router@1.168.23': - resolution: {integrity: sha512-+GblieDnutG6oipJJPNtRJjrWF8QTZEG/l0532+BngFkVK48oHNOcvIkSoAFYftK1egAwM7KBxXsb0Ou+X6/MQ==} - engines: {node: '>=20.19'} - peerDependencies: - react: '>=18.0.0 || >=19.0.0' - react-dom: '>=18.0.0 || >=19.0.0' - - '@tanstack/react-store@0.9.3': - resolution: {integrity: sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - '@tanstack/react-table@8.21.3': - resolution: {integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==} - engines: {node: '>=12'} - peerDependencies: - react: '>=16.8' - react-dom: '>=16.8' - - '@tanstack/react-virtual@3.13.24': - resolution: {integrity: sha512-aIJvz5OSkhNIhZIpYivrxrPTKYsjW9Uzy+sP/mx0S3sev2HyvPb7xmjbYvokzEpfgYHy/HjzJ2zFAETuUfgCpg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - '@tanstack/router-core@1.168.15': - resolution: {integrity: sha512-Wr0424NDtD8fT/uALobMZ9DdcfsTyXtW5IPR++7zvW8/7RaIOeaqXpVDId8ywaGtqPWLWOfaUg2zUtYtukoXYA==} - engines: {node: '>=20.19'} - hasBin: true - - '@tanstack/router-generator@1.166.33': - resolution: {integrity: sha512-MXP1WrEaZ13tlO5iJoXC+ZIFHNj5CtcvWuqlAZ3zXY70Musuq+mfUcKWMVdcIstnNqrZl5M2hfqLh5Zf5t4NVw==} - engines: {node: '>=20.19'} - - '@tanstack/router-plugin@1.167.23': - resolution: {integrity: sha512-dqfCd8gsZThbVQ8bcYMO62/hW5GCkUoPLnnjOd3fCWoEi+Ei5oWa/GnlgHCpG7bdeGr/K8isnYUmI9Ysq5vLrg==} - engines: {node: '>=20.19'} - hasBin: true - peerDependencies: - '@rsbuild/core': '>=1.0.2 || ^2.0.0' - '@tanstack/react-router': ^1.168.23 - vite: '>=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0' - vite-plugin-solid: ^2.11.10 || ^3.0.0-0 - webpack: '>=5.92.0' - peerDependenciesMeta: - '@rsbuild/core': - optional: true - '@tanstack/react-router': - optional: true - vite: - optional: true - vite-plugin-solid: - optional: true - webpack: - optional: true - - '@tanstack/router-utils@1.161.7': - resolution: {integrity: sha512-VkY0u7ax/GD0qU6ZLLnfPC+UMxVzxRbvZp4yV4iUSXjgJZ/siAT5/QlLm9FEDJ9QDoC0VD9W7f00tKKreUI7Ng==} - engines: {node: '>=20.19'} - - '@tanstack/store@0.9.3': - resolution: {integrity: sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==} - - '@tanstack/table-core@8.21.3': - resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} - engines: {node: '>=12'} - - '@tanstack/virtual-core@3.14.0': - resolution: {integrity: sha512-JLANqGy/D6k4Ujmh8Tr25lGimuOXNiaVyXaCAZS0W+1390sADdGnyUdSWNIfd49gebtIxGMij4IktRVzrdr12Q==} - - '@tanstack/virtual-file-routes@1.161.7': - resolution: {integrity: sha512-olW33+Cn+bsCsZKPwEGhlkqS6w3M2slFv11JIobdnCFKMLG97oAI2kWKdx5/zsywTL8flpnoIgaZZPlQTFYhdQ==} - engines: {node: '>=20.19'} - hasBin: true - - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - - '@types/babel__generator@7.27.0': - resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} - - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - - '@types/babel__traverse@7.28.0': - resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} - - '@types/chai@5.2.3': - resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - - '@types/d3-array@3.2.2': - resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} - - '@types/d3-color@3.1.3': - resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} - - '@types/d3-ease@3.0.2': - resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} - - '@types/d3-interpolate@3.0.4': - resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} - - '@types/d3-path@3.1.1': - resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} - - '@types/d3-scale@4.0.9': - resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} - - '@types/d3-shape@3.1.8': - resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} - - '@types/d3-time@3.0.4': - resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} - - '@types/d3-timer@3.0.2': - resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} - - '@types/deep-eql@4.0.2': - resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - - '@types/node@22.19.17': - resolution: {integrity: sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==} - - '@types/prop-types@15.7.15': - resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} - - '@types/react-dom@18.3.7': - resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} - peerDependencies: - '@types/react': ^18.0.0 - - '@types/react@18.3.28': - resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==} - - '@types/use-sync-external-store@0.0.6': - resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} - - '@vitejs/plugin-react@4.7.0': - resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - - '@vitest/coverage-v8@4.1.5': - resolution: {integrity: sha512-38C0/Ddb7HcRG0Z4/DUem8x57d2p9jYgp18mkaYswEOQBGsI1CG4f/hjm0ZCeaJfWhSZ4k7jgs29V1Zom7Ki9A==} - peerDependencies: - '@vitest/browser': 4.1.5 - vitest: 4.1.5 - peerDependenciesMeta: - '@vitest/browser': - optional: true - - '@vitest/expect@4.1.5': - resolution: {integrity: sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==} - - '@vitest/mocker@4.1.5': - resolution: {integrity: sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==} - peerDependencies: - msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - - '@vitest/pretty-format@4.1.5': - resolution: {integrity: sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==} - - '@vitest/runner@4.1.5': - resolution: {integrity: sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==} - - '@vitest/snapshot@4.1.5': - resolution: {integrity: sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==} - - '@vitest/spy@4.1.5': - resolution: {integrity: sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==} - - '@vitest/utils@4.1.5': - resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==} - - acorn@8.16.0: - resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} - engines: {node: '>=0.4.0'} - hasBin: true - - ansis@4.2.0: - resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} - engines: {node: '>=14'} - - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - - aria-hidden@1.2.6: - resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} - engines: {node: '>=10'} - - assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} - - ast-v8-to-istanbul@1.0.0: - resolution: {integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==} - - babel-dead-code-elimination@1.0.12: - resolution: {integrity: sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==} - - baseline-browser-mapping@2.10.21: - resolution: {integrity: sha512-Q+rUQ7Uz8AHM7DEaNdwvfFCTq7a43lNTzuS94eiWqwyxfV/wJv+oUivef51T91mmRY4d4A1u9rcSvkeufCVXlA==} - engines: {node: '>=6.0.0'} - hasBin: true - - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - - browserslist@4.28.2: - resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - - caniuse-lite@1.0.30001790: - resolution: {integrity: sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==} - - chai@6.2.2: - resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} - engines: {node: '>=18'} - - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - - class-variance-authority@0.7.1: - resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} - - clsx@2.1.1: - resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} - engines: {node: '>=6'} - - cmdk@1.1.1: - resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==} - peerDependencies: - react: ^18 || ^19 || ^19.0.0-rc - react-dom: ^18 || ^19 || ^19.0.0-rc - - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - cookie-es@3.1.1: - resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} - - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - - d3-array@3.2.4: - resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} - engines: {node: '>=12'} - - d3-color@3.1.0: - resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} - engines: {node: '>=12'} - - d3-ease@3.0.1: - resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} - engines: {node: '>=12'} - - d3-format@3.1.2: - resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} - engines: {node: '>=12'} - - d3-interpolate@3.0.1: - resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} - engines: {node: '>=12'} - - d3-path@3.1.0: - resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} - engines: {node: '>=12'} - - d3-scale@4.0.2: - resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} - engines: {node: '>=12'} - - d3-shape@3.2.0: - resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} - engines: {node: '>=12'} - - d3-time-format@4.1.0: - resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} - engines: {node: '>=12'} - - d3-time@3.1.0: - resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} - engines: {node: '>=12'} - - d3-timer@3.0.1: - resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} - engines: {node: '>=12'} - - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decimal.js-light@2.5.1: - resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} - - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - - detect-node-es@1.1.0: - resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - - diff@8.0.4: - resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} - engines: {node: '>=0.3.1'} - - electron-to-chromium@1.5.344: - resolution: {integrity: sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==} - - enhanced-resolve@5.21.0: - resolution: {integrity: sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==} - engines: {node: '>=10.13.0'} - - es-module-lexer@2.0.0: - resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} - - es-toolkit@1.46.0: - resolution: {integrity: sha512-IToJ6ct9OLl5zz6WsC/1vZEwfSZ7Myil+ygl5Tf30Xjn9AEkzNB4kqp2G7VUJKF1DtTx/ra5M5KLlXvzOg51BA==} - - esbuild@0.25.12: - resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} - engines: {node: '>=18'} - hasBin: true - - esbuild@0.27.7: - resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} - engines: {node: '>=18'} - hasBin: true - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - - eventemitter3@5.0.4: - resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} - - expect-type@1.3.0: - resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} - engines: {node: '>=12.0.0'} - - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - get-nonce@1.0.1: - resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} - engines: {node: '>=6'} - - get-tsconfig@4.14.0: - resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - - immer@10.2.0: - resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==} - - immer@11.1.4: - resolution: {integrity: sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==} - - internmap@2.0.3: - resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} - engines: {node: '>=12'} - - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - isbot@5.1.39: - resolution: {integrity: sha512-obH0yYahGXdzNxo+djmHhBYThUKDkz565cxkIlt2L9hXfv1NlaLKoDBHo6KxXsYrIXx2RK3x5vY36CfZcobxEw==} - engines: {node: '>=18'} - - istanbul-lib-coverage@3.2.2: - resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} - engines: {node: '>=8'} - - istanbul-lib-report@3.0.1: - resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} - engines: {node: '>=10'} - - istanbul-reports@3.2.0: - resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} - engines: {node: '>=8'} - - jiti@2.6.1: - resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} - hasBin: true - - js-tokens@10.0.0: - resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true - - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - - lightningcss-android-arm64@1.32.0: - resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - - lightningcss-darwin-arm64@1.32.0: - resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.32.0: - resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.32.0: - resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.32.0: - resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm64-gnu@1.32.0: - resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - lightningcss-linux-arm64-musl@1.32.0: - resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - - lightningcss-linux-x64-gnu@1.32.0: - resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - - lightningcss-linux-x64-musl@1.32.0: - resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [musl] - - lightningcss-win32-arm64-msvc@1.32.0: - resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - - lightningcss-win32-x64-msvc@1.32.0: - resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - - lightningcss@1.32.0: - resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} - engines: {node: '>= 12.0.0'} - - loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true - - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - - lucide-react@0.577.0: - resolution: {integrity: sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A==} - peerDependencies: - react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - - magicast@0.5.2: - resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} - - make-dir@4.0.0: - resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} - engines: {node: '>=10'} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - node-releases@2.0.38: - resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - obug@2.1.1: - resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} - - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@2.3.2: - resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} - engines: {node: '>=8.6'} - - picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} - engines: {node: '>=12'} - - postcss@8.5.10: - resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==} - engines: {node: ^10 || ^12 || >=14} - - prettier@3.8.3: - resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} - engines: {node: '>=14'} - hasBin: true - - react-dom@18.3.1: - resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} - peerDependencies: - react: ^18.3.1 - - react-error-boundary@6.1.1: - resolution: {integrity: sha512-BrYwPOdXi5mqkk5lw+Uvt0ThHx32rCt3BkukS4X23A2AIWDPSGX6iaWTc0y9TU/mHDA/6qOSGel+B2ERkOvD1w==} - peerDependencies: - react: ^18.0.0 || ^19.0.0 - - react-is@19.2.5: - resolution: {integrity: sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ==} - - react-redux@9.2.0: - resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==} - peerDependencies: - '@types/react': ^18.2.25 || ^19 - react: ^18.0 || ^19 - redux: ^5.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - redux: - optional: true - - react-refresh@0.17.0: - resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} - engines: {node: '>=0.10.0'} - - react-remove-scroll-bar@2.3.8: - resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - react-remove-scroll@2.7.2: - resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - react-style-singleton@2.2.3: - resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - react@18.3.1: - resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} - engines: {node: '>=0.10.0'} - - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - - recharts@3.8.1: - resolution: {integrity: sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==} - engines: {node: '>=18'} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - redux-thunk@3.1.0: - resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} - peerDependencies: - redux: ^5.0.0 - - redux@5.0.1: - resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} - - reselect@5.1.1: - resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} - - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - - rollup@4.60.2: - resolution: {integrity: sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - scheduler@0.23.2: - resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} - - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - - semver@7.7.4: - resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} - engines: {node: '>=10'} - hasBin: true - - seroval-plugins@1.5.2: - resolution: {integrity: sha512-qpY0Cl+fKYFn4GOf3cMiq6l72CpuVaawb6ILjubOQ+diJ54LfOWaSSPsaswN8DRPIPW4Yq+tE1k5aKd7ILyaFg==} - engines: {node: '>=10'} - peerDependencies: - seroval: ^1.0 - - seroval@1.5.2: - resolution: {integrity: sha512-xcRN39BdsnO9Tf+VzsE7b3JyTJASItIV1FVFewJKCFcW4s4haIKS3e6vj8PGB9qBwC7tnuOywQMdv5N4qkzi7Q==} - engines: {node: '>=10'} - - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - - sonner@1.7.4: - resolution: {integrity: sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw==} - peerDependencies: - react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc - react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - - std-env@4.1.0: - resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} - - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - tailwind-merge@2.6.1: - resolution: {integrity: sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==} - - tailwindcss@4.2.4: - resolution: {integrity: sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==} - - tapable@2.3.3: - resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} - engines: {node: '>=6'} - - tiny-invariant@1.3.3: - resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - - tinyexec@1.1.1: - resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} - engines: {node: '>=18'} - - tinyglobby@0.2.16: - resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} - engines: {node: '>=12.0.0'} - - tinyrainbow@3.1.0: - resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} - engines: {node: '>=14.0.0'} - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - tsx@4.21.0: - resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} - engines: {node: '>=18.0.0'} - hasBin: true - - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} - hasBin: true - - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - - unplugin@2.3.11: - resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} - engines: {node: '>=18.12.0'} - - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - - use-callback-ref@1.3.3: - resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - use-sidecar@1.1.3: - resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - use-sync-external-store@1.6.0: - resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - victory-vendor@37.3.6: - resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} - - vite@6.4.2: - resolution: {integrity: sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - 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 - - vitest@4.1.5: - resolution: {integrity: sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@opentelemetry/api': ^1.9.0 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.5 - '@vitest/browser-preview': 4.1.5 - '@vitest/browser-webdriverio': 4.1.5 - '@vitest/coverage-istanbul': 4.1.5 - '@vitest/coverage-v8': 4.1.5 - '@vitest/ui': 4.1.5 - happy-dom: '*' - jsdom: '*' - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@opentelemetry/api': - optional: true - '@types/node': - optional: true - '@vitest/browser-playwright': - optional: true - '@vitest/browser-preview': - optional: true - '@vitest/browser-webdriverio': - optional: true - '@vitest/coverage-istanbul': - optional: true - '@vitest/coverage-v8': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - - webpack-virtual-modules@0.6.2: - resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} - - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true - - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - -snapshots: - - '@babel/code-frame@7.29.0': - dependencies: - '@babel/helper-validator-identifier': 7.28.5 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/compat-data@7.29.0': {} - - '@babel/core@7.29.0': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helpers': 7.29.2 - '@babel/parser': 7.29.2 - '@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.4.3 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/generator@7.29.1': - dependencies: - '@babel/parser': 7.29.2 - '@babel/types': 7.29.0 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 - - '@babel/helper-compilation-targets@7.28.6': - dependencies: - '@babel/compat-data': 7.29.0 - '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.2 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-globals@7.28.0': {} - - '@babel/helper-module-imports@7.28.6': - dependencies: - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-imports': 7.28.6 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-plugin-utils@7.28.6': {} - - '@babel/helper-string-parser@7.27.1': {} - - '@babel/helper-validator-identifier@7.28.5': {} - - '@babel/helper-validator-option@7.27.1': {} - - '@babel/helpers@7.29.2': - dependencies: - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 - - '@babel/parser@7.29.2': - dependencies: - '@babel/types': 7.29.0 - - '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/template@7.28.6': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.2 - '@babel/types': 7.29.0 - - '@babel/traverse@7.29.0': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.29.2 - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - '@babel/types@7.29.0': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - - '@bcoe/v8-coverage@1.0.2': {} - - '@biomejs/biome@2.4.13': - optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.4.13 - '@biomejs/cli-darwin-x64': 2.4.13 - '@biomejs/cli-linux-arm64': 2.4.13 - '@biomejs/cli-linux-arm64-musl': 2.4.13 - '@biomejs/cli-linux-x64': 2.4.13 - '@biomejs/cli-linux-x64-musl': 2.4.13 - '@biomejs/cli-win32-arm64': 2.4.13 - '@biomejs/cli-win32-x64': 2.4.13 - - '@biomejs/cli-darwin-arm64@2.4.13': - optional: true - - '@biomejs/cli-darwin-x64@2.4.13': - optional: true - - '@biomejs/cli-linux-arm64-musl@2.4.13': - optional: true - - '@biomejs/cli-linux-arm64@2.4.13': - optional: true - - '@biomejs/cli-linux-x64-musl@2.4.13': - optional: true - - '@biomejs/cli-linux-x64@2.4.13': - optional: true - - '@biomejs/cli-win32-arm64@2.4.13': - optional: true - - '@biomejs/cli-win32-x64@2.4.13': - optional: true - - '@esbuild/aix-ppc64@0.25.12': - optional: true - - '@esbuild/aix-ppc64@0.27.7': - optional: true - - '@esbuild/android-arm64@0.25.12': - optional: true - - '@esbuild/android-arm64@0.27.7': - optional: true - - '@esbuild/android-arm@0.25.12': - optional: true - - '@esbuild/android-arm@0.27.7': - optional: true - - '@esbuild/android-x64@0.25.12': - optional: true - - '@esbuild/android-x64@0.27.7': - optional: true - - '@esbuild/darwin-arm64@0.25.12': - optional: true - - '@esbuild/darwin-arm64@0.27.7': - optional: true - - '@esbuild/darwin-x64@0.25.12': - optional: true - - '@esbuild/darwin-x64@0.27.7': - optional: true - - '@esbuild/freebsd-arm64@0.25.12': - optional: true - - '@esbuild/freebsd-arm64@0.27.7': - optional: true - - '@esbuild/freebsd-x64@0.25.12': - optional: true - - '@esbuild/freebsd-x64@0.27.7': - optional: true - - '@esbuild/linux-arm64@0.25.12': - optional: true - - '@esbuild/linux-arm64@0.27.7': - optional: true - - '@esbuild/linux-arm@0.25.12': - optional: true - - '@esbuild/linux-arm@0.27.7': - optional: true - - '@esbuild/linux-ia32@0.25.12': - optional: true - - '@esbuild/linux-ia32@0.27.7': - optional: true - - '@esbuild/linux-loong64@0.25.12': - optional: true - - '@esbuild/linux-loong64@0.27.7': - optional: true - - '@esbuild/linux-mips64el@0.25.12': - optional: true - - '@esbuild/linux-mips64el@0.27.7': - optional: true - - '@esbuild/linux-ppc64@0.25.12': - optional: true - - '@esbuild/linux-ppc64@0.27.7': - optional: true - - '@esbuild/linux-riscv64@0.25.12': - optional: true - - '@esbuild/linux-riscv64@0.27.7': - optional: true - - '@esbuild/linux-s390x@0.25.12': - optional: true - - '@esbuild/linux-s390x@0.27.7': - optional: true - - '@esbuild/linux-x64@0.25.12': - optional: true - - '@esbuild/linux-x64@0.27.7': - optional: true - - '@esbuild/netbsd-arm64@0.25.12': - optional: true - - '@esbuild/netbsd-arm64@0.27.7': - optional: true - - '@esbuild/netbsd-x64@0.25.12': - optional: true - - '@esbuild/netbsd-x64@0.27.7': - optional: true - - '@esbuild/openbsd-arm64@0.25.12': - optional: true - - '@esbuild/openbsd-arm64@0.27.7': - optional: true - - '@esbuild/openbsd-x64@0.25.12': - optional: true - - '@esbuild/openbsd-x64@0.27.7': - optional: true - - '@esbuild/openharmony-arm64@0.25.12': - optional: true - - '@esbuild/openharmony-arm64@0.27.7': - optional: true - - '@esbuild/sunos-x64@0.25.12': - optional: true - - '@esbuild/sunos-x64@0.27.7': - optional: true - - '@esbuild/win32-arm64@0.25.12': - optional: true - - '@esbuild/win32-arm64@0.27.7': - optional: true - - '@esbuild/win32-ia32@0.25.12': - optional: true - - '@esbuild/win32-ia32@0.27.7': - optional: true - - '@esbuild/win32-x64@0.25.12': - optional: true - - '@esbuild/win32-x64@0.27.7': - optional: true - - '@floating-ui/core@1.7.5': - dependencies: - '@floating-ui/utils': 0.2.11 - - '@floating-ui/dom@1.7.6': - dependencies: - '@floating-ui/core': 1.7.5 - '@floating-ui/utils': 0.2.11 - - '@floating-ui/react-dom@2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@floating-ui/dom': 1.7.6 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@floating-ui/utils@0.2.11': {} - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - - '@radix-ui/number@1.1.1': {} - - '@radix-ui/primitive@1.1.3': {} - - '@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.28)(react@18.3.1)': - dependencies: - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-context@1.1.2(@types/react@18.3.28)(react@18.3.1)': - dependencies: - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-dialog@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) - aria-hidden: 1.2.6 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-direction@1.1.1(@types/react@18.3.28)(react@18.3.1)': - dependencies: - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.28)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-focus-guards@1.1.3(@types/react@18.3.28)(react@18.3.1)': - dependencies: - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-id@1.1.1(@types/react@18.3.28)(react@18.3.1)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-menu@2.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) - aria-hidden: 1.2.6 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-popper@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@floating-ui/react-dom': 2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-use-rect': 1.1.1(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/rect': 1.1.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-presence@1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-primitive@2.1.4(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-slot': 1.2.4(@types/react@18.3.28)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/number': 1.1.1 - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-select@2.2.6(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/number': 1.1.1 - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - aria-hidden: 1.2.6 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-slot@1.2.3(@types/react@18.3.28)(react@18.3.1)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-slot@1.2.4(@types/react@18.3.28)(react@18.3.1)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-tabs@1.1.13(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-tooltip@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-use-callback-ref@1.1.1(@types/react@18.3.28)(react@18.3.1)': - dependencies: - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-use-controllable-state@1.2.2(@types/react@18.3.28)(react@18.3.1)': - dependencies: - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-use-effect-event@0.0.2(@types/react@18.3.28)(react@18.3.1)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@18.3.28)(react@18.3.1)': - dependencies: - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@18.3.28)(react@18.3.1)': - dependencies: - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-use-previous@1.1.1(@types/react@18.3.28)(react@18.3.1)': - dependencies: - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-use-rect@1.1.1(@types/react@18.3.28)(react@18.3.1)': - dependencies: - '@radix-ui/rect': 1.1.1 - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-use-size@1.1.1(@types/react@18.3.28)(react@18.3.1)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/rect@1.1.1': {} - - '@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1))(react@18.3.1)': - dependencies: - '@standard-schema/spec': 1.1.0 - '@standard-schema/utils': 0.3.0 - immer: 11.1.4 - redux: 5.0.1 - redux-thunk: 3.1.0(redux@5.0.1) - reselect: 5.1.1 - optionalDependencies: - react: 18.3.1 - react-redux: 9.2.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1) - - '@rolldown/pluginutils@1.0.0-beta.27': {} - - '@rollup/rollup-android-arm-eabi@4.60.2': - optional: true - - '@rollup/rollup-android-arm64@4.60.2': - optional: true - - '@rollup/rollup-darwin-arm64@4.60.2': - optional: true - - '@rollup/rollup-darwin-x64@4.60.2': - optional: true - - '@rollup/rollup-freebsd-arm64@4.60.2': - optional: true - - '@rollup/rollup-freebsd-x64@4.60.2': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.60.2': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.60.2': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.60.2': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.60.2': - optional: true - - '@rollup/rollup-linux-loong64-gnu@4.60.2': - optional: true - - '@rollup/rollup-linux-loong64-musl@4.60.2': - optional: true - - '@rollup/rollup-linux-ppc64-gnu@4.60.2': - optional: true - - '@rollup/rollup-linux-ppc64-musl@4.60.2': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.60.2': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.60.2': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.60.2': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.60.2': - optional: true - - '@rollup/rollup-linux-x64-musl@4.60.2': - optional: true - - '@rollup/rollup-openbsd-x64@4.60.2': - optional: true - - '@rollup/rollup-openharmony-arm64@4.60.2': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.60.2': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.60.2': - optional: true - - '@rollup/rollup-win32-x64-gnu@4.60.2': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.60.2': - optional: true - - '@standard-schema/spec@1.1.0': {} - - '@standard-schema/utils@0.3.0': {} - - '@tailwindcss/node@4.2.4': - dependencies: - '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.21.0 - jiti: 2.6.1 - lightningcss: 1.32.0 - magic-string: 0.30.21 - source-map-js: 1.2.1 - tailwindcss: 4.2.4 - - '@tailwindcss/oxide-android-arm64@4.2.4': - optional: true - - '@tailwindcss/oxide-darwin-arm64@4.2.4': - optional: true - - '@tailwindcss/oxide-darwin-x64@4.2.4': - optional: true - - '@tailwindcss/oxide-freebsd-x64@4.2.4': - optional: true - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': - optional: true - - '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': - optional: true - - '@tailwindcss/oxide-linux-arm64-musl@4.2.4': - optional: true - - '@tailwindcss/oxide-linux-x64-gnu@4.2.4': - optional: true - - '@tailwindcss/oxide-linux-x64-musl@4.2.4': - optional: true - - '@tailwindcss/oxide-wasm32-wasi@4.2.4': - optional: true - - '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': - optional: true - - '@tailwindcss/oxide-win32-x64-msvc@4.2.4': - optional: true - - '@tailwindcss/oxide@4.2.4': - optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.2.4 - '@tailwindcss/oxide-darwin-arm64': 4.2.4 - '@tailwindcss/oxide-darwin-x64': 4.2.4 - '@tailwindcss/oxide-freebsd-x64': 4.2.4 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.4 - '@tailwindcss/oxide-linux-arm64-gnu': 4.2.4 - '@tailwindcss/oxide-linux-arm64-musl': 4.2.4 - '@tailwindcss/oxide-linux-x64-gnu': 4.2.4 - '@tailwindcss/oxide-linux-x64-musl': 4.2.4 - '@tailwindcss/oxide-wasm32-wasi': 4.2.4 - '@tailwindcss/oxide-win32-arm64-msvc': 4.2.4 - '@tailwindcss/oxide-win32-x64-msvc': 4.2.4 - - '@tailwindcss/vite@4.2.4(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))': - dependencies: - '@tailwindcss/node': 4.2.4 - '@tailwindcss/oxide': 4.2.4 - tailwindcss: 4.2.4 - vite: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0) - - '@tanstack/history@1.161.6': {} - - '@tanstack/query-core@5.100.1': {} - - '@tanstack/query-devtools@5.100.1': {} - - '@tanstack/react-query-devtools@5.100.1(@tanstack/react-query@5.100.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@tanstack/query-devtools': 5.100.1 - '@tanstack/react-query': 5.100.1(react@18.3.1) - react: 18.3.1 - - '@tanstack/react-query@5.100.1(react@18.3.1)': - dependencies: - '@tanstack/query-core': 5.100.1 - react: 18.3.1 - - '@tanstack/react-router@1.168.23(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@tanstack/history': 1.161.6 - '@tanstack/react-store': 0.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@tanstack/router-core': 1.168.15 - isbot: 5.1.39 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@tanstack/react-store@0.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@tanstack/store': 0.9.3 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - use-sync-external-store: 1.6.0(react@18.3.1) - - '@tanstack/react-table@8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@tanstack/table-core': 8.21.3 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@tanstack/react-virtual@3.13.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@tanstack/virtual-core': 3.14.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@tanstack/router-core@1.168.15': - dependencies: - '@tanstack/history': 1.161.6 - cookie-es: 3.1.1 - seroval: 1.5.2 - seroval-plugins: 1.5.2(seroval@1.5.2) - - '@tanstack/router-generator@1.166.33': - dependencies: - '@babel/types': 7.29.0 - '@tanstack/router-core': 1.168.15 - '@tanstack/router-utils': 1.161.7 - '@tanstack/virtual-file-routes': 1.161.7 - magic-string: 0.30.21 - prettier: 3.8.3 - tsx: 4.21.0 - zod: 3.25.76 - transitivePeerDependencies: - - supports-color - - '@tanstack/router-plugin@1.167.23(@tanstack/react-router@1.168.23(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))': - dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) - '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - '@tanstack/router-core': 1.168.15 - '@tanstack/router-generator': 1.166.33 - '@tanstack/router-utils': 1.161.7 - '@tanstack/virtual-file-routes': 1.161.7 - chokidar: 3.6.0 - unplugin: 2.3.11 - zod: 3.25.76 - optionalDependencies: - '@tanstack/react-router': 1.168.23(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - vite: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0) - transitivePeerDependencies: - - supports-color - - '@tanstack/router-utils@1.161.7': - dependencies: - '@babel/core': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/parser': 7.29.2 - '@babel/types': 7.29.0 - ansis: 4.2.0 - babel-dead-code-elimination: 1.0.12 - diff: 8.0.4 - pathe: 2.0.3 - tinyglobby: 0.2.16 - transitivePeerDependencies: - - supports-color - - '@tanstack/store@0.9.3': {} - - '@tanstack/table-core@8.21.3': {} - - '@tanstack/virtual-core@3.14.0': {} - - '@tanstack/virtual-file-routes@1.161.7': {} - - '@types/babel__core@7.20.5': - dependencies: - '@babel/parser': 7.29.2 - '@babel/types': 7.29.0 - '@types/babel__generator': 7.27.0 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.28.0 - - '@types/babel__generator@7.27.0': - dependencies: - '@babel/types': 7.29.0 - - '@types/babel__template@7.4.4': - dependencies: - '@babel/parser': 7.29.2 - '@babel/types': 7.29.0 - - '@types/babel__traverse@7.28.0': - dependencies: - '@babel/types': 7.29.0 - - '@types/chai@5.2.3': - dependencies: - '@types/deep-eql': 4.0.2 - assertion-error: 2.0.1 - - '@types/d3-array@3.2.2': {} - - '@types/d3-color@3.1.3': {} - - '@types/d3-ease@3.0.2': {} - - '@types/d3-interpolate@3.0.4': - dependencies: - '@types/d3-color': 3.1.3 - - '@types/d3-path@3.1.1': {} - - '@types/d3-scale@4.0.9': - dependencies: - '@types/d3-time': 3.0.4 - - '@types/d3-shape@3.1.8': - dependencies: - '@types/d3-path': 3.1.1 - - '@types/d3-time@3.0.4': {} - - '@types/d3-timer@3.0.2': {} - - '@types/deep-eql@4.0.2': {} - - '@types/estree@1.0.8': {} - - '@types/node@22.19.17': - dependencies: - undici-types: 6.21.0 - - '@types/prop-types@15.7.15': {} - - '@types/react-dom@18.3.7(@types/react@18.3.28)': - dependencies: - '@types/react': 18.3.28 - - '@types/react@18.3.28': - dependencies: - '@types/prop-types': 15.7.15 - csstype: 3.2.3 - - '@types/use-sync-external-store@0.0.6': {} - - '@vitejs/plugin-react@4.7.0(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))': - dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) - '@rolldown/pluginutils': 1.0.0-beta.27 - '@types/babel__core': 7.20.5 - react-refresh: 0.17.0 - vite: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0) - transitivePeerDependencies: - - supports-color - - '@vitest/coverage-v8@4.1.5(vitest@4.1.5)': - dependencies: - '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.1.5 - ast-v8-to-istanbul: 1.0.0 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - istanbul-reports: 3.2.0 - magicast: 0.5.2 - obug: 2.1.1 - std-env: 4.1.0 - tinyrainbow: 3.1.0 - vitest: 4.1.5(@types/node@22.19.17)(@vitest/coverage-v8@4.1.5)(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)) - - '@vitest/expect@4.1.5': - dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.1.5 - '@vitest/utils': 4.1.5 - chai: 6.2.2 - tinyrainbow: 3.1.0 - - '@vitest/mocker@4.1.5(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))': - dependencies: - '@vitest/spy': 4.1.5 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0) - - '@vitest/pretty-format@4.1.5': - dependencies: - tinyrainbow: 3.1.0 - - '@vitest/runner@4.1.5': - dependencies: - '@vitest/utils': 4.1.5 - pathe: 2.0.3 - - '@vitest/snapshot@4.1.5': - dependencies: - '@vitest/pretty-format': 4.1.5 - '@vitest/utils': 4.1.5 - magic-string: 0.30.21 - pathe: 2.0.3 - - '@vitest/spy@4.1.5': {} - - '@vitest/utils@4.1.5': - dependencies: - '@vitest/pretty-format': 4.1.5 - convert-source-map: 2.0.0 - tinyrainbow: 3.1.0 - - acorn@8.16.0: {} - - ansis@4.2.0: {} - - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.2 - - aria-hidden@1.2.6: - dependencies: - tslib: 2.8.1 - - assertion-error@2.0.1: {} - - ast-v8-to-istanbul@1.0.0: - dependencies: - '@jridgewell/trace-mapping': 0.3.31 - estree-walker: 3.0.3 - js-tokens: 10.0.0 - - babel-dead-code-elimination@1.0.12: - dependencies: - '@babel/core': 7.29.0 - '@babel/parser': 7.29.2 - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - transitivePeerDependencies: - - supports-color - - baseline-browser-mapping@2.10.21: {} - - binary-extensions@2.3.0: {} - - braces@3.0.3: - dependencies: - fill-range: 7.1.1 - - browserslist@4.28.2: - dependencies: - baseline-browser-mapping: 2.10.21 - caniuse-lite: 1.0.30001790 - electron-to-chromium: 1.5.344 - node-releases: 2.0.38 - update-browserslist-db: 1.2.3(browserslist@4.28.2) - - caniuse-lite@1.0.30001790: {} - - chai@6.2.2: {} - - chokidar@3.6.0: - dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - - class-variance-authority@0.7.1: - dependencies: - clsx: 2.1.1 - - clsx@2.1.1: {} - - cmdk@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-dialog': 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.4(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - transitivePeerDependencies: - - '@types/react' - - '@types/react-dom' - - convert-source-map@2.0.0: {} - - cookie-es@3.1.1: {} - - csstype@3.2.3: {} - - d3-array@3.2.4: - dependencies: - internmap: 2.0.3 - - d3-color@3.1.0: {} - - d3-ease@3.0.1: {} - - d3-format@3.1.2: {} - - d3-interpolate@3.0.1: - dependencies: - d3-color: 3.1.0 - - d3-path@3.1.0: {} - - d3-scale@4.0.2: - dependencies: - d3-array: 3.2.4 - d3-format: 3.1.2 - d3-interpolate: 3.0.1 - d3-time: 3.1.0 - d3-time-format: 4.1.0 - - d3-shape@3.2.0: - dependencies: - d3-path: 3.1.0 - - d3-time-format@4.1.0: - dependencies: - d3-time: 3.1.0 - - d3-time@3.1.0: - dependencies: - d3-array: 3.2.4 - - d3-timer@3.0.1: {} - - debug@4.4.3: - dependencies: - ms: 2.1.3 - - decimal.js-light@2.5.1: {} - - detect-libc@2.1.2: {} - - detect-node-es@1.1.0: {} - - diff@8.0.4: {} - - electron-to-chromium@1.5.344: {} - - enhanced-resolve@5.21.0: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.3.3 - - es-module-lexer@2.0.0: {} - - es-toolkit@1.46.0: {} - - esbuild@0.25.12: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.12 - '@esbuild/android-arm': 0.25.12 - '@esbuild/android-arm64': 0.25.12 - '@esbuild/android-x64': 0.25.12 - '@esbuild/darwin-arm64': 0.25.12 - '@esbuild/darwin-x64': 0.25.12 - '@esbuild/freebsd-arm64': 0.25.12 - '@esbuild/freebsd-x64': 0.25.12 - '@esbuild/linux-arm': 0.25.12 - '@esbuild/linux-arm64': 0.25.12 - '@esbuild/linux-ia32': 0.25.12 - '@esbuild/linux-loong64': 0.25.12 - '@esbuild/linux-mips64el': 0.25.12 - '@esbuild/linux-ppc64': 0.25.12 - '@esbuild/linux-riscv64': 0.25.12 - '@esbuild/linux-s390x': 0.25.12 - '@esbuild/linux-x64': 0.25.12 - '@esbuild/netbsd-arm64': 0.25.12 - '@esbuild/netbsd-x64': 0.25.12 - '@esbuild/openbsd-arm64': 0.25.12 - '@esbuild/openbsd-x64': 0.25.12 - '@esbuild/openharmony-arm64': 0.25.12 - '@esbuild/sunos-x64': 0.25.12 - '@esbuild/win32-arm64': 0.25.12 - '@esbuild/win32-ia32': 0.25.12 - '@esbuild/win32-x64': 0.25.12 - - esbuild@0.27.7: - optionalDependencies: - '@esbuild/aix-ppc64': 0.27.7 - '@esbuild/android-arm': 0.27.7 - '@esbuild/android-arm64': 0.27.7 - '@esbuild/android-x64': 0.27.7 - '@esbuild/darwin-arm64': 0.27.7 - '@esbuild/darwin-x64': 0.27.7 - '@esbuild/freebsd-arm64': 0.27.7 - '@esbuild/freebsd-x64': 0.27.7 - '@esbuild/linux-arm': 0.27.7 - '@esbuild/linux-arm64': 0.27.7 - '@esbuild/linux-ia32': 0.27.7 - '@esbuild/linux-loong64': 0.27.7 - '@esbuild/linux-mips64el': 0.27.7 - '@esbuild/linux-ppc64': 0.27.7 - '@esbuild/linux-riscv64': 0.27.7 - '@esbuild/linux-s390x': 0.27.7 - '@esbuild/linux-x64': 0.27.7 - '@esbuild/netbsd-arm64': 0.27.7 - '@esbuild/netbsd-x64': 0.27.7 - '@esbuild/openbsd-arm64': 0.27.7 - '@esbuild/openbsd-x64': 0.27.7 - '@esbuild/openharmony-arm64': 0.27.7 - '@esbuild/sunos-x64': 0.27.7 - '@esbuild/win32-arm64': 0.27.7 - '@esbuild/win32-ia32': 0.27.7 - '@esbuild/win32-x64': 0.27.7 - - escalade@3.2.0: {} - - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.8 - - eventemitter3@5.0.4: {} - - expect-type@1.3.0: {} - - fdir@6.5.0(picomatch@4.0.4): - optionalDependencies: - picomatch: 4.0.4 - - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - - fsevents@2.3.3: - optional: true - - gensync@1.0.0-beta.2: {} - - get-nonce@1.0.1: {} - - get-tsconfig@4.14.0: - dependencies: - resolve-pkg-maps: 1.0.0 - - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - - graceful-fs@4.2.11: {} - - has-flag@4.0.0: {} - - html-escaper@2.0.2: {} - - immer@10.2.0: {} - - immer@11.1.4: {} - - internmap@2.0.3: {} - - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - - is-extglob@2.1.1: {} - - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - - is-number@7.0.0: {} - - isbot@5.1.39: {} - - istanbul-lib-coverage@3.2.2: {} - - istanbul-lib-report@3.0.1: - dependencies: - istanbul-lib-coverage: 3.2.2 - make-dir: 4.0.0 - supports-color: 7.2.0 - - istanbul-reports@3.2.0: - dependencies: - html-escaper: 2.0.2 - istanbul-lib-report: 3.0.1 - - jiti@2.6.1: {} - - js-tokens@10.0.0: {} - - js-tokens@4.0.0: {} - - jsesc@3.1.0: {} - - json5@2.2.3: {} - - lightningcss-android-arm64@1.32.0: - optional: true - - lightningcss-darwin-arm64@1.32.0: - optional: true - - lightningcss-darwin-x64@1.32.0: - optional: true - - lightningcss-freebsd-x64@1.32.0: - optional: true - - lightningcss-linux-arm-gnueabihf@1.32.0: - optional: true - - lightningcss-linux-arm64-gnu@1.32.0: - optional: true - - lightningcss-linux-arm64-musl@1.32.0: - optional: true - - lightningcss-linux-x64-gnu@1.32.0: - optional: true - - lightningcss-linux-x64-musl@1.32.0: - optional: true - - lightningcss-win32-arm64-msvc@1.32.0: - optional: true - - lightningcss-win32-x64-msvc@1.32.0: - optional: true - - lightningcss@1.32.0: - dependencies: - detect-libc: 2.1.2 - optionalDependencies: - lightningcss-android-arm64: 1.32.0 - lightningcss-darwin-arm64: 1.32.0 - lightningcss-darwin-x64: 1.32.0 - lightningcss-freebsd-x64: 1.32.0 - lightningcss-linux-arm-gnueabihf: 1.32.0 - lightningcss-linux-arm64-gnu: 1.32.0 - lightningcss-linux-arm64-musl: 1.32.0 - lightningcss-linux-x64-gnu: 1.32.0 - lightningcss-linux-x64-musl: 1.32.0 - lightningcss-win32-arm64-msvc: 1.32.0 - lightningcss-win32-x64-msvc: 1.32.0 - - loose-envify@1.4.0: - dependencies: - js-tokens: 4.0.0 - - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 - - lucide-react@0.577.0(react@18.3.1): - dependencies: - react: 18.3.1 - - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - - magicast@0.5.2: - dependencies: - '@babel/parser': 7.29.2 - '@babel/types': 7.29.0 - source-map-js: 1.2.1 - - make-dir@4.0.0: - dependencies: - semver: 7.7.4 - - ms@2.1.3: {} - - nanoid@3.3.11: {} - - node-releases@2.0.38: {} - - normalize-path@3.0.0: {} - - obug@2.1.1: {} - - pathe@2.0.3: {} - - picocolors@1.1.1: {} - - picomatch@2.3.2: {} - - picomatch@4.0.4: {} - - postcss@8.5.10: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - prettier@3.8.3: {} - - react-dom@18.3.1(react@18.3.1): - dependencies: - loose-envify: 1.4.0 - react: 18.3.1 - scheduler: 0.23.2 - - react-error-boundary@6.1.1(react@18.3.1): - dependencies: - react: 18.3.1 - - react-is@19.2.5: {} - - react-redux@9.2.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1): - dependencies: - '@types/use-sync-external-store': 0.0.6 - react: 18.3.1 - use-sync-external-store: 1.6.0(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.28 - redux: 5.0.1 - - react-refresh@0.17.0: {} - - react-remove-scroll-bar@2.3.8(@types/react@18.3.28)(react@18.3.1): - dependencies: - react: 18.3.1 - react-style-singleton: 2.2.3(@types/react@18.3.28)(react@18.3.1) - tslib: 2.8.1 - optionalDependencies: - '@types/react': 18.3.28 - - react-remove-scroll@2.7.2(@types/react@18.3.28)(react@18.3.1): - dependencies: - react: 18.3.1 - react-remove-scroll-bar: 2.3.8(@types/react@18.3.28)(react@18.3.1) - react-style-singleton: 2.2.3(@types/react@18.3.28)(react@18.3.1) - tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@18.3.28)(react@18.3.1) - use-sidecar: 1.1.3(@types/react@18.3.28)(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.28 - - react-style-singleton@2.2.3(@types/react@18.3.28)(react@18.3.1): - dependencies: - get-nonce: 1.0.1 - react: 18.3.1 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 18.3.28 - - react@18.3.1: - dependencies: - loose-envify: 1.4.0 - - readdirp@3.6.0: - dependencies: - picomatch: 2.3.2 - - recharts@3.8.1(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react-is@19.2.5)(react@18.3.1)(redux@5.0.1): - dependencies: - '@reduxjs/toolkit': 2.11.2(react-redux@9.2.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1))(react@18.3.1) - clsx: 2.1.1 - decimal.js-light: 2.5.1 - es-toolkit: 1.46.0 - eventemitter3: 5.0.4 - immer: 10.2.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-is: 19.2.5 - react-redux: 9.2.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1) - reselect: 5.1.1 - tiny-invariant: 1.3.3 - use-sync-external-store: 1.6.0(react@18.3.1) - victory-vendor: 37.3.6 - transitivePeerDependencies: - - '@types/react' - - redux - - redux-thunk@3.1.0(redux@5.0.1): - dependencies: - redux: 5.0.1 - - redux@5.0.1: {} - - reselect@5.1.1: {} - - resolve-pkg-maps@1.0.0: {} - - rollup@4.60.2: - dependencies: - '@types/estree': 1.0.8 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.60.2 - '@rollup/rollup-android-arm64': 4.60.2 - '@rollup/rollup-darwin-arm64': 4.60.2 - '@rollup/rollup-darwin-x64': 4.60.2 - '@rollup/rollup-freebsd-arm64': 4.60.2 - '@rollup/rollup-freebsd-x64': 4.60.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.60.2 - '@rollup/rollup-linux-arm-musleabihf': 4.60.2 - '@rollup/rollup-linux-arm64-gnu': 4.60.2 - '@rollup/rollup-linux-arm64-musl': 4.60.2 - '@rollup/rollup-linux-loong64-gnu': 4.60.2 - '@rollup/rollup-linux-loong64-musl': 4.60.2 - '@rollup/rollup-linux-ppc64-gnu': 4.60.2 - '@rollup/rollup-linux-ppc64-musl': 4.60.2 - '@rollup/rollup-linux-riscv64-gnu': 4.60.2 - '@rollup/rollup-linux-riscv64-musl': 4.60.2 - '@rollup/rollup-linux-s390x-gnu': 4.60.2 - '@rollup/rollup-linux-x64-gnu': 4.60.2 - '@rollup/rollup-linux-x64-musl': 4.60.2 - '@rollup/rollup-openbsd-x64': 4.60.2 - '@rollup/rollup-openharmony-arm64': 4.60.2 - '@rollup/rollup-win32-arm64-msvc': 4.60.2 - '@rollup/rollup-win32-ia32-msvc': 4.60.2 - '@rollup/rollup-win32-x64-gnu': 4.60.2 - '@rollup/rollup-win32-x64-msvc': 4.60.2 - fsevents: 2.3.3 - - scheduler@0.23.2: - dependencies: - loose-envify: 1.4.0 - - semver@6.3.1: {} - - semver@7.7.4: {} - - seroval-plugins@1.5.2(seroval@1.5.2): - dependencies: - seroval: 1.5.2 - - seroval@1.5.2: {} - - siginfo@2.0.0: {} - - sonner@1.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - source-map-js@1.2.1: {} - - stackback@0.0.2: {} - - std-env@4.1.0: {} - - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - - tailwind-merge@2.6.1: {} - - tailwindcss@4.2.4: {} - - tapable@2.3.3: {} - - tiny-invariant@1.3.3: {} - - tinybench@2.9.0: {} - - tinyexec@1.1.1: {} - - tinyglobby@0.2.16: - dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 - - tinyrainbow@3.1.0: {} - - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - - tslib@2.8.1: {} - - tsx@4.21.0: - dependencies: - esbuild: 0.27.7 - get-tsconfig: 4.14.0 - optionalDependencies: - fsevents: 2.3.3 - - typescript@5.9.3: {} - - undici-types@6.21.0: {} - - unplugin@2.3.11: - dependencies: - '@jridgewell/remapping': 2.3.5 - acorn: 8.16.0 - picomatch: 4.0.4 - webpack-virtual-modules: 0.6.2 - - update-browserslist-db@1.2.3(browserslist@4.28.2): - dependencies: - browserslist: 4.28.2 - escalade: 3.2.0 - picocolors: 1.1.1 - - use-callback-ref@1.3.3(@types/react@18.3.28)(react@18.3.1): - dependencies: - react: 18.3.1 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 18.3.28 - - use-sidecar@1.1.3(@types/react@18.3.28)(react@18.3.1): - dependencies: - detect-node-es: 1.1.0 - react: 18.3.1 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 18.3.28 - - use-sync-external-store@1.6.0(react@18.3.1): - dependencies: - react: 18.3.1 - - victory-vendor@37.3.6: - dependencies: - '@types/d3-array': 3.2.2 - '@types/d3-ease': 3.0.2 - '@types/d3-interpolate': 3.0.4 - '@types/d3-scale': 4.0.9 - '@types/d3-shape': 3.1.8 - '@types/d3-time': 3.0.4 - '@types/d3-timer': 3.0.2 - d3-array: 3.2.4 - d3-ease: 3.0.1 - d3-interpolate: 3.0.1 - d3-scale: 4.0.2 - d3-shape: 3.2.0 - d3-time: 3.1.0 - d3-timer: 3.0.1 - - vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0): - dependencies: - esbuild: 0.25.12 - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 - postcss: 8.5.10 - rollup: 4.60.2 - tinyglobby: 0.2.16 - optionalDependencies: - '@types/node': 22.19.17 - fsevents: 2.3.3 - jiti: 2.6.1 - lightningcss: 1.32.0 - tsx: 4.21.0 - - vitest@4.1.5(@types/node@22.19.17)(@vitest/coverage-v8@4.1.5)(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)): - dependencies: - '@vitest/expect': 4.1.5 - '@vitest/mocker': 4.1.5(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)) - '@vitest/pretty-format': 4.1.5 - '@vitest/runner': 4.1.5 - '@vitest/snapshot': 4.1.5 - '@vitest/spy': 4.1.5 - '@vitest/utils': 4.1.5 - es-module-lexer: 2.0.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.4 - std-env: 4.1.0 - tinybench: 2.9.0 - tinyexec: 1.1.1 - tinyglobby: 0.2.16 - tinyrainbow: 3.1.0 - vite: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 22.19.17 - '@vitest/coverage-v8': 4.1.5(vitest@4.1.5) - transitivePeerDependencies: - - msw - - webpack-virtual-modules@0.6.2: {} - - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 - - yallist@3.1.1: {} - - zod@3.25.76: {} diff --git a/dashboard-next/src/components/layout/header.tsx b/dashboard-next/src/components/layout/header.tsx deleted file mode 100644 index 8f5de82..0000000 --- a/dashboard-next/src/components/layout/header.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { Search } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { Kbd } from "@/components/ui/kbd"; -import { useCommandPalette } from "@/providers/command-palette-provider"; -import { LastRefreshed } from "./last-refreshed"; -import { MobileMenu } from "./mobile-menu"; -import { RefreshControl } from "./refresh-control"; -import { ThemeToggle } from "./theme-toggle"; - -export function Header() { - const { setOpen } = useCommandPalette(); - return ( -
    - - - -
    - - - -
    -
    - ); -} diff --git a/dashboard-next/src/components/layout/index.ts b/dashboard-next/src/components/layout/index.ts deleted file mode 100644 index c842370..0000000 --- a/dashboard-next/src/components/layout/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -export { AppShell } from "./app-shell"; -export type { Crumb } from "./breadcrumbs"; -export { Breadcrumbs } from "./breadcrumbs"; -export { CommandPalette } from "./command-palette"; -export { Header } from "./header"; -export { LastRefreshed } from "./last-refreshed"; -export { MobileMenu } from "./mobile-menu"; -export { PageHeader } from "./page-header"; -export { RefreshControl } from "./refresh-control"; -export { RouteErrorBoundary } from "./route-error-boundary"; -export { Sidebar } from "./sidebar"; -export { ThemeToggle } from "./theme-toggle"; diff --git a/dashboard-next/src/components/layout/sidebar.tsx b/dashboard-next/src/components/layout/sidebar.tsx deleted file mode 100644 index 30910a7..0000000 --- a/dashboard-next/src/components/layout/sidebar.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { Link, useLocation } from "@tanstack/react-router"; -import { - Activity, - AlertOctagon, - BarChart3, - Box, - CircuitBoard, - LayoutDashboard, - ListTree, - type LucideIcon, - ScrollText, - Server, - Settings2, - Skull, -} from "lucide-react"; -import { cn } from "@/lib/cn"; -import { site } from "@/lib/site"; - -interface NavItem { - to: string; - label: string; - icon: LucideIcon; -} - -interface NavGroup { - title: string; - items: NavItem[]; -} - -const NAV: NavGroup[] = [ - { - title: "Monitoring", - items: [ - { to: "/", label: "Overview", icon: LayoutDashboard }, - { to: "/jobs", label: "Jobs", icon: ListTree }, - { to: "/metrics", label: "Metrics", icon: BarChart3 }, - { to: "/logs", label: "Logs", icon: ScrollText }, - ], - }, - { - title: "Infrastructure", - items: [ - { to: "/queues", label: "Queues", icon: Box }, - { to: "/workers", label: "Workers", icon: Server }, - { to: "/resources", label: "Resources", icon: Activity }, - ], - }, - { - title: "Reliability", - items: [ - { to: "/dead-letters", label: "Dead letters", icon: Skull }, - { to: "/circuit-breakers", label: "Circuit breakers", icon: CircuitBoard }, - { to: "/system", label: "System", icon: Settings2 }, - ], - }, -]; - -export function Sidebar() { - const { pathname } = useLocation(); - return ( - - ); -} diff --git a/dashboard-next/src/components/ui/badge.tsx b/dashboard-next/src/components/ui/badge.tsx deleted file mode 100644 index 2cfe305..0000000 --- a/dashboard-next/src/components/ui/badge.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { cva, type VariantProps } from "class-variance-authority"; -import type { HTMLAttributes } from "react"; -import { cn } from "@/lib/cn"; - -const badgeVariants = cva( - "inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium tracking-tight", - { - variants: { - tone: { - neutral: - "bg-[var(--surface-3)] text-[var(--fg-muted)] ring-1 ring-inset ring-[var(--border-strong)]", - accent: "bg-accent-dim text-accent ring-1 ring-inset ring-accent/30", - info: "bg-info-dim text-info ring-1 ring-inset ring-info/30", - success: "bg-success-dim text-success ring-1 ring-inset ring-success/30", - warning: "bg-warning-dim text-warning ring-1 ring-inset ring-warning/30", - danger: "bg-danger-dim text-danger ring-1 ring-inset ring-danger/30", - }, - }, - defaultVariants: { - tone: "neutral", - }, - }, -); - -export interface BadgeProps - extends HTMLAttributes, - VariantProps {} - -export function Badge({ className, tone, ...props }: BadgeProps) { - return ; -} - -export { badgeVariants }; diff --git a/dashboard-next/src/components/ui/button.tsx b/dashboard-next/src/components/ui/button.tsx deleted file mode 100644 index cc24c0d..0000000 --- a/dashboard-next/src/components/ui/button.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { cva, type VariantProps } from "class-variance-authority"; -import { type ButtonHTMLAttributes, forwardRef } from "react"; -import { cn } from "@/lib/cn"; - -const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-ring)] disabled:pointer-events-none disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0", - { - variants: { - variant: { - default: "bg-accent text-accent-fg hover:bg-accent/90 shadow-sm", - secondary: - "bg-[var(--surface-2)] text-[var(--fg)] hover:bg-[var(--surface-3)] ring-1 ring-inset ring-[var(--border-strong)]", - ghost: "text-[var(--fg-muted)] hover:bg-[var(--surface-2)] hover:text-[var(--fg)]", - outline: - "ring-1 ring-inset ring-[var(--border-strong)] bg-transparent text-[var(--fg)] hover:bg-[var(--surface-2)]", - danger: "bg-danger text-white hover:bg-danger/90 shadow-sm", - link: "text-accent underline-offset-4 hover:underline", - }, - size: { - default: "h-9 px-3.5 py-2", - sm: "h-8 rounded-md px-3 text-xs", - lg: "h-10 rounded-md px-5", - icon: "size-9", - }, - }, - defaultVariants: { - variant: "default", - size: "default", - }, - }, -); - -export interface ButtonProps - extends ButtonHTMLAttributes, - VariantProps {} - -export const Button = forwardRef( - ({ className, variant, size, type = "button", ...props }, ref) => ( - - - - - - ); -} diff --git a/dashboard-next/src/components/ui/data-table.tsx b/dashboard-next/src/components/ui/data-table.tsx deleted file mode 100644 index 9e9ae81..0000000 --- a/dashboard-next/src/components/ui/data-table.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { - type ColumnDef, - flexRender, - getCoreRowModel, - getSortedRowModel, - type Row, - type SortingState, - useReactTable, -} from "@tanstack/react-table"; -import { ArrowDown, ArrowUp, ChevronsUpDown } from "lucide-react"; -import { type ReactNode, useState } from "react"; -import { cn } from "@/lib/cn"; -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./table"; - -interface DataTableProps { - columns: ColumnDef[]; - data: TData[]; - empty?: ReactNode; - onRowClick?: (row: TData) => void; - rowKey?: (row: TData, index: number) => string; - className?: string; - initialSorting?: SortingState; -} - -export function DataTable({ - columns, - data, - empty, - onRowClick, - rowKey, - className, - initialSorting = [], -}: DataTableProps) { - const [sorting, setSorting] = useState(initialSorting); - - const table = useReactTable({ - data, - columns, - state: { sorting }, - onSortingChange: setSorting, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), - }); - - return ( -
    - - - {table.getHeaderGroups().map((group) => ( - - {group.headers.map((header) => { - const canSort = header.column.getCanSort(); - const sorted = header.column.getIsSorted(); - return ( - - {header.isPlaceholder ? null : canSort ? ( - - ) : ( - flexRender(header.column.columnDef.header, header.getContext()) - )} - - ); - })} - - ))} - - - {table.getRowModel().rows.length === 0 ? ( - - - {empty ?? "No data"} - - - ) : ( - table.getRowModel().rows.map((row: Row, index) => ( - onRowClick(row.original) : undefined} - onKeyDown={ - onRowClick - ? (event) => { - if (event.key === "Enter" || event.key === " ") { - event.preventDefault(); - onRowClick(row.original); - } - } - : undefined - } - > - {row.getVisibleCells().map((cell) => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} - - )) - )} - -
    -
    - ); -} diff --git a/dashboard-next/src/components/ui/empty-state.tsx b/dashboard-next/src/components/ui/empty-state.tsx deleted file mode 100644 index e4fee69..0000000 --- a/dashboard-next/src/components/ui/empty-state.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import type { LucideIcon } from "lucide-react"; -import type { ReactNode } from "react"; -import { cn } from "@/lib/cn"; - -interface EmptyStateProps { - icon?: LucideIcon; - title: string; - description?: string; - action?: ReactNode; - className?: string; -} - -export function EmptyState({ icon: Icon, title, description, action, className }: EmptyStateProps) { - return ( -
    - {Icon ? ( -
    - -
    - ) : null} -
    -
    {title}
    - {description ? ( -
    {description}
    - ) : null} -
    - {action} -
    - ); -} diff --git a/dashboard-next/src/components/ui/error-state.tsx b/dashboard-next/src/components/ui/error-state.tsx deleted file mode 100644 index 718ac6b..0000000 --- a/dashboard-next/src/components/ui/error-state.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { AlertTriangle, type LucideIcon } from "lucide-react"; -import type { ReactNode } from "react"; -import { cn } from "@/lib/cn"; -import { Button } from "./button"; - -interface ErrorStateProps { - icon?: LucideIcon; - title?: string; - description?: string; - onRetry?: () => void; - retryLabel?: string; - action?: ReactNode; - className?: string; -} - -export function ErrorState({ - icon: Icon = AlertTriangle, - title = "Something went wrong", - description, - onRetry, - retryLabel = "Retry", - action, - className, -}: ErrorStateProps) { - return ( -
    -
    - -
    -
    -
    {title}
    - {description ? ( -
    {description}
    - ) : null} -
    - {action ?? - (onRetry ? ( - - ) : null)} -
    - ); -} diff --git a/dashboard-next/src/components/ui/index.ts b/dashboard-next/src/components/ui/index.ts deleted file mode 100644 index c8dcd7e..0000000 --- a/dashboard-next/src/components/ui/index.ts +++ /dev/null @@ -1,98 +0,0 @@ -export { Badge, badgeVariants } from "./badge"; -export { Button, buttonVariants } from "./button"; -export { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "./card"; -export { - Command, - CommandDialog, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, - CommandSeparator, - CommandShortcut, -} from "./command"; -export { ConfirmDialog } from "./confirm-dialog"; -export { DataTable } from "./data-table"; -export { DestructiveConfirmDialog } from "./destructive-confirm-dialog"; -export { - Dialog, - DialogClose, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogOverlay, - DialogPortal, - DialogTitle, - DialogTrigger, -} from "./dialog"; -export { - DropdownMenu, - DropdownMenuCheckboxItem, - DropdownMenuContent, - DropdownMenuGroup, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuPortal, - DropdownMenuRadioGroup, - DropdownMenuRadioItem, - DropdownMenuSeparator, - DropdownMenuShortcut, - DropdownMenuSub, - DropdownMenuSubContent, - DropdownMenuSubTrigger, - DropdownMenuTrigger, -} from "./dropdown-menu"; -export { EmptyState } from "./empty-state"; -export { ErrorState } from "./error-state"; -export { Input } from "./input"; -export { Kbd } from "./kbd"; -export { Pagination } from "./pagination"; -export { ScrollArea, ScrollBar } from "./scroll-area"; -export { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectLabel, - SelectScrollDownButton, - SelectScrollUpButton, - SelectSeparator, - SelectTrigger, - SelectValue, -} from "./select"; -export { Separator } from "./separator"; -export { - Sheet, - SheetClose, - SheetContent, - SheetDescription, - SheetHeader, - SheetOverlay, - SheetPortal, - SheetTitle, - SheetTrigger, -} from "./sheet"; -export { Skeleton } from "./skeleton"; -export { StatCard } from "./stat-card"; -export { - Table, - TableBody, - TableCaption, - TableCell, - TableFooter, - TableHead, - TableHeader, - TableRow, -} from "./table"; -export { Tabs, TabsContent, TabsList, TabsTrigger } from "./tabs"; -export { Toaster, toast } from "./toaster"; -export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip"; diff --git a/dashboard-next/src/components/ui/pagination.tsx b/dashboard-next/src/components/ui/pagination.tsx deleted file mode 100644 index 34bff4d..0000000 --- a/dashboard-next/src/components/ui/pagination.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { ChevronLeft, ChevronRight } from "lucide-react"; -import { cn } from "@/lib/cn"; -import { Button } from "./button"; - -interface PaginationProps { - page: number; - pageCount?: number; - hasMore?: boolean; - onChange: (page: number) => void; - className?: string; -} - -export function Pagination({ page, pageCount, hasMore, onChange, className }: PaginationProps) { - const canNext = pageCount != null ? page < pageCount - 1 : Boolean(hasMore); - const canPrev = page > 0; - return ( -
    - - Page {page + 1} - {pageCount != null ? ( - <> - {" "} - of {pageCount} - - ) : null} - -
    - - -
    -
    - ); -} diff --git a/dashboard-next/src/components/ui/stat-card.tsx b/dashboard-next/src/components/ui/stat-card.tsx deleted file mode 100644 index e4db07e..0000000 --- a/dashboard-next/src/components/ui/stat-card.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { forwardRef, type HTMLAttributes, type ReactNode } from "react"; -import { cn } from "@/lib/cn"; -import { Card } from "./card"; - -interface StatCardProps extends HTMLAttributes { - label: string; - value: ReactNode; - hint?: ReactNode; - icon?: ReactNode; - trend?: "up" | "down" | "flat"; - tone?: "neutral" | "accent" | "success" | "warning" | "danger" | "info"; -} - -const TONE_RING: Record, string> = { - neutral: "text-[var(--fg-muted)]", - accent: "text-accent", - info: "text-info", - success: "text-success", - warning: "text-warning", - danger: "text-danger", -}; - -export const StatCard = forwardRef( - ({ label, value, hint, icon, tone = "neutral", className, ...props }, ref) => ( - -
    -
    - {label} -
    - {icon ?
    {icon}
    : null} -
    -
    {value}
    - {hint ?
    {hint}
    : null} -
    - ), -); -StatCard.displayName = "StatCard"; diff --git a/dashboard-next/src/hooks/index.ts b/dashboard-next/src/hooks/index.ts deleted file mode 100644 index 84ccbe6..0000000 --- a/dashboard-next/src/hooks/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { useDebouncedValue } from "./use-debounced-value"; -export { useLastRefreshed } from "./use-last-refreshed"; -export { useMediaQuery } from "./use-media-query"; diff --git a/dashboard-next/src/main.tsx b/dashboard-next/src/main.tsx deleted file mode 100644 index f4132f9..0000000 --- a/dashboard-next/src/main.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { createRouter, RouterProvider } from "@tanstack/react-router"; -import { StrictMode } from "react"; -import { createRoot } from "react-dom/client"; -import { Providers } from "@/providers"; -import { routeTree } from "./route-tree.gen"; -import "./globals.css"; - -const router = createRouter({ - routeTree, - defaultPreload: "intent", -}); - -declare module "@tanstack/react-router" { - interface Register { - router: typeof router; - } -} - -const rootEl = document.getElementById("app"); -if (!rootEl) throw new Error("#app element not found"); - -createRoot(rootEl).render( - - - - - , -); diff --git a/dashboard-next/src/vite-env.d.ts b/dashboard-next/src/vite-env.d.ts deleted file mode 100644 index 11f02fe..0000000 --- a/dashboard-next/src/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/dashboard-next/tsconfig.json b/dashboard-next/tsconfig.json deleted file mode 100644 index b003e88..0000000 --- a/dashboard-next/tsconfig.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "moduleResolution": "bundler", - "strict": true, - "noUncheckedIndexedAccess": true, - "noImplicitOverride": true, - "jsx": "react-jsx", - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "baseUrl": ".", - "paths": { - "@/*": ["./src/*"] - }, - "types": ["node", "vite/client"] - }, - "include": ["src", "vite.config.ts"] -} diff --git a/dashboard-next/vite.config.ts b/dashboard-next/vite.config.ts deleted file mode 100644 index ab0bd06..0000000 --- a/dashboard-next/vite.config.ts +++ /dev/null @@ -1,40 +0,0 @@ -import path from "node:path"; -import { tanstackRouter } from "@tanstack/router-plugin/vite"; -import tailwindcss from "@tailwindcss/vite"; -import react from "@vitejs/plugin-react"; -import { defineConfig } from "vite"; - -const backend = "http://127.0.0.1:8080"; - -export default defineConfig({ - plugins: [ - tanstackRouter({ - target: "react", - routesDirectory: "./src/routes", - generatedRouteTree: "./src/route-tree.gen.ts", - quoteStyle: "double", - semicolons: true, - }), - react(), - tailwindcss(), - ], - resolve: { - alias: { - "@": path.resolve(__dirname, "./src"), - }, - }, - build: { - outDir: "../py_src/taskito/static/dashboard", - emptyOutDir: true, - sourcemap: false, - target: "es2022", - }, - server: { - proxy: { - "/api": backend, - "/health": backend, - "/readiness": backend, - "/metrics": backend, - }, - }, -}); diff --git a/dashboard-next/.gitignore b/dashboard/.gitignore similarity index 100% rename from dashboard-next/.gitignore rename to dashboard/.gitignore diff --git a/dashboard/biome.json b/dashboard/biome.json index a62ddd9..0a95199 100644 --- a/dashboard/biome.json +++ b/dashboard/biome.json @@ -10,6 +10,12 @@ }, "complexity": { "noForEach": "off" + }, + "correctness": { + "useExhaustiveDependencies": "warn" + }, + "style": { + "noNonNullAssertion": "off" } } }, @@ -34,6 +40,6 @@ } }, "files": { - "includes": ["src/**/*.ts", "src/**/*.tsx"] + "includes": ["src/**/*.ts", "src/**/*.tsx", "!**/*.gen.ts"] } } diff --git a/dashboard-next/components.json b/dashboard/components.json similarity index 100% rename from dashboard-next/components.json rename to dashboard/components.json diff --git a/dashboard/index.html b/dashboard/index.html index 9d3be91..249a8f3 100644 --- a/dashboard/index.html +++ b/dashboard/index.html @@ -1,9 +1,18 @@ - + - taskito dashboard + + Taskito +
    diff --git a/dashboard/package.json b/dashboard/package.json index d671583..5c893d3 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -1,33 +1,58 @@ { - "name": "taskito-dashboard", + "name": "taskito-dashboard-next", "private": true, - "version": "0.10.0", + "version": "0.11.0", "type": "module", "scripts": { "dev": "vite", - "build": "vite build && sed -i 's/vertical-align:middle;display:block/display:block/g' dist/index.html && cp dist/index.html ../py_src/taskito/templates/dashboard.html", + "build": "vite build", "preview": "vite preview", "typecheck": "tsc --noEmit", "lint": "biome check src/", "lint:fix": "biome check --fix src/", "format": "biome format --write src/", "format:check": "biome format src/", - "ci": "biome ci src/ && tsc --noEmit && vite build" + "test": "vitest run", + "test:watch": "vitest", + "ci": "biome ci src/ && tsc --noEmit && vitest run && vite build" }, "dependencies": { - "@preact/signals": "^1.3.0", - "lucide-preact": "^0.577.0", - "preact": "^10.25.0", - "preact-router": "^4.1.2" + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-tooltip": "^1.2.8", + "@tanstack/react-query": "^5.62.7", + "@tanstack/react-query-devtools": "^5.62.7", + "@tanstack/react-router": "^1.95.1", + "@tanstack/react-table": "^8.21.3", + "@tanstack/react-virtual": "^3.13.24", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "lucide-react": "^0.577.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-error-boundary": "^6.1.1", + "recharts": "^3.8.1", + "sonner": "^1.7.1", + "tailwind-merge": "^2.6.0" }, "devDependencies": { "@biomejs/biome": "^2.4.8", - "@preact/preset-vite": "^2.9.0", "@tailwindcss/vite": "^4.0.0", + "@tanstack/router-plugin": "^1.95.1", + "@types/node": "^22.10.5", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "@vitejs/plugin-react": "^4.3.4", + "@vitest/coverage-v8": "^4.1.5", "tailwindcss": "^4.0.0", "typescript": "^5.7.0", "vite": "^6.4.2", - "vite-plugin-singlefile": "^2.0.0" + "vitest": "^4.1.5" }, "packageManager": "pnpm@10.30.3" } diff --git a/dashboard/pnpm-lock.yaml b/dashboard/pnpm-lock.yaml index 812f830..7eeca12 100644 --- a/dashboard/pnpm-lock.yaml +++ b/dashboard/pnpm-lock.yaml @@ -8,28 +8,97 @@ importers: .: dependencies: - '@preact/signals': - specifier: ^1.3.0 - version: 1.3.4(preact@10.29.1) - lucide-preact: + '@radix-ui/react-dialog': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.16 + version: 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-scroll-area': + specifier: ^1.2.10 + version: 1.2.10(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-select': + specifier: ^2.2.6 + version: 2.2.6(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': + specifier: ^1.2.4 + version: 1.2.4(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-tabs': + specifier: ^1.1.13 + version: 1.1.13(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-tooltip': + specifier: ^1.2.8 + version: 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tanstack/react-query': + specifier: ^5.62.7 + version: 5.100.1(react@18.3.1) + '@tanstack/react-query-devtools': + specifier: ^5.62.7 + version: 5.100.1(@tanstack/react-query@5.100.1(react@18.3.1))(react@18.3.1) + '@tanstack/react-router': + specifier: ^1.95.1 + version: 1.168.23(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tanstack/react-table': + specifier: ^8.21.3 + version: 8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tanstack/react-virtual': + specifier: ^3.13.24 + version: 3.13.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + cmdk: + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + lucide-react: specifier: ^0.577.0 - version: 0.577.0(preact@10.29.1) - preact: - specifier: ^10.25.0 - version: 10.29.1 - preact-router: - specifier: ^4.1.2 - version: 4.1.2(preact@10.29.1) + version: 0.577.0(react@18.3.1) + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + react-error-boundary: + specifier: ^6.1.1 + version: 6.1.1(react@18.3.1) + recharts: + specifier: ^3.8.1 + version: 3.8.1(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react-is@19.2.5)(react@18.3.1)(redux@5.0.1) + sonner: + specifier: ^1.7.1 + version: 1.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + tailwind-merge: + specifier: ^2.6.0 + version: 2.6.1 devDependencies: '@biomejs/biome': specifier: ^2.4.8 version: 2.4.13 - '@preact/preset-vite': - specifier: ^2.9.0 - version: 2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.60.2)(vite@6.4.2(jiti@2.6.1)(lightningcss@1.32.0)) '@tailwindcss/vite': specifier: ^4.0.0 - version: 4.2.4(vite@6.4.2(jiti@2.6.1)(lightningcss@1.32.0)) + version: 4.2.4(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)) + '@tanstack/router-plugin': + specifier: ^1.95.1 + version: 1.167.23(@tanstack/react-router@1.168.23(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)) + '@types/node': + specifier: ^22.10.5 + version: 22.19.17 + '@types/react': + specifier: ^18.3.18 + version: 18.3.28 + '@types/react-dom': + specifier: ^18.3.5 + version: 18.3.7(@types/react@18.3.28) + '@vitejs/plugin-react': + specifier: ^4.3.4 + version: 4.7.0(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)) + '@vitest/coverage-v8': + specifier: ^4.1.5 + version: 4.1.5(vitest@4.1.5) tailwindcss: specifier: ^4.0.0 version: 4.2.4 @@ -38,10 +107,10 @@ importers: version: 5.9.3 vite: specifier: ^6.4.2 - version: 6.4.2(jiti@2.6.1)(lightningcss@1.32.0) - vite-plugin-singlefile: - specifier: ^2.0.0 - version: 2.3.3(rollup@4.60.2)(vite@6.4.2(jiti@2.6.1)(lightningcss@1.32.0)) + version: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0) + vitest: + specifier: ^4.1.5 + version: 4.1.5(@types/node@22.19.17)(@vitest/coverage-v8@4.1.5)(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)) packages: @@ -61,10 +130,6 @@ packages: resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} engines: {node: '>=6.9.0'} - '@babel/helper-annotate-as-pure@7.27.3': - resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} - engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.28.6': resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} engines: {node: '>=6.9.0'} @@ -114,14 +179,20 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-development@7.27.1': - resolution: {integrity: sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==} + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx@7.28.6': - resolution: {integrity: sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==} + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -138,6 +209,10 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + '@biomejs/biome@2.4.13': resolution: {integrity: sha512-gLXOwkOBBg0tr7bDsqlkIh4uFeKuMjxvqsrb1Tukww1iDmHcfr4Uu8MoQxp0Rcte+69+osRNWXwHsu/zxT6XqA==} engines: {node: '>=14.21.3'} @@ -201,156 +276,327 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.25.12': resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.25.12': resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.25.12': resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.25.12': resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.25.12': resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.25.12': resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.12': resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.25.12': resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.25.12': resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.25.12': resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.25.12': resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.25.12': resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.25.12': resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.25.12': resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.25.12': resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.25.12': resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.25.12': resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.12': resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.25.12': resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.12': resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openharmony-arm64@0.25.12': resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.25.12': resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.25.12': resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.25.12': resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.25.12': resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} engines: {node: '>=18'} cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + + '@floating-ui/react-dom@2.1.8': + resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -367,127 +613,475 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@preact/preset-vite@2.10.5': - resolution: {integrity: sha512-p0vJpxiVO7KWWazWny3LUZ+saXyZKWv6Ju0bYMWNJRp2YveufRPgSUB1C4MTqGJfz07EehMgfN+AJNwQy+w6Iw==} - peerDependencies: - '@babel/core': 7.x - vite: 2.x || 3.x || 4.x || 5.x || 6.x || 7.x || 8.x + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} - '@preact/signals-core@1.14.1': - resolution: {integrity: sha512-vxPpfXqrwUe9lpjqfYNjAF/0RF/eFGeLgdJzdmIIZjpOnTmGmAB4BjWone562mJGMRP4frU6iZ6ei3PDsu52Ng==} + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} - '@preact/signals@1.3.4': - resolution: {integrity: sha512-TPMkStdT0QpSc8FpB63aOwXoSiZyIrPsP9Uj347KopdS6olZdAYeeird/5FZv/M1Yc1ge5qstub2o8VDbvkT4g==} + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} peerDependencies: - preact: 10.x + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@prefresh/babel-plugin@0.5.3': - resolution: {integrity: sha512-57LX2SHs4BX2s1IwCjNzTE2OJeEepRCNf1VTEpbNcUyHfMO68eeOWGDIt4ob9aYlW6PEWZ1SuwNikuoIXANDtQ==} + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@prefresh/core@1.5.9': - resolution: {integrity: sha512-IKBKCPaz34OFVC+adiQ2qaTF5qdztO2/4ZPf4KsRTgjKosWqxVXmEbxCiUydYZRY8GVie+DQlKzQr9gt6HQ+EQ==} + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} peerDependencies: - preact: ^10.0.0 || ^11.0.0-0 + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@prefresh/utils@1.2.1': - resolution: {integrity: sha512-vq/sIuN5nYfYzvyayXI4C2QkprfNaHUQ9ZX+3xLD8nL3rWyzpxOm1+K7RtMbhd+66QcaISViK7amjnheQ/4WZw==} + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@prefresh/vite@2.4.12': - resolution: {integrity: sha512-FY1fzXpUjiuosznMV0YM7XAOPZjB5FIdWS0W24+XnlxYkt9hNAwwsiKYn+cuTEoMtD/ZVazS5QVssBr9YhpCQA==} + '@radix-ui/react-dialog@1.1.15': + resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} peerDependencies: - preact: ^10.4.0 || ^11.0.0-0 - vite: '>=2.0.0' + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@rollup/pluginutils@4.2.1': - resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} - engines: {node: '>= 8.0.0'} + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@rollup/pluginutils@5.3.0': - resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} - engines: {node: '>=14.0.0'} + '@radix-ui/react-dismissable-layer@1.1.11': + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - rollup: + '@types/react': + optional: true + '@types/react-dom': optional: true - '@rollup/rollup-android-arm-eabi@4.60.2': - resolution: {integrity: sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==} - cpu: [arm] - os: [android] + '@radix-ui/react-dropdown-menu@2.1.16': + resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@rollup/rollup-android-arm64@4.60.2': - resolution: {integrity: sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==} - cpu: [arm64] - os: [android] + '@radix-ui/react-focus-guards@1.1.3': + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@rollup/rollup-darwin-arm64@4.60.2': - resolution: {integrity: sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==} - cpu: [arm64] - os: [darwin] + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@rollup/rollup-darwin-x64@4.60.2': - resolution: {integrity: sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==} - cpu: [x64] - os: [darwin] + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@rollup/rollup-freebsd-arm64@4.60.2': - resolution: {integrity: sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==} - cpu: [arm64] - os: [freebsd] + '@radix-ui/react-menu@2.1.16': + resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@rollup/rollup-freebsd-x64@4.60.2': - resolution: {integrity: sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==} - cpu: [x64] - os: [freebsd] + '@radix-ui/react-popper@1.2.8': + resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.60.2': - resolution: {integrity: sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==} - cpu: [arm] - os: [linux] - libc: [glibc] + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@rollup/rollup-linux-arm-musleabihf@4.60.2': - resolution: {integrity: sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==} - cpu: [arm] - os: [linux] - libc: [musl] + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@rollup/rollup-linux-arm64-gnu@4.60.2': - resolution: {integrity: sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==} - cpu: [arm64] - os: [linux] - libc: [glibc] + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@rollup/rollup-linux-arm64-musl@4.60.2': - resolution: {integrity: sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==} - cpu: [arm64] - os: [linux] - libc: [musl] + '@radix-ui/react-primitive@2.1.4': + resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@rollup/rollup-linux-loong64-gnu@4.60.2': - resolution: {integrity: sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==} - cpu: [loong64] - os: [linux] - libc: [glibc] + '@radix-ui/react-roving-focus@1.1.11': + resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@rollup/rollup-linux-loong64-musl@4.60.2': - resolution: {integrity: sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==} - cpu: [loong64] - os: [linux] - libc: [musl] + '@radix-ui/react-scroll-area@1.2.10': + resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@rollup/rollup-linux-ppc64-gnu@4.60.2': - resolution: {integrity: sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==} - cpu: [ppc64] - os: [linux] - libc: [glibc] + '@radix-ui/react-select@2.2.6': + resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@rollup/rollup-linux-ppc64-musl@4.60.2': - resolution: {integrity: sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==} - cpu: [ppc64] - os: [linux] - libc: [musl] + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.2.4': + resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-tabs@1.1.13': + resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tooltip@1.2.8': + resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.3': + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + + '@reduxjs/toolkit@2.11.2': + resolution: {integrity: sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==} + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/rollup-android-arm-eabi@4.60.2': + resolution: {integrity: sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.60.2': + resolution: {integrity: sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.60.2': + resolution: {integrity: sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.60.2': + resolution: {integrity: sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.60.2': + resolution: {integrity: sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.60.2': + resolution: {integrity: sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.2': + resolution: {integrity: sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.60.2': + resolution: {integrity: sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.60.2': + resolution: {integrity: sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.60.2': + resolution: {integrity: sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.60.2': + resolution: {integrity: sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.60.2': + resolution: {integrity: sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.60.2': + resolution: {integrity: sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.60.2': + resolution: {integrity: sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==} + cpu: [ppc64] + os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.60.2': resolution: {integrity: sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==} @@ -549,6 +1143,12 @@ packages: cpu: [x64] os: [win32] + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + '@tailwindcss/node@4.2.4': resolution: {integrity: sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==} @@ -643,21 +1243,247 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 || ^7 || ^8 + '@tanstack/history@1.161.6': + resolution: {integrity: sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg==} + engines: {node: '>=20.19'} + + '@tanstack/query-core@5.100.1': + resolution: {integrity: sha512-awvQhOO/2TrSCHE5LKKsXcvvj6WSBncwEcMFCB/ez0Qs0b17iyyivoGArNV3HFfXryZwCpnb/olsaBBKrIbtSw==} + + '@tanstack/query-devtools@5.100.1': + resolution: {integrity: sha512-jZLV2l7XjYxXCrXHj9pj15gZuY8Te+idoSPS2hIh3+SxOd20Gn0rfUoqEw9vc+us/b16hi0/DWqpzx9O1ZsyIQ==} + + '@tanstack/react-query-devtools@5.100.1': + resolution: {integrity: sha512-JuLinBUl/BlZhm0WVX83fJgE2a3YSbuEdxf3fgP+THg92hX7YfwuH5DzT35a6sL/rifZsPr0yJ9itB6jDOcdRg==} + peerDependencies: + '@tanstack/react-query': ^5.100.1 + react: ^18 || ^19 + + '@tanstack/react-query@5.100.1': + resolution: {integrity: sha512-UgWRLhQKprC37SsO6y1zRabOqDmM2gsdTNPbqTT35yl7kOOhwXU4nyfOiGHXPwoEFJV1IpSk85hjIFjNFWVpzw==} + peerDependencies: + react: ^18 || ^19 + + '@tanstack/react-router@1.168.23': + resolution: {integrity: sha512-+GblieDnutG6oipJJPNtRJjrWF8QTZEG/l0532+BngFkVK48oHNOcvIkSoAFYftK1egAwM7KBxXsb0Ou+X6/MQ==} + engines: {node: '>=20.19'} + peerDependencies: + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + + '@tanstack/react-store@0.9.3': + resolution: {integrity: sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/react-table@8.21.3': + resolution: {integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==} + engines: {node: '>=12'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + + '@tanstack/react-virtual@3.13.24': + resolution: {integrity: sha512-aIJvz5OSkhNIhZIpYivrxrPTKYsjW9Uzy+sP/mx0S3sev2HyvPb7xmjbYvokzEpfgYHy/HjzJ2zFAETuUfgCpg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/router-core@1.168.15': + resolution: {integrity: sha512-Wr0424NDtD8fT/uALobMZ9DdcfsTyXtW5IPR++7zvW8/7RaIOeaqXpVDId8ywaGtqPWLWOfaUg2zUtYtukoXYA==} + engines: {node: '>=20.19'} + hasBin: true + + '@tanstack/router-generator@1.166.33': + resolution: {integrity: sha512-MXP1WrEaZ13tlO5iJoXC+ZIFHNj5CtcvWuqlAZ3zXY70Musuq+mfUcKWMVdcIstnNqrZl5M2hfqLh5Zf5t4NVw==} + engines: {node: '>=20.19'} + + '@tanstack/router-plugin@1.167.23': + resolution: {integrity: sha512-dqfCd8gsZThbVQ8bcYMO62/hW5GCkUoPLnnjOd3fCWoEi+Ei5oWa/GnlgHCpG7bdeGr/K8isnYUmI9Ysq5vLrg==} + engines: {node: '>=20.19'} + hasBin: true + peerDependencies: + '@rsbuild/core': '>=1.0.2 || ^2.0.0' + '@tanstack/react-router': ^1.168.23 + vite: '>=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0' + vite-plugin-solid: ^2.11.10 || ^3.0.0-0 + webpack: '>=5.92.0' + peerDependenciesMeta: + '@rsbuild/core': + optional: true + '@tanstack/react-router': + optional: true + vite: + optional: true + vite-plugin-solid: + optional: true + webpack: + optional: true + + '@tanstack/router-utils@1.161.7': + resolution: {integrity: sha512-VkY0u7ax/GD0qU6ZLLnfPC+UMxVzxRbvZp4yV4iUSXjgJZ/siAT5/QlLm9FEDJ9QDoC0VD9W7f00tKKreUI7Ng==} + engines: {node: '>=20.19'} + + '@tanstack/store@0.9.3': + resolution: {integrity: sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==} + + '@tanstack/table-core@8.21.3': + resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} + engines: {node: '>=12'} + + '@tanstack/virtual-core@3.14.0': + resolution: {integrity: sha512-JLANqGy/D6k4Ujmh8Tr25lGimuOXNiaVyXaCAZS0W+1390sADdGnyUdSWNIfd49gebtIxGMij4IktRVzrdr12Q==} + + '@tanstack/virtual-file-routes@1.161.7': + resolution: {integrity: sha512-olW33+Cn+bsCsZKPwEGhlkqS6w3M2slFv11JIobdnCFKMLG97oAI2kWKdx5/zsywTL8flpnoIgaZZPlQTFYhdQ==} + engines: {node: '>=20.19'} + hasBin: true + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - babel-plugin-transform-hook-names@1.0.2: - resolution: {integrity: sha512-5gafyjyyBTTdX/tQQ0hRgu4AhNHG/hqWi0ZZmg2xvs2FgRkJXzDNKBZCyoYqgFkovfDrgM8OoKg8karoUvWeCw==} + '@types/node@22.19.17': + resolution: {integrity: sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react@18.3.28': + resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==} + + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@vitest/coverage-v8@4.1.5': + resolution: {integrity: sha512-38C0/Ddb7HcRG0Z4/DUem8x57d2p9jYgp18mkaYswEOQBGsI1CG4f/hjm0ZCeaJfWhSZ4k7jgs29V1Zom7Ki9A==} + peerDependencies: + '@vitest/browser': 4.1.5 + vitest: 4.1.5 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@4.1.5': + resolution: {integrity: sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==} + + '@vitest/mocker@4.1.5': + resolution: {integrity: sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==} peerDependencies: - '@babel/core': ^7.12.10 + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.5': + resolution: {integrity: sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==} + + '@vitest/runner@4.1.5': + resolution: {integrity: sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==} + + '@vitest/snapshot@4.1.5': + resolution: {integrity: sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==} + + '@vitest/spy@4.1.5': + resolution: {integrity: sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==} + + '@vitest/utils@4.1.5': + resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==} + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ansis@4.2.0: + resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} + engines: {node: '>=14'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-v8-to-istanbul@1.0.0: + resolution: {integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==} + + babel-dead-code-elimination@1.0.12: + resolution: {integrity: sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==} baseline-browser-mapping@2.10.21: resolution: {integrity: sha512-Q+rUQ7Uz8AHM7DEaNdwvfFCTq7a43lNTzuS94eiWqwyxfV/wJv+oUivef51T91mmRY4d4A1u9rcSvkeufCVXlA==} engines: {node: '>=6.0.0'} hasBin: true - boolbase@1.0.0: - resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -671,15 +1497,79 @@ packages: caniuse-lite@1.0.30001790: resolution: {integrity: sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + cmdk@1.1.1: + resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + react-dom: ^18 || ^19 || ^19.0.0-rc + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - css-select@5.2.2: - resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + cookie-es@3.1.1: + resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} - css-what@6.2.2: - resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} - engines: {node: '>= 6'} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} @@ -690,22 +1580,19 @@ packages: supports-color: optional: true + decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} - dom-serializer@2.0.0: - resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} - - domelementtype@2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - domhandler@5.0.3: - resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} - engines: {node: '>= 4'} - - domutils@3.2.2: - resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + diff@8.0.4: + resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} + engines: {node: '>=0.3.1'} electron-to-chromium@1.5.344: resolution: {integrity: sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==} @@ -714,21 +1601,35 @@ packages: resolution: {integrity: sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==} engines: {node: '>=10.13.0'} - entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} + es-module-lexer@2.0.0: + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + + es-toolkit@1.46.0: + resolution: {integrity: sha512-IToJ6ct9OLl5zz6WsC/1vZEwfSZ7Myil+ygl5Tf30Xjn9AEkzNB4kqp2G7VUJKF1DtTx/ra5M5KLlXvzOg51BA==} esbuild@0.25.12: resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} hasBin: true + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - estree-walker@2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} @@ -752,21 +1653,76 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - he@1.2.0: - resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} - hasBin: true + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + immer@10.2.0: + resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==} + + immer@11.1.4: + resolution: {integrity: sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + isbot@5.1.39: + resolution: {integrity: sha512-obH0yYahGXdzNxo+djmHhBYThUKDkz565cxkIlt2L9hXfv1NlaLKoDBHo6KxXsYrIXx2RK3x5vY36CfZcobxEw==} + engines: {node: '>=18'} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + jiti@2.6.1: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -780,9 +1736,6 @@ packages: engines: {node: '>=6'} hasBin: true - kolorist@1.8.0: - resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} - lightningcss-android-arm64@1.32.0: resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} @@ -857,20 +1810,27 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lucide-preact@0.577.0: - resolution: {integrity: sha512-fCY59YQ2OMYWqE1V7k8HwfXyiBMHAfTI1roCOasdc+Cekya7BIObSJ/cil+tVMSbU6siv4uZlaz5twAGmkYqIQ==} + lucide-react@0.577.0: + resolution: {integrity: sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A==} peerDependencies: - preact: ^10.27.2 + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} + magicast@0.5.2: + resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -880,14 +1840,18 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - node-html-parser@6.1.13: - resolution: {integrity: sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==} - node-releases@2.0.38: resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} - nth-check@2.1.1: - resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -904,37 +1868,152 @@ packages: resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==} engines: {node: ^10 || ^12 || >=14} - preact-router@4.1.2: - resolution: {integrity: sha512-uICUaUFYh+XQ+6vZtQn1q+X6rSqwq+zorWOCLWPF5FAsQh3EJ+RsDQ9Ee+fjk545YWQHfUxhrBAaemfxEnMOUg==} + prettier@3.8.3: + resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} + engines: {node: '>=14'} + hasBin: true + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-error-boundary@6.1.1: + resolution: {integrity: sha512-BrYwPOdXi5mqkk5lw+Uvt0ThHx32rCt3BkukS4X23A2AIWDPSGX6iaWTc0y9TU/mHDA/6qOSGel+B2ERkOvD1w==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + + react-is@19.2.5: + resolution: {integrity: sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ==} + + react-redux@9.2.0: + resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==} + peerDependencies: + '@types/react': ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + redux: + optional: true + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + engines: {node: '>=10'} peerDependencies: - preact: '>=10' + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + recharts@3.8.1: + resolution: {integrity: sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==} + engines: {node: '>=18'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + redux-thunk@3.1.0: + resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} + peerDependencies: + redux: ^5.0.0 + + redux@5.0.1: + resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} - preact@10.29.1: - resolution: {integrity: sha512-gQCLc/vWroE8lIpleXtdJhTFDogTdZG9AjMUpVkDf2iTCNwYNWA+u16dL41TqUDJO4gm2IgrcMv3uTpjd4Pwmg==} + reselect@5.1.1: + resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} rollup@4.60.2: resolution: {integrity: sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - simple-code-frame@1.3.0: - resolution: {integrity: sha512-MB4pQmETUBlNs62BBeRjIFGeuy/x6gGKh7+eRUemn1rCFhqo7K+4slPqsyizCbcbYLnaYqaoZ2FWsZ/jN06D8w==} + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + seroval-plugins@1.5.2: + resolution: {integrity: sha512-qpY0Cl+fKYFn4GOf3cMiq6l72CpuVaawb6ILjubOQ+diJ54LfOWaSSPsaswN8DRPIPW4Yq+tE1k5aKd7ILyaFg==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + + seroval@1.5.2: + resolution: {integrity: sha512-xcRN39BdsnO9Tf+VzsE7b3JyTJASItIV1FVFewJKCFcW4s4haIKS3e6vj8PGB9qBwC7tnuOywQMdv5N4qkzi7Q==} + engines: {node: '>=10'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + sonner@1.7.4: + resolution: {integrity: sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} - source-map@0.7.6: - resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} - engines: {node: '>= 12'} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} - stack-trace@1.0.0-pre2: - resolution: {integrity: sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A==} - engines: {node: '>=16'} + tailwind-merge@2.6.1: + resolution: {integrity: sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==} tailwindcss@4.2.4: resolution: {integrity: sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==} @@ -943,39 +2022,81 @@ packages: resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.1.1: + resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} + engines: {node: '>=18'} + tinyglobby@0.2.16: resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} hasBin: true + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + unplugin@2.3.11: + resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} + engines: {node: '>=18.12.0'} + update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' - vite-plugin-singlefile@2.3.3: - resolution: {integrity: sha512-XVnGH0QzbOa8fxRSsHdCarVN1BSBXNi7uLMQYlrGRN5apdHkk62XQWRJhVever0lnfuyBkwn+kvVChdm/OoOUg==} - engines: {node: '>18.0.0'} + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} peerDependencies: - rollup: ^4.59.0 - vite: ^5.4.21 || ^6.0.0 || ^7.0.0 || ^8.0.0 + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: - rollup: + '@types/react': optional: true - vite-prerender-plugin@0.5.13: - resolution: {integrity: sha512-IKSpYkzDBsKAxa05naRbj7GvNVMSdww/Z/E89oO3xndz+gWnOBOKOAbEXv7qDhktY/j3vHgJmoV1pPzqU2tx9g==} + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} peerDependencies: - vite: 5.x || 6.x || 7.x || 8.x + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + victory-vendor@37.3.6: + resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} vite@6.4.2: resolution: {integrity: sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==} @@ -1017,11 +2138,60 @@ packages: yaml: optional: true + vitest@4.1.5: + resolution: {integrity: sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.5 + '@vitest/browser-preview': 4.1.5 + '@vitest/browser-webdriverio': 4.1.5 + '@vitest/coverage-istanbul': 4.1.5 + '@vitest/coverage-v8': 4.1.5 + '@vitest/ui': 4.1.5 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - zimmerframe@1.1.4: - resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} snapshots: @@ -1061,10 +2231,6 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 - '@babel/helper-annotate-as-pure@7.27.3': - dependencies: - '@babel/types': 7.29.0 - '@babel/helper-compilation-targets@7.28.6': dependencies: '@babel/compat-data': 7.29.0 @@ -1113,23 +2279,20 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0) - transitivePeerDependencies: - - supports-color + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-jsx@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-module-imports': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) - '@babel/types': 7.29.0 - transitivePeerDependencies: - - supports-color '@babel/template@7.28.6': dependencies: @@ -1154,6 +2317,8 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@bcoe/v8-coverage@1.0.2': {} + '@biomejs/biome@2.4.13': optionalDependencies: '@biomejs/cli-darwin-arm64': 2.4.13 @@ -1192,81 +2357,176 @@ snapshots: '@esbuild/aix-ppc64@0.25.12': optional: true + '@esbuild/aix-ppc64@0.27.7': + optional: true + '@esbuild/android-arm64@0.25.12': optional: true + '@esbuild/android-arm64@0.27.7': + optional: true + '@esbuild/android-arm@0.25.12': optional: true + '@esbuild/android-arm@0.27.7': + optional: true + '@esbuild/android-x64@0.25.12': optional: true + '@esbuild/android-x64@0.27.7': + optional: true + '@esbuild/darwin-arm64@0.25.12': optional: true + '@esbuild/darwin-arm64@0.27.7': + optional: true + '@esbuild/darwin-x64@0.25.12': optional: true + '@esbuild/darwin-x64@0.27.7': + optional: true + '@esbuild/freebsd-arm64@0.25.12': optional: true + '@esbuild/freebsd-arm64@0.27.7': + optional: true + '@esbuild/freebsd-x64@0.25.12': optional: true + '@esbuild/freebsd-x64@0.27.7': + optional: true + '@esbuild/linux-arm64@0.25.12': optional: true + '@esbuild/linux-arm64@0.27.7': + optional: true + '@esbuild/linux-arm@0.25.12': optional: true + '@esbuild/linux-arm@0.27.7': + optional: true + '@esbuild/linux-ia32@0.25.12': optional: true + '@esbuild/linux-ia32@0.27.7': + optional: true + '@esbuild/linux-loong64@0.25.12': optional: true + '@esbuild/linux-loong64@0.27.7': + optional: true + '@esbuild/linux-mips64el@0.25.12': optional: true + '@esbuild/linux-mips64el@0.27.7': + optional: true + '@esbuild/linux-ppc64@0.25.12': optional: true + '@esbuild/linux-ppc64@0.27.7': + optional: true + '@esbuild/linux-riscv64@0.25.12': optional: true + '@esbuild/linux-riscv64@0.27.7': + optional: true + '@esbuild/linux-s390x@0.25.12': optional: true + '@esbuild/linux-s390x@0.27.7': + optional: true + '@esbuild/linux-x64@0.25.12': optional: true + '@esbuild/linux-x64@0.27.7': + optional: true + '@esbuild/netbsd-arm64@0.25.12': optional: true + '@esbuild/netbsd-arm64@0.27.7': + optional: true + '@esbuild/netbsd-x64@0.25.12': optional: true + '@esbuild/netbsd-x64@0.27.7': + optional: true + '@esbuild/openbsd-arm64@0.25.12': optional: true + '@esbuild/openbsd-arm64@0.27.7': + optional: true + '@esbuild/openbsd-x64@0.25.12': optional: true + '@esbuild/openbsd-x64@0.27.7': + optional: true + '@esbuild/openharmony-arm64@0.25.12': optional: true + '@esbuild/openharmony-arm64@0.27.7': + optional: true + '@esbuild/sunos-x64@0.25.12': optional: true + '@esbuild/sunos-x64@0.27.7': + optional: true + '@esbuild/win32-arm64@0.25.12': optional: true + '@esbuild/win32-arm64@0.27.7': + optional: true + '@esbuild/win32-ia32@0.25.12': optional: true + '@esbuild/win32-ia32@0.27.7': + optional: true + '@esbuild/win32-x64@0.25.12': optional: true + '@esbuild/win32-x64@0.27.7': + optional: true + + '@floating-ui/core@1.7.5': + dependencies: + '@floating-ui/utils': 0.2.11 + + '@floating-ui/dom@1.7.6': + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 + + '@floating-ui/react-dom@2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/dom': 1.7.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@floating-ui/utils@0.2.11': {} + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -1286,64 +2546,396 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@preact/preset-vite@2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.60.2)(vite@6.4.2(jiti@2.6.1)(lightningcss@1.32.0))': + '@radix-ui/number@1.1.1': {} + + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.29.0) - '@prefresh/vite': 2.4.12(preact@10.29.1)(vite@6.4.2(jiti@2.6.1)(lightningcss@1.32.0)) - '@rollup/pluginutils': 5.3.0(rollup@4.60.2) - babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.29.0) - debug: 4.4.3 - magic-string: 0.30.21 - picocolors: 1.1.1 - vite: 6.4.2(jiti@2.6.1)(lightningcss@1.32.0) - vite-prerender-plugin: 0.5.13(vite@6.4.2(jiti@2.6.1)(lightningcss@1.32.0)) - zimmerframe: 1.1.4 - transitivePeerDependencies: - - preact - - rollup - - supports-color + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) - '@preact/signals-core@1.14.1': {} + '@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) - '@preact/signals@1.3.4(preact@10.29.1)': + '@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.28)(react@18.3.1)': dependencies: - '@preact/signals-core': 1.14.1 - preact: 10.29.1 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 - '@prefresh/babel-plugin@0.5.3': {} + '@radix-ui/react-context@1.1.2(@types/react@18.3.28)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 - '@prefresh/core@1.5.9(preact@10.29.1)': + '@radix-ui/react-dialog@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - preact: 10.29.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) - '@prefresh/utils@1.2.1': {} + '@radix-ui/react-direction@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 - '@prefresh/vite@2.4.12(preact@10.29.1)(vite@6.4.2(jiti@2.6.1)(lightningcss@1.32.0))': + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/core': 7.29.0 - '@prefresh/babel-plugin': 0.5.3 - '@prefresh/core': 1.5.9(preact@10.29.1) - '@prefresh/utils': 1.2.1 - '@rollup/pluginutils': 4.2.1 - preact: 10.29.1 - vite: 6.4.2(jiti@2.6.1)(lightningcss@1.32.0) - transitivePeerDependencies: - - supports-color + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) - '@rollup/pluginutils@4.2.1': + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - estree-walker: 2.0.2 - picomatch: 2.3.2 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) - '@rollup/pluginutils@5.3.0(rollup@4.60.2)': + '@radix-ui/react-focus-guards@1.1.3(@types/react@18.3.28)(react@18.3.1)': dependencies: - '@types/estree': 1.0.8 - estree-walker: 2.0.2 - picomatch: 4.0.4 + react: 18.3.1 optionalDependencies: - rollup: 4.60.2 + '@types/react': 18.3.28 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-id@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-menu@2.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-popper@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/react-dom': 2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-rect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/rect': 1.1.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-presence@1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-primitive@2.1.4(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-slot': 1.2.4(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-select@2.2.6(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-slot@1.2.3(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-slot@1.2.4(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-tabs@1.1.13(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-previous@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-rect@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-size@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/rect@1.1.1': {} + + '@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1))(react@18.3.1)': + dependencies: + '@standard-schema/spec': 1.1.0 + '@standard-schema/utils': 0.3.0 + immer: 11.1.4 + redux: 5.0.1 + redux-thunk: 3.1.0(redux@5.0.1) + reselect: 5.1.1 + optionalDependencies: + react: 18.3.1 + react-redux: 9.2.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1) + + '@rolldown/pluginutils@1.0.0-beta.27': {} '@rollup/rollup-android-arm-eabi@4.60.2': optional: true @@ -1375,128 +2967,402 @@ snapshots: '@rollup/rollup-linux-arm64-musl@4.60.2': optional: true - '@rollup/rollup-linux-loong64-gnu@4.60.2': - optional: true + '@rollup/rollup-linux-loong64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.60.2': + optional: true + + '@rollup/rollup-openbsd-x64@4.60.2': + optional: true + + '@rollup/rollup-openharmony-arm64@4.60.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.60.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.60.2': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.2': + optional: true + + '@standard-schema/spec@1.1.0': {} + + '@standard-schema/utils@0.3.0': {} + + '@tailwindcss/node@4.2.4': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.21.0 + jiti: 2.6.1 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.2.4 + + '@tailwindcss/oxide-android-arm64@4.2.4': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.2.4': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.2.4': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.2.4': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.2.4': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.2.4': + optional: true + + '@tailwindcss/oxide@4.2.4': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.2.4 + '@tailwindcss/oxide-darwin-arm64': 4.2.4 + '@tailwindcss/oxide-darwin-x64': 4.2.4 + '@tailwindcss/oxide-freebsd-x64': 4.2.4 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.4 + '@tailwindcss/oxide-linux-arm64-gnu': 4.2.4 + '@tailwindcss/oxide-linux-arm64-musl': 4.2.4 + '@tailwindcss/oxide-linux-x64-gnu': 4.2.4 + '@tailwindcss/oxide-linux-x64-musl': 4.2.4 + '@tailwindcss/oxide-wasm32-wasi': 4.2.4 + '@tailwindcss/oxide-win32-arm64-msvc': 4.2.4 + '@tailwindcss/oxide-win32-x64-msvc': 4.2.4 + + '@tailwindcss/vite@4.2.4(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))': + dependencies: + '@tailwindcss/node': 4.2.4 + '@tailwindcss/oxide': 4.2.4 + tailwindcss: 4.2.4 + vite: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0) + + '@tanstack/history@1.161.6': {} + + '@tanstack/query-core@5.100.1': {} + + '@tanstack/query-devtools@5.100.1': {} + + '@tanstack/react-query-devtools@5.100.1(@tanstack/react-query@5.100.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/query-devtools': 5.100.1 + '@tanstack/react-query': 5.100.1(react@18.3.1) + react: 18.3.1 + + '@tanstack/react-query@5.100.1(react@18.3.1)': + dependencies: + '@tanstack/query-core': 5.100.1 + react: 18.3.1 + + '@tanstack/react-router@1.168.23(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/history': 1.161.6 + '@tanstack/react-store': 0.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tanstack/router-core': 1.168.15 + isbot: 5.1.39 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@tanstack/react-store@0.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/store': 0.9.3 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + use-sync-external-store: 1.6.0(react@18.3.1) + + '@tanstack/react-table@8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/table-core': 8.21.3 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@tanstack/react-virtual@3.13.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/virtual-core': 3.14.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@tanstack/router-core@1.168.15': + dependencies: + '@tanstack/history': 1.161.6 + cookie-es: 3.1.1 + seroval: 1.5.2 + seroval-plugins: 1.5.2(seroval@1.5.2) + + '@tanstack/router-generator@1.166.33': + dependencies: + '@babel/types': 7.29.0 + '@tanstack/router-core': 1.168.15 + '@tanstack/router-utils': 1.161.7 + '@tanstack/virtual-file-routes': 1.161.7 + magic-string: 0.30.21 + prettier: 3.8.3 + tsx: 4.21.0 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + + '@tanstack/router-plugin@1.167.23(@tanstack/react-router@1.168.23(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@tanstack/router-core': 1.168.15 + '@tanstack/router-generator': 1.166.33 + '@tanstack/router-utils': 1.161.7 + '@tanstack/virtual-file-routes': 1.161.7 + chokidar: 3.6.0 + unplugin: 2.3.11 + zod: 3.25.76 + optionalDependencies: + '@tanstack/react-router': 1.168.23(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + vite: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0) + transitivePeerDependencies: + - supports-color + + '@tanstack/router-utils@1.161.7': + dependencies: + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + ansis: 4.2.0 + babel-dead-code-elimination: 1.0.12 + diff: 8.0.4 + pathe: 2.0.3 + tinyglobby: 0.2.16 + transitivePeerDependencies: + - supports-color + + '@tanstack/store@0.9.3': {} + + '@tanstack/table-core@8.21.3': {} + + '@tanstack/virtual-core@3.14.0': {} + + '@tanstack/virtual-file-routes@1.161.7': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 - '@rollup/rollup-linux-loong64-musl@4.60.2': - optional: true + '@types/d3-array@3.2.2': {} - '@rollup/rollup-linux-ppc64-gnu@4.60.2': - optional: true + '@types/d3-color@3.1.3': {} - '@rollup/rollup-linux-ppc64-musl@4.60.2': - optional: true + '@types/d3-ease@3.0.2': {} - '@rollup/rollup-linux-riscv64-gnu@4.60.2': - optional: true + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 - '@rollup/rollup-linux-riscv64-musl@4.60.2': - optional: true + '@types/d3-path@3.1.1': {} - '@rollup/rollup-linux-s390x-gnu@4.60.2': - optional: true + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 - '@rollup/rollup-linux-x64-gnu@4.60.2': - optional: true + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 - '@rollup/rollup-linux-x64-musl@4.60.2': - optional: true + '@types/d3-time@3.0.4': {} - '@rollup/rollup-openbsd-x64@4.60.2': - optional: true + '@types/d3-timer@3.0.2': {} - '@rollup/rollup-openharmony-arm64@4.60.2': - optional: true + '@types/deep-eql@4.0.2': {} - '@rollup/rollup-win32-arm64-msvc@4.60.2': - optional: true + '@types/estree@1.0.8': {} - '@rollup/rollup-win32-ia32-msvc@4.60.2': - optional: true + '@types/node@22.19.17': + dependencies: + undici-types: 6.21.0 - '@rollup/rollup-win32-x64-gnu@4.60.2': - optional: true + '@types/prop-types@15.7.15': {} - '@rollup/rollup-win32-x64-msvc@4.60.2': - optional: true + '@types/react-dom@18.3.7(@types/react@18.3.28)': + dependencies: + '@types/react': 18.3.28 - '@tailwindcss/node@4.2.4': + '@types/react@18.3.28': dependencies: - '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.21.0 - jiti: 2.6.1 - lightningcss: 1.32.0 - magic-string: 0.30.21 - source-map-js: 1.2.1 - tailwindcss: 4.2.4 + '@types/prop-types': 15.7.15 + csstype: 3.2.3 - '@tailwindcss/oxide-android-arm64@4.2.4': - optional: true + '@types/use-sync-external-store@0.0.6': {} - '@tailwindcss/oxide-darwin-arm64@4.2.4': - optional: true + '@vitejs/plugin-react@4.7.0(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0) + transitivePeerDependencies: + - supports-color - '@tailwindcss/oxide-darwin-x64@4.2.4': - optional: true + '@vitest/coverage-v8@4.1.5(vitest@4.1.5)': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.1.5 + ast-v8-to-istanbul: 1.0.0 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + magicast: 0.5.2 + obug: 2.1.1 + std-env: 4.1.0 + tinyrainbow: 3.1.0 + vitest: 4.1.5(@types/node@22.19.17)(@vitest/coverage-v8@4.1.5)(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)) + + '@vitest/expect@4.1.5': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.5 + '@vitest/utils': 4.1.5 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.5(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))': + dependencies: + '@vitest/spy': 4.1.5 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0) - '@tailwindcss/oxide-freebsd-x64@4.2.4': - optional: true + '@vitest/pretty-format@4.1.5': + dependencies: + tinyrainbow: 3.1.0 - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': - optional: true + '@vitest/runner@4.1.5': + dependencies: + '@vitest/utils': 4.1.5 + pathe: 2.0.3 - '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': - optional: true + '@vitest/snapshot@4.1.5': + dependencies: + '@vitest/pretty-format': 4.1.5 + '@vitest/utils': 4.1.5 + magic-string: 0.30.21 + pathe: 2.0.3 - '@tailwindcss/oxide-linux-arm64-musl@4.2.4': - optional: true + '@vitest/spy@4.1.5': {} - '@tailwindcss/oxide-linux-x64-gnu@4.2.4': - optional: true + '@vitest/utils@4.1.5': + dependencies: + '@vitest/pretty-format': 4.1.5 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 - '@tailwindcss/oxide-linux-x64-musl@4.2.4': - optional: true + acorn@8.16.0: {} - '@tailwindcss/oxide-wasm32-wasi@4.2.4': - optional: true + ansis@4.2.0: {} - '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': - optional: true + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.2 - '@tailwindcss/oxide-win32-x64-msvc@4.2.4': - optional: true + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 - '@tailwindcss/oxide@4.2.4': - optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.2.4 - '@tailwindcss/oxide-darwin-arm64': 4.2.4 - '@tailwindcss/oxide-darwin-x64': 4.2.4 - '@tailwindcss/oxide-freebsd-x64': 4.2.4 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.4 - '@tailwindcss/oxide-linux-arm64-gnu': 4.2.4 - '@tailwindcss/oxide-linux-arm64-musl': 4.2.4 - '@tailwindcss/oxide-linux-x64-gnu': 4.2.4 - '@tailwindcss/oxide-linux-x64-musl': 4.2.4 - '@tailwindcss/oxide-wasm32-wasi': 4.2.4 - '@tailwindcss/oxide-win32-arm64-msvc': 4.2.4 - '@tailwindcss/oxide-win32-x64-msvc': 4.2.4 + assertion-error@2.0.1: {} - '@tailwindcss/vite@4.2.4(vite@6.4.2(jiti@2.6.1)(lightningcss@1.32.0))': + ast-v8-to-istanbul@1.0.0: dependencies: - '@tailwindcss/node': 4.2.4 - '@tailwindcss/oxide': 4.2.4 - tailwindcss: 4.2.4 - vite: 6.4.2(jiti@2.6.1)(lightningcss@1.32.0) - - '@types/estree@1.0.8': {} + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 - babel-plugin-transform-hook-names@1.0.2(@babel/core@7.29.0): + babel-dead-code-elimination@1.0.12: dependencies: '@babel/core': 7.29.0 + '@babel/parser': 7.29.2 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color baseline-browser-mapping@2.10.21: {} - boolbase@1.0.0: {} + binary-extensions@2.3.0: {} braces@3.0.3: dependencies: @@ -1512,41 +3378,93 @@ snapshots: caniuse-lite@1.0.30001790: {} + chai@6.2.2: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + clsx@2.1.1: {} + + cmdk@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + convert-source-map@2.0.0: {} - css-select@5.2.2: + cookie-es@3.1.1: {} + + csstype@3.2.3: {} + + d3-array@3.2.4: dependencies: - boolbase: 1.0.0 - css-what: 6.2.2 - domhandler: 5.0.3 - domutils: 3.2.2 - nth-check: 2.1.1 + internmap: 2.0.3 - css-what@6.2.2: {} + d3-color@3.1.0: {} - debug@4.4.3: + d3-ease@3.0.1: {} + + d3-format@3.1.2: {} + + d3-interpolate@3.0.1: dependencies: - ms: 2.1.3 + d3-color: 3.1.0 - detect-libc@2.1.2: {} + d3-path@3.1.0: {} - dom-serializer@2.0.0: + d3-scale@4.0.2: dependencies: - domelementtype: 2.3.0 - domhandler: 5.0.3 - entities: 4.5.0 + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 - domelementtype@2.3.0: {} + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 - domhandler@5.0.3: + d3-time@3.1.0: dependencies: - domelementtype: 2.3.0 + d3-array: 3.2.4 + + d3-timer@3.0.1: {} - domutils@3.2.2: + debug@4.4.3: dependencies: - dom-serializer: 2.0.0 - domelementtype: 2.3.0 - domhandler: 5.0.3 + ms: 2.1.3 + + decimal.js-light@2.5.1: {} + + detect-libc@2.1.2: {} + + detect-node-es@1.1.0: {} + + diff@8.0.4: {} electron-to-chromium@1.5.344: {} @@ -1555,7 +3473,9 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.3 - entities@4.5.0: {} + es-module-lexer@2.0.0: {} + + es-toolkit@1.46.0: {} esbuild@0.25.12: optionalDependencies: @@ -1586,9 +3506,44 @@ snapshots: '@esbuild/win32-ia32': 0.25.12 '@esbuild/win32-x64': 0.25.12 + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + escalade@3.2.0: {} - estree-walker@2.0.2: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + eventemitter3@5.0.4: {} + + expect-type@1.3.0: {} fdir@6.5.0(picomatch@4.0.4): optionalDependencies: @@ -1603,22 +3558,65 @@ snapshots: gensync@1.0.0-beta.2: {} + get-nonce@1.0.1: {} + + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + graceful-fs@4.2.11: {} - he@1.2.0: {} + has-flag@4.0.0: {} + + html-escaper@2.0.2: {} + + immer@10.2.0: {} + + immer@11.1.4: {} + + internmap@2.0.3: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 is-number@7.0.0: {} + isbot@5.1.39: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + jiti@2.6.1: {} + js-tokens@10.0.0: {} + js-tokens@4.0.0: {} jsesc@3.1.0: {} json5@2.2.3: {} - kolorist@1.8.0: {} - lightningcss-android-arm64@1.32.0: optional: true @@ -1668,37 +3666,43 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + lru-cache@5.1.1: dependencies: yallist: 3.1.1 - lucide-preact@0.577.0(preact@10.29.1): + lucide-react@0.577.0(react@18.3.1): dependencies: - preact: 10.29.1 + react: 18.3.1 magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - micromatch@4.0.8: + magicast@0.5.2: dependencies: - braces: 3.0.3 - picomatch: 2.3.2 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.7.4 ms@2.1.3: {} nanoid@3.3.11: {} - node-html-parser@6.1.13: - dependencies: - css-select: 5.2.2 - he: 1.2.0 - node-releases@2.0.38: {} - nth-check@2.1.1: - dependencies: - boolbase: 1.0.0 + normalize-path@3.0.0: {} + + obug@2.1.1: {} + + pathe@2.0.3: {} picocolors@1.1.1: {} @@ -1712,11 +3716,95 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - preact-router@4.1.2(preact@10.29.1): + prettier@3.8.3: {} + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-error-boundary@6.1.1(react@18.3.1): + dependencies: + react: 18.3.1 + + react-is@19.2.5: {} + + react-redux@9.2.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1): + dependencies: + '@types/use-sync-external-store': 0.0.6 + react: 18.3.1 + use-sync-external-store: 1.6.0(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + redux: 5.0.1 + + react-refresh@0.17.0: {} + + react-remove-scroll-bar@2.3.8(@types/react@18.3.28)(react@18.3.1): + dependencies: + react: 18.3.1 + react-style-singleton: 2.2.3(@types/react@18.3.28)(react@18.3.1) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.28 + + react-remove-scroll@2.7.2(@types/react@18.3.28)(react@18.3.1): + dependencies: + react: 18.3.1 + react-remove-scroll-bar: 2.3.8(@types/react@18.3.28)(react@18.3.1) + react-style-singleton: 2.2.3(@types/react@18.3.28)(react@18.3.1) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@18.3.28)(react@18.3.1) + use-sidecar: 1.1.3(@types/react@18.3.28)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + + react-style-singleton@2.2.3(@types/react@18.3.28)(react@18.3.1): + dependencies: + get-nonce: 1.0.1 + react: 18.3.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.28 + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + readdirp@3.6.0: dependencies: - preact: 10.29.1 + picomatch: 2.3.2 + + recharts@3.8.1(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react-is@19.2.5)(react@18.3.1)(redux@5.0.1): + dependencies: + '@reduxjs/toolkit': 2.11.2(react-redux@9.2.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1))(react@18.3.1) + clsx: 2.1.1 + decimal.js-light: 2.5.1 + es-toolkit: 1.46.0 + eventemitter3: 5.0.4 + immer: 10.2.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-is: 19.2.5 + react-redux: 9.2.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1) + reselect: 5.1.1 + tiny-invariant: 1.3.3 + use-sync-external-store: 1.6.0(react@18.3.1) + victory-vendor: 37.3.6 + transitivePeerDependencies: + - '@types/react' + - redux + + redux-thunk@3.1.0(redux@5.0.1): + dependencies: + redux: 5.0.1 + + redux@5.0.1: {} + + reselect@5.1.1: {} - preact@10.29.1: {} + resolve-pkg-maps@1.0.0: {} rollup@4.60.2: dependencies: @@ -1749,57 +3837,123 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.60.2 fsevents: 2.3.3 + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + semver@6.3.1: {} - simple-code-frame@1.3.0: + semver@7.7.4: {} + + seroval-plugins@1.5.2(seroval@1.5.2): + dependencies: + seroval: 1.5.2 + + seroval@1.5.2: {} + + siginfo@2.0.0: {} + + sonner@1.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - kolorist: 1.8.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) source-map-js@1.2.1: {} - source-map@0.7.6: {} + stackback@0.0.2: {} + + std-env@4.1.0: {} - stack-trace@1.0.0-pre2: {} + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + tailwind-merge@2.6.1: {} tailwindcss@4.2.4: {} tapable@2.3.3: {} + tiny-invariant@1.3.3: {} + + tinybench@2.9.0: {} + + tinyexec@1.1.1: {} + tinyglobby@0.2.16: dependencies: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinyrainbow@3.1.0: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 + tslib@2.8.1: {} + + tsx@4.21.0: + dependencies: + esbuild: 0.27.7 + get-tsconfig: 4.14.0 + optionalDependencies: + fsevents: 2.3.3 + typescript@5.9.3: {} + undici-types@6.21.0: {} + + unplugin@2.3.11: + dependencies: + '@jridgewell/remapping': 2.3.5 + acorn: 8.16.0 + picomatch: 4.0.4 + webpack-virtual-modules: 0.6.2 + update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: browserslist: 4.28.2 escalade: 3.2.0 picocolors: 1.1.1 - vite-plugin-singlefile@2.3.3(rollup@4.60.2)(vite@6.4.2(jiti@2.6.1)(lightningcss@1.32.0)): + use-callback-ref@1.3.3(@types/react@18.3.28)(react@18.3.1): dependencies: - micromatch: 4.0.8 - vite: 6.4.2(jiti@2.6.1)(lightningcss@1.32.0) + react: 18.3.1 + tslib: 2.8.1 optionalDependencies: - rollup: 4.60.2 + '@types/react': 18.3.28 - vite-prerender-plugin@0.5.13(vite@6.4.2(jiti@2.6.1)(lightningcss@1.32.0)): + use-sidecar@1.1.3(@types/react@18.3.28)(react@18.3.1): dependencies: - kolorist: 1.8.0 - magic-string: 0.30.21 - node-html-parser: 6.1.13 - simple-code-frame: 1.3.0 - source-map: 0.7.6 - stack-trace: 1.0.0-pre2 - vite: 6.4.2(jiti@2.6.1)(lightningcss@1.32.0) + detect-node-es: 1.1.0 + react: 18.3.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.28 + + use-sync-external-store@1.6.0(react@18.3.1): + dependencies: + react: 18.3.1 - vite@6.4.2(jiti@2.6.1)(lightningcss@1.32.0): + victory-vendor@37.3.6: + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-ease': 3.0.2 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + + vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.4) @@ -1808,10 +3962,47 @@ snapshots: rollup: 4.60.2 tinyglobby: 0.2.16 optionalDependencies: + '@types/node': 22.19.17 fsevents: 2.3.3 jiti: 2.6.1 lightningcss: 1.32.0 + tsx: 4.21.0 + + vitest@4.1.5(@types/node@22.19.17)(@vitest/coverage-v8@4.1.5)(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)): + dependencies: + '@vitest/expect': 4.1.5 + '@vitest/mocker': 4.1.5(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)) + '@vitest/pretty-format': 4.1.5 + '@vitest/runner': 4.1.5 + '@vitest/snapshot': 4.1.5 + '@vitest/spy': 4.1.5 + '@vitest/utils': 4.1.5 + es-module-lexer: 2.0.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.1.1 + tinyglobby: 0.2.16 + tinyrainbow: 3.1.0 + vite: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.19.17 + '@vitest/coverage-v8': 4.1.5(vitest@4.1.5) + transitivePeerDependencies: + - msw + + webpack-virtual-modules@0.6.2: {} + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 yallist@3.1.1: {} - zimmerframe@1.1.4: {} + zod@3.25.76: {} diff --git a/dashboard/src/api/client.ts b/dashboard/src/api/client.ts deleted file mode 100644 index df6d3a7..0000000 --- a/dashboard/src/api/client.ts +++ /dev/null @@ -1,24 +0,0 @@ -export class ApiError extends Error { - constructor( - public status: number, - message: string, - ) { - super(message); - } -} - -export async function api(path: string, signal?: AbortSignal): Promise { - const res = await fetch(path, { signal }); - if (!res.ok) { - throw new ApiError(res.status, `${res.status} ${res.statusText}`); - } - return res.json(); -} - -export async function apiPost(path: string, signal?: AbortSignal): Promise { - const res = await fetch(path, { method: "POST", signal }); - if (!res.ok) { - throw new ApiError(res.status, `${res.status} ${res.statusText}`); - } - return res.json(); -} diff --git a/dashboard/src/api/index.ts b/dashboard/src/api/index.ts deleted file mode 100644 index f11dda3..0000000 --- a/dashboard/src/api/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -export { ApiError, api, apiPost } from "./client"; -export type { - CircuitBreaker, - DagData, - DagEdge, - DagNode, - DeadLetter, - InterceptionStats, - Job, - JobError, - JobStatus, - MetricsResponse, - ProxyStats, - QueueStats, - QueueStatsMap, - ReplayEntry, - ResourceStatus, - TaskLog, - TaskMetrics, - TimeseriesBucket, - Worker, -} from "./types"; diff --git a/dashboard/src/api/types.ts b/dashboard/src/api/types.ts deleted file mode 100644 index e65909c..0000000 --- a/dashboard/src/api/types.ts +++ /dev/null @@ -1,161 +0,0 @@ -export type JobStatus = "pending" | "running" | "complete" | "failed" | "dead" | "cancelled"; - -export interface QueueStats { - pending: number; - running: number; - completed: number; - failed: number; - dead: number; - cancelled: number; -} - -export interface Job { - id: string; - task_name: string; - queue: string; - status: JobStatus; - priority: number; - progress: number | null; - retry_count: number; - max_retries: number; - created_at: number; - scheduled_at: number; - started_at: number | null; - completed_at: number | null; - timeout_ms: number; - error: string | null; - unique_key: string | null; - metadata: string | null; -} - -export interface JobError { - attempt: number; - error: string; - failed_at: number; -} - -export interface TaskLog { - job_id: string; - task_name: string; - level: string; - message: string; - extra: string | null; - logged_at: number; -} - -export interface ReplayEntry { - replay_job_id: string; - replayed_at: number; - original_error: string | null; - replay_error: string | null; -} - -export interface DagData { - nodes: DagNode[]; - edges: DagEdge[]; -} - -export interface DagNode { - id: string; - task_name: string; - status: JobStatus; -} - -export interface DagEdge { - from: string; - to: string; -} - -export interface DeadLetter { - id: string; - original_job_id: string; - task_name: string; - queue: string; - error: string | null; - retry_count: number; - failed_at: number; -} - -export type MetricsResponse = Record; - -export interface TaskMetrics { - count: number; - success_count: number; - failure_count: number; - avg_ms: number; - p50_ms: number; - p95_ms: number; - p99_ms: number; - min_ms: number; - max_ms: number; -} - -export interface TimeseriesBucket { - timestamp: number; - count: number; - success: number; - failure: number; - avg_ms: number; -} - -export interface Worker { - worker_id: string; - queues: string; - last_heartbeat: number; - registered_at: number; - tags: string | null; -} - -export interface CircuitBreaker { - task_name: string; - state: "closed" | "open" | "half_open"; - failure_count: number; - threshold: number; - window_ms: number; - cooldown_ms: number; - last_failure_at: number | null; -} - -export interface ResourceStatus { - name: string; - scope: string; - health: string; - init_duration_ms: number; - recreations: number; - depends_on: string[]; - pool?: { - active: number; - idle: number; - size: number; - total_timeouts: number; - }; -} - -export type ProxyStats = Record< - string, - { - reconstructions: number; - avg_ms: number; - errors: number; - } ->; - -export type InterceptionStats = Record< - string, - { - count: number; - avg_ms: number; - } ->; - -export type QueueStatsMap = Record< - string, - { - pending: number; - running: number; - completed?: number; - failed?: number; - dead?: number; - cancelled?: number; - } ->; diff --git a/dashboard/src/app.tsx b/dashboard/src/app.tsx deleted file mode 100644 index 5d65e7d..0000000 --- a/dashboard/src/app.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import Router from "preact-router"; -import { Shell } from "./components/layout"; -import { ToastContainer } from "./components/ui"; -import { CircuitBreakers } from "./pages/circuit-breakers"; -import { DeadLetters } from "./pages/dead-letters"; -import { JobDetail } from "./pages/job-detail"; -import { Jobs } from "./pages/jobs"; -import { Logs } from "./pages/logs"; -import { Metrics } from "./pages/metrics"; -import { Overview } from "./pages/overview"; -import { Queues } from "./pages/queues"; -import { Resources } from "./pages/resources"; -import { System } from "./pages/system"; -import { Workers } from "./pages/workers"; - -export function App() { - return ( - - - - - - - - - - - - - - - - - ); -} diff --git a/dashboard/src/charts/dag-viewer.tsx b/dashboard/src/charts/dag-viewer.tsx deleted file mode 100644 index b699fde..0000000 --- a/dashboard/src/charts/dag-viewer.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import { route } from "preact-router"; -import type { DagData, DagNode, JobStatus } from "../api"; - -interface DagViewerProps { - dag: DagData; -} - -const STATUS_COLORS: Record = { - pending: "#ffa726", - running: "#42a5f5", - complete: "#66bb6a", - failed: "#ef5350", - dead: "#ef5350", - cancelled: "#a0a0b0", -}; - -export function DagViewer({ dag }: DagViewerProps) { - if (!dag.nodes || dag.nodes.length <= 1) return null; - - const nodeW = 160; - const nodeH = 36; - const gapX = 40; - const gapY = 20; - - // Build adjacency and in-degree - const adj: Record = {}; - const inDeg: Record = {}; - dag.nodes.forEach((n) => { - adj[n.id] = []; - inDeg[n.id] = 0; - }); - dag.edges.forEach((e) => { - if (!adj[e.from]) adj[e.from] = []; - adj[e.from].push(e.to); - inDeg[e.to] = (inDeg[e.to] || 0) + 1; - }); - - // BFS layer assignment - const layers: string[][] = []; - const placed = new Set(); - let queue = dag.nodes.filter((n) => (inDeg[n.id] || 0) === 0).map((n) => n.id); - while (queue.length) { - layers.push([...queue]); - for (const id of queue) placed.add(id); - const next: string[] = []; - queue.forEach((id) => { - (adj[id] || []).forEach((to) => { - if (!placed.has(to) && !next.includes(to)) next.push(to); - }); - }); - queue = next; - } - dag.nodes.forEach((n) => { - if (!placed.has(n.id)) { - layers.push([n.id]); - placed.add(n.id); - } - }); - - const nodeMap: Record = {}; - for (const n of dag.nodes) nodeMap[n.id] = n; - - const positions: Record = {}; - let svgW = 0; - let svgH = 0; - layers.forEach((layer, li) => { - layer.forEach((id, ni) => { - const x = 20 + li * (nodeW + gapX); - const y = 20 + ni * (nodeH + gapY); - positions[id] = { x, y }; - svgW = Math.max(svgW, x + nodeW + 20); - svgH = Math.max(svgH, y + nodeH + 20); - }); - }); - - return ( -
    -

    Dependency Graph

    -
    - - Job dependency graph - - - - - - {dag.edges.map((e, i) => { - const from = positions[e.from]; - const to = positions[e.to]; - if (!from || !to) return null; - return ( - - ); - })} - {dag.nodes.map((n) => { - const p = positions[n.id]; - if (!p) return null; - const color = STATUS_COLORS[n.status] || "#a0a0b0"; - return ( - route(`/jobs/${n.id}`)}> - - - {n.status.toUpperCase()} - - - {n.task_name.length > 18 ? n.task_name.slice(-18) : n.task_name} - - - ); - })} - -
    -
    - ); -} diff --git a/dashboard/src/charts/index.ts b/dashboard/src/charts/index.ts deleted file mode 100644 index bd85f55..0000000 --- a/dashboard/src/charts/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { DagViewer } from "./dag-viewer"; -export { ThroughputChart } from "./throughput-chart"; -export { TimeseriesChart } from "./timeseries-chart"; diff --git a/dashboard/src/charts/throughput-chart.tsx b/dashboard/src/charts/throughput-chart.tsx deleted file mode 100644 index afc42da..0000000 --- a/dashboard/src/charts/throughput-chart.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import { TrendingUp } from "lucide-preact"; -import { useEffect, useRef } from "preact/hooks"; -import { theme } from "../hooks"; - -interface ThroughputChartProps { - data: number[]; -} - -export function ThroughputChart({ data }: ThroughputChartProps) { - const canvasRef = useRef(null); - - useEffect(() => { - const canvas = canvasRef.current; - if (!canvas) return; - - const ctx = canvas.getContext("2d"); - if (!ctx) return; - - const isDark = theme.value === "dark"; - const gridColor = isDark ? "rgba(255,255,255,0.04)" : "rgba(0,0,0,0.06)"; - const textColor = isDark ? "rgba(139,149,165,0.5)" : "rgba(100,116,139,0.7)"; - const placeholderColor = isDark ? "rgba(139,149,165,0.4)" : "rgba(100,116,139,0.5)"; - const areaTop = isDark ? "rgba(34,197,94,0.2)" : "rgba(34,197,94,0.12)"; - const areaBottom = isDark ? "rgba(34,197,94,0.01)" : "rgba(34,197,94,0.02)"; - - const dpr = window.devicePixelRatio || 1; - const rect = canvas.getBoundingClientRect(); - canvas.width = rect.width * dpr; - canvas.height = rect.height * dpr; - ctx.scale(dpr, dpr); - const w = rect.width; - const h = rect.height; - - ctx.clearRect(0, 0, w, h); - - if (data.length < 2) { - ctx.fillStyle = placeholderColor; - ctx.font = "12px -apple-system, sans-serif"; - ctx.textAlign = "center"; - ctx.fillText("Collecting data\u2026", w / 2, h / 2); - return; - } - - const max = Math.max(...data, 1); - const pad = { top: 12, right: 12, bottom: 24, left: 44 }; - const cw = w - pad.left - pad.right; - const ch = h - pad.top - pad.bottom; - - // Grid lines - for (let i = 0; i <= 4; i++) { - const y = pad.top + ch * (1 - i / 4); - ctx.strokeStyle = gridColor; - ctx.lineWidth = 1; - ctx.beginPath(); - ctx.moveTo(pad.left, y); - ctx.lineTo(w - pad.right, y); - ctx.stroke(); - ctx.fillStyle = textColor; - ctx.font = "10px -apple-system, sans-serif"; - ctx.textAlign = "right"; - ctx.fillText(((max * i) / 4).toFixed(1), pad.left - 6, y + 3); - } - - // Gradient fill - const gradient = ctx.createLinearGradient(0, pad.top, 0, pad.top + ch); - gradient.addColorStop(0, areaTop); - gradient.addColorStop(1, areaBottom); - - ctx.beginPath(); - ctx.moveTo(pad.left, pad.top + ch); - data.forEach((v, i) => { - const x = pad.left + (i / (data.length - 1)) * cw; - const y = pad.top + ch * (1 - v / max); - ctx.lineTo(x, y); - }); - ctx.lineTo(pad.left + cw, pad.top + ch); - ctx.closePath(); - ctx.fillStyle = gradient; - ctx.fill(); - - // Line - ctx.beginPath(); - data.forEach((v, i) => { - const x = pad.left + (i / (data.length - 1)) * cw; - const y = pad.top + ch * (1 - v / max); - i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); - }); - ctx.strokeStyle = "#22c55e"; - ctx.lineWidth = 2; - ctx.lineJoin = "round"; - ctx.stroke(); - - // Current value dot - if (data.length > 0) { - const lastX = pad.left + cw; - const lastY = pad.top + ch * (1 - data[data.length - 1] / max); - ctx.beginPath(); - ctx.arc(lastX, lastY, 3, 0, Math.PI * 2); - ctx.fillStyle = "#22c55e"; - ctx.fill(); - ctx.beginPath(); - ctx.arc(lastX, lastY, 5, 0, Math.PI * 2); - ctx.strokeStyle = "rgba(34,197,94,0.3)"; - ctx.lineWidth = 2; - ctx.stroke(); - } - }, [data, theme.value]); - - const current = data.length > 0 ? data[data.length - 1] : 0; - - return ( -
    -
    -
    - -

    Throughput

    -
    - - {current.toFixed(1)} jobs/s - -
    - -
    - ); -} diff --git a/dashboard/src/charts/timeseries-chart.tsx b/dashboard/src/charts/timeseries-chart.tsx deleted file mode 100644 index 18d7752..0000000 --- a/dashboard/src/charts/timeseries-chart.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import { useEffect, useRef } from "preact/hooks"; -import type { TimeseriesBucket } from "../api"; -import { theme } from "../hooks"; - -interface TimeseriesChartProps { - data: TimeseriesBucket[]; -} - -export function TimeseriesChart({ data }: TimeseriesChartProps) { - const canvasRef = useRef(null); - - useEffect(() => { - const canvas = canvasRef.current; - if (!canvas) return; - - const ctx = canvas.getContext("2d"); - if (!ctx) return; - - const dpr = window.devicePixelRatio || 1; - const rect = canvas.getBoundingClientRect(); - canvas.width = rect.width * dpr; - canvas.height = rect.height * dpr; - ctx.scale(dpr, dpr); - const w = rect.width; - const h = rect.height; - - const isDark = theme.value === "dark"; - const gridColor = isDark ? "rgba(255,255,255,0.04)" : "rgba(0,0,0,0.06)"; - const textColor = isDark ? "rgba(139,149,165,0.5)" : "rgba(100,116,139,0.7)"; - const placeholderColor = isDark ? "rgba(139,149,165,0.4)" : "rgba(100,116,139,0.5)"; - - ctx.clearRect(0, 0, w, h); - - if (!data.length) { - ctx.fillStyle = placeholderColor; - ctx.font = "12px -apple-system, sans-serif"; - ctx.textAlign = "center"; - ctx.fillText("No timeseries data", w / 2, h / 2); - return; - } - - const pad = { top: 12, right: 12, bottom: 32, left: 48 }; - const cw = w - pad.left - pad.right; - const ch = h - pad.top - pad.bottom; - - const maxCount = Math.max(...data.map((d) => d.success + d.failure), 1); - const barW = Math.max(3, cw / data.length - 2); - const gap = Math.max(1, (cw - barW * data.length) / data.length); - - // Y-axis grid - for (let i = 0; i <= 4; i++) { - const y = pad.top + ch * (1 - i / 4); - ctx.strokeStyle = gridColor; - ctx.lineWidth = 1; - ctx.beginPath(); - ctx.moveTo(pad.left, y); - ctx.lineTo(w - pad.right, y); - ctx.stroke(); - ctx.fillStyle = textColor; - ctx.font = "10px -apple-system, sans-serif"; - ctx.textAlign = "right"; - ctx.fillText(Math.round((maxCount * i) / 4).toString(), pad.left - 6, y + 3); - } - - // Bars - data.forEach((d, i) => { - const x = pad.left + i * (barW + gap); - const successH = (d.success / maxCount) * ch; - const failureH = (d.failure / maxCount) * ch; - - // Success bar with rounded top - ctx.fillStyle = "rgba(34,197,94,0.65)"; - ctx.beginPath(); - const successY = pad.top + ch - successH - failureH; - ctx.roundRect(x, successY, barW, successH, [2, 2, 0, 0]); - ctx.fill(); - - // Failure bar stacked - if (failureH > 0) { - ctx.fillStyle = "rgba(239,68,68,0.65)"; - ctx.beginPath(); - ctx.roundRect(x, pad.top + ch - failureH, barW, failureH, [0, 0, 2, 2]); - ctx.fill(); - } - }); - - // X-axis timestamps - ctx.fillStyle = textColor; - ctx.font = "10px -apple-system, sans-serif"; - ctx.textAlign = "center"; - const labelCount = Math.min(6, data.length); - for (let i = 0; i < labelCount; i++) { - const idx = Math.floor((i / (labelCount - 1 || 1)) * (data.length - 1)); - const d = new Date(data[idx].timestamp * 1000); - const label = d.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" }); - const x = pad.left + idx * (barW + gap) + barW / 2; - ctx.fillText(label, x, h - 10); - } - }, [data, theme.value]); - - return ( -
    -
    -

    Throughput Over Time

    -
    - - - Success - - - - Failure - -
    -
    - -
    - ); -} diff --git a/dashboard-next/src/components/layout/app-shell.tsx b/dashboard/src/components/layout/app-shell.tsx similarity index 100% rename from dashboard-next/src/components/layout/app-shell.tsx rename to dashboard/src/components/layout/app-shell.tsx diff --git a/dashboard-next/src/components/layout/breadcrumbs.tsx b/dashboard/src/components/layout/breadcrumbs.tsx similarity index 100% rename from dashboard-next/src/components/layout/breadcrumbs.tsx rename to dashboard/src/components/layout/breadcrumbs.tsx diff --git a/dashboard-next/src/components/layout/command-palette.tsx b/dashboard/src/components/layout/command-palette.tsx similarity index 100% rename from dashboard-next/src/components/layout/command-palette.tsx rename to dashboard/src/components/layout/command-palette.tsx diff --git a/dashboard/src/components/layout/header.tsx b/dashboard/src/components/layout/header.tsx index 9371c2c..8f5de82 100644 --- a/dashboard/src/components/layout/header.tsx +++ b/dashboard/src/components/layout/header.tsx @@ -1,100 +1,45 @@ -import { Moon, RefreshCw, Search, Sun, Zap } from "lucide-preact"; -import { useEffect, useState } from "preact/hooks"; -import { route } from "preact-router"; -import { - lastRefreshAt, - refreshInterval, - setRefreshInterval, - theme, - toggleTheme, -} from "../../hooks"; - -function RelativeTime() { - const [ago, setAgo] = useState(""); - - useEffect(() => { - const update = () => { - const seconds = Math.round((Date.now() - lastRefreshAt.value) / 1000); - setAgo(seconds < 2 ? "just now" : `${seconds}s ago`); - }; - update(); - const timer = setInterval(update, 1000); - return () => clearInterval(timer); - }, [lastRefreshAt.value]); - - return {ago}; -} +import { Search } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Kbd } from "@/components/ui/kbd"; +import { useCommandPalette } from "@/providers/command-palette-provider"; +import { LastRefreshed } from "./last-refreshed"; +import { MobileMenu } from "./mobile-menu"; +import { RefreshControl } from "./refresh-control"; +import { ThemeToggle } from "./theme-toggle"; export function Header() { - const [searchValue, setSearchValue] = useState(""); - - const handleSearch = (e: Event) => { - e.preventDefault(); - const id = searchValue.trim(); - if (id) { - route(`/jobs/${id}`); - setSearchValue(""); - } - }; - + const { setOpen } = useCommandPalette(); return ( -
    - -
    - -
    - - taskito +
    + + + + + +
    + + +
    ); diff --git a/dashboard/src/components/layout/index.ts b/dashboard/src/components/layout/index.ts index 70b058c..c842370 100644 --- a/dashboard/src/components/layout/index.ts +++ b/dashboard/src/components/layout/index.ts @@ -1,3 +1,12 @@ +export { AppShell } from "./app-shell"; +export type { Crumb } from "./breadcrumbs"; +export { Breadcrumbs } from "./breadcrumbs"; +export { CommandPalette } from "./command-palette"; export { Header } from "./header"; -export { Shell } from "./shell"; +export { LastRefreshed } from "./last-refreshed"; +export { MobileMenu } from "./mobile-menu"; +export { PageHeader } from "./page-header"; +export { RefreshControl } from "./refresh-control"; +export { RouteErrorBoundary } from "./route-error-boundary"; export { Sidebar } from "./sidebar"; +export { ThemeToggle } from "./theme-toggle"; diff --git a/dashboard-next/src/components/layout/last-refreshed.tsx b/dashboard/src/components/layout/last-refreshed.tsx similarity index 100% rename from dashboard-next/src/components/layout/last-refreshed.tsx rename to dashboard/src/components/layout/last-refreshed.tsx diff --git a/dashboard-next/src/components/layout/mobile-menu.tsx b/dashboard/src/components/layout/mobile-menu.tsx similarity index 100% rename from dashboard-next/src/components/layout/mobile-menu.tsx rename to dashboard/src/components/layout/mobile-menu.tsx diff --git a/dashboard-next/src/components/layout/page-header.tsx b/dashboard/src/components/layout/page-header.tsx similarity index 100% rename from dashboard-next/src/components/layout/page-header.tsx rename to dashboard/src/components/layout/page-header.tsx diff --git a/dashboard-next/src/components/layout/refresh-control.tsx b/dashboard/src/components/layout/refresh-control.tsx similarity index 100% rename from dashboard-next/src/components/layout/refresh-control.tsx rename to dashboard/src/components/layout/refresh-control.tsx diff --git a/dashboard-next/src/components/layout/route-error-boundary.tsx b/dashboard/src/components/layout/route-error-boundary.tsx similarity index 100% rename from dashboard-next/src/components/layout/route-error-boundary.tsx rename to dashboard/src/components/layout/route-error-boundary.tsx diff --git a/dashboard/src/components/layout/shell.tsx b/dashboard/src/components/layout/shell.tsx deleted file mode 100644 index 0d65daf..0000000 --- a/dashboard/src/components/layout/shell.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import type { ComponentChildren } from "preact"; -import { Header } from "./header"; -import { Sidebar } from "./sidebar"; - -interface ShellProps { - children: ComponentChildren; -} - -export function Shell({ children }: ShellProps) { - return ( -
    -
    -
    - -
    -
    {children}
    -
    -
    -
    - ); -} diff --git a/dashboard/src/components/layout/sidebar.tsx b/dashboard/src/components/layout/sidebar.tsx index e913590..30910a7 100644 --- a/dashboard/src/components/layout/sidebar.tsx +++ b/dashboard/src/components/layout/sidebar.tsx @@ -1,21 +1,23 @@ -import type { LucideIcon } from "lucide-preact"; +import { Link, useLocation } from "@tanstack/react-router"; import { + Activity, + AlertOctagon, BarChart3, Box, - Cog, - Layers, + CircuitBoard, LayoutDashboard, - ListTodo, + ListTree, + type LucideIcon, ScrollText, Server, - ShieldAlert, + Settings2, Skull, -} from "lucide-preact"; -import { useEffect, useState } from "preact/hooks"; -import { getCurrentUrl } from "preact-router"; +} from "lucide-react"; +import { cn } from "@/lib/cn"; +import { site } from "@/lib/site"; interface NavItem { - path: string; + to: string; label: string; icon: LucideIcon; } @@ -25,89 +27,82 @@ interface NavGroup { items: NavItem[]; } -const NAV_GROUPS: NavGroup[] = [ +const NAV: NavGroup[] = [ { title: "Monitoring", items: [ - { path: "/", label: "Overview", icon: LayoutDashboard }, - { path: "/jobs", label: "Jobs", icon: ListTodo }, - { path: "/metrics", label: "Metrics", icon: BarChart3 }, - { path: "/logs", label: "Logs", icon: ScrollText }, + { to: "/", label: "Overview", icon: LayoutDashboard }, + { to: "/jobs", label: "Jobs", icon: ListTree }, + { to: "/metrics", label: "Metrics", icon: BarChart3 }, + { to: "/logs", label: "Logs", icon: ScrollText }, ], }, { title: "Infrastructure", items: [ - { path: "/workers", label: "Workers", icon: Server }, - { path: "/queues", label: "Queues", icon: Layers }, - { path: "/resources", label: "Resources", icon: Box }, - { path: "/circuit-breakers", label: "Circuit Breakers", icon: ShieldAlert }, + { to: "/queues", label: "Queues", icon: Box }, + { to: "/workers", label: "Workers", icon: Server }, + { to: "/resources", label: "Resources", icon: Activity }, ], }, { - title: "Advanced", + title: "Reliability", items: [ - { path: "/dead-letters", label: "Dead Letters", icon: Skull }, - { path: "/system", label: "System", icon: Cog }, + { to: "/dead-letters", label: "Dead letters", icon: Skull }, + { to: "/circuit-breakers", label: "Circuit breakers", icon: CircuitBoard }, + { to: "/system", label: "System", icon: Settings2 }, ], }, ]; -function isActive(current: string, path: string): boolean { - if (path === "/") return current === "/"; - return current === path || current.startsWith(`${path}/`); -} - export function Sidebar() { - const [currentPath, setCurrentPath] = useState(getCurrentUrl()); - - useEffect(() => { - const handler = () => setCurrentPath(getCurrentUrl()); - addEventListener("popstate", handler); - addEventListener("pushstate", handler); - return () => { - removeEventListener("popstate", handler); - removeEventListener("pushstate", handler); - }; - }, []); - + const { pathname } = useLocation(); return ( -