From c85bfd21db0b5ccb2fa3539c3d536011d63f4623 Mon Sep 17 00:00:00 2001 From: rekpero Date: Sat, 14 Mar 2026 04:22:38 +0000 Subject: [PATCH 01/94] Fix #3: Migrate dashboard UI to React 18 + Vite SPA - Add React 18 + Vite 5 frontend in frontend/ directory with Tailwind CSS, React Query v5, Lucide React icons, and date-fns - Implement full component tree: layout (Header, WorkspaceSwitcher, TabNav), metrics (MetricsBar, MetricCard), agents (AgentCard, AgentLogViewer), issues (IssueQueue), PRs (PRTracker), modals (AddWorkspace, WorkspaceSettings, EnvEditor), and shared UI primitives (Card, Badge, Button, Modal, Spinner) - Preserve existing dark color palette via CSS custom properties in tokens.css - Add SPA catch-all route in dashboard.py (registered after all /api/* routes) so React client-side navigation works on direct URL loads - Add build-ui command to run.sh (cd frontend && npm install && npm run build) and integrate it into the install flow - Update .gitignore to exclude frontend/node_modules/ and frontend/dist/ - Build outputs to orchestrator/static/ via vite.config.js outDir setting Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 4 +- frontend/index.html | 12 + frontend/package-lock.json | 2699 +++++++++++++++++ frontend/package.json | 27 + frontend/postcss.config.js | 6 + frontend/src/App.jsx | 62 + frontend/src/api/client.js | 104 + .../src/components/agents/ActiveAgents.jsx | 69 + frontend/src/components/agents/AgentCard.jsx | 92 + .../src/components/agents/AgentLogViewer.jsx | 89 + .../components/agents/AgentStatusBadge.jsx | 14 + frontend/src/components/issues/IssueQueue.jsx | 117 + .../components/issues/IssueStatusBadge.jsx | 14 + frontend/src/components/layout/Header.jsx | 42 + frontend/src/components/layout/TabNav.jsx | 40 + .../components/layout/WorkspaceSwitcher.jsx | 100 + .../src/components/metrics/MetricCard.jsx | 29 + .../src/components/metrics/MetricsBar.jsx | 27 + .../components/modals/AddWorkspaceModal.jsx | 81 + frontend/src/components/modals/EnvEditor.jsx | 176 ++ .../modals/WorkspaceSettingsModal.jsx | 131 + frontend/src/components/prs/PRTracker.jsx | 77 + frontend/src/components/prs/ReviewThreads.jsx | 39 + frontend/src/components/ui/Badge.jsx | 18 + frontend/src/components/ui/Button.jsx | 29 + frontend/src/components/ui/Card.jsx | 22 + frontend/src/components/ui/EmptyState.jsx | 15 + frontend/src/components/ui/Modal.jsx | 54 + frontend/src/components/ui/Spinner.jsx | 10 + frontend/src/context/WorkspaceContext.jsx | 30 + frontend/src/hooks/useAgents.js | 21 + frontend/src/hooks/useIssues.js | 21 + frontend/src/hooks/useMetrics.js | 11 + frontend/src/hooks/usePRs.js | 11 + frontend/src/hooks/useWorkspaces.js | 45 + frontend/src/index.css | 41 + frontend/src/main.jsx | 25 + frontend/src/tokens.css | 12 + frontend/tailwind.config.js | 24 + frontend/vite.config.js | 15 + orchestrator/dashboard.py | 15 +- orchestrator/static/assets/index-ShMwgtnx.js | 151 + orchestrator/static/assets/index-Z2XrvsZb.css | 1 + orchestrator/static/index.html | 2269 +------------- run.sh | 18 + 45 files changed, 4645 insertions(+), 2264 deletions(-) create mode 100644 frontend/index.html create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/postcss.config.js create mode 100644 frontend/src/App.jsx create mode 100644 frontend/src/api/client.js create mode 100644 frontend/src/components/agents/ActiveAgents.jsx create mode 100644 frontend/src/components/agents/AgentCard.jsx create mode 100644 frontend/src/components/agents/AgentLogViewer.jsx create mode 100644 frontend/src/components/agents/AgentStatusBadge.jsx create mode 100644 frontend/src/components/issues/IssueQueue.jsx create mode 100644 frontend/src/components/issues/IssueStatusBadge.jsx create mode 100644 frontend/src/components/layout/Header.jsx create mode 100644 frontend/src/components/layout/TabNav.jsx create mode 100644 frontend/src/components/layout/WorkspaceSwitcher.jsx create mode 100644 frontend/src/components/metrics/MetricCard.jsx create mode 100644 frontend/src/components/metrics/MetricsBar.jsx create mode 100644 frontend/src/components/modals/AddWorkspaceModal.jsx create mode 100644 frontend/src/components/modals/EnvEditor.jsx create mode 100644 frontend/src/components/modals/WorkspaceSettingsModal.jsx create mode 100644 frontend/src/components/prs/PRTracker.jsx create mode 100644 frontend/src/components/prs/ReviewThreads.jsx create mode 100644 frontend/src/components/ui/Badge.jsx create mode 100644 frontend/src/components/ui/Button.jsx create mode 100644 frontend/src/components/ui/Card.jsx create mode 100644 frontend/src/components/ui/EmptyState.jsx create mode 100644 frontend/src/components/ui/Modal.jsx create mode 100644 frontend/src/components/ui/Spinner.jsx create mode 100644 frontend/src/context/WorkspaceContext.jsx create mode 100644 frontend/src/hooks/useAgents.js create mode 100644 frontend/src/hooks/useIssues.js create mode 100644 frontend/src/hooks/useMetrics.js create mode 100644 frontend/src/hooks/usePRs.js create mode 100644 frontend/src/hooks/useWorkspaces.js create mode 100644 frontend/src/index.css create mode 100644 frontend/src/main.jsx create mode 100644 frontend/src/tokens.css create mode 100644 frontend/tailwind.config.js create mode 100644 frontend/vite.config.js create mode 100644 orchestrator/static/assets/index-ShMwgtnx.js create mode 100644 orchestrator/static/assets/index-Z2XrvsZb.css diff --git a/.gitignore b/.gitignore index 6d589c6..531d46b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,6 @@ __pycache__/ venv/ *.egg-info/ dist/ -build/ \ No newline at end of file +build/ +frontend/node_modules/ +frontend/dist/ \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..ae962dd --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,12 @@ + + + + + + Claude Code Swarm Dashboard + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..78d9638 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,2699 @@ +{ + "name": "claude-code-swarm-dashboard", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "claude-code-swarm-dashboard", + "version": "0.1.0", + "dependencies": { + "@tanstack/react-query": "^5.56.2", + "date-fns": "^4.1.0", + "lucide-react": "^0.462.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.11", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.2", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.47", + "tailwindcss": "^3.4.14", + "vite": "^5.4.9" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tanstack/query-core": { + "version": "5.90.20", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.20.tgz", + "integrity": "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.90.21", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.21.tgz", + "integrity": "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.90.20" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.27", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", + "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001774", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.7", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.7.tgz", + "integrity": "sha512-1ghYO3HnxGec0TCGBXiDLVns4eCSx4zJpxnHrlqFQajmhfKMQBzUGDdkMK7fUW7PTHTeLf+j87aTuKuuwWzMGw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001778", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001778.tgz", + "integrity": "sha512-PN7uxFL+ExFJO61aVmP1aIEG4i9whQd4eoSCebav62UwDyp5OHh06zN4jqKSMePVgxHifCw1QJxdRkA1Pisekg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.313", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz", + "integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.462.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.462.0.tgz", + "integrity": "sha512-NTL7EbAao9IFtuSivSZgrAh4fZd09Lr+6MTkqIxuHaH2nnYiYIzXPo06cOxHg9wKLdj6LL8TByG4qpePqwgx/g==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..8ea8765 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,27 @@ +{ + "name": "claude-code-swarm-dashboard", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "@tanstack/react-query": "^5.56.2", + "lucide-react": "^0.462.0", + "date-fns": "^4.1.0" + }, + "devDependencies": { + "@types/react": "^18.3.11", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.2", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.47", + "tailwindcss": "^3.4.14", + "vite": "^5.4.9" + } +} diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx new file mode 100644 index 0000000..22e4ffd --- /dev/null +++ b/frontend/src/App.jsx @@ -0,0 +1,62 @@ +import { useState } from 'react' +import { Header } from './components/layout/Header' +import { TabNav } from './components/layout/TabNav' +import { MetricsBar } from './components/metrics/MetricsBar' +import { ActiveAgents } from './components/agents/ActiveAgents' +import { IssueQueue } from './components/issues/IssueQueue' +import { PRTracker } from './components/prs/PRTracker' +import { AddWorkspaceModal } from './components/modals/AddWorkspaceModal' +import { WorkspaceSettingsModal } from './components/modals/WorkspaceSettingsModal' +import { useMetrics } from './hooks/useMetrics' +import { useAgents } from './hooks/useAgents' +import { useIssues } from './hooks/useIssues' +import { usePRs } from './hooks/usePRs' +import { useWorkspaceContext } from './context/WorkspaceContext' + +function ErrorBanner({ error }) { + if (!error) return null + return ( +
+ Cannot connect to backend — {error.message} +
+ ) +} + +export function App() { + const [activeTab, setActiveTab] = useState('agents') + const [addWorkspaceOpen, setAddWorkspaceOpen] = useState(false) + const [settingsOpen, setSettingsOpen] = useState(false) + const { selectedWorkspaceId } = useWorkspaceContext() + + const { error: metricsError } = useMetrics(selectedWorkspaceId) + const { data: agentsData } = useAgents(selectedWorkspaceId) + const { data: issuesData } = useIssues(selectedWorkspaceId) + const { data: prsData } = usePRs(selectedWorkspaceId) + + const counts = { + agents: agentsData?.total ?? 0, + issues: issuesData?.issues?.length ?? 0, + prs: prsData?.prs?.length ?? 0, + } + + return ( +
+
setAddWorkspaceOpen(true)} + onOpenSettings={() => setSettingsOpen(true)} + /> + + + + +
+ {activeTab === 'agents' && } + {activeTab === 'issues' && } + {activeTab === 'prs' && } +
+ + setAddWorkspaceOpen(false)} /> + setSettingsOpen(false)} /> +
+ ) +} diff --git a/frontend/src/api/client.js b/frontend/src/api/client.js new file mode 100644 index 0000000..e23e933 --- /dev/null +++ b/frontend/src/api/client.js @@ -0,0 +1,104 @@ +const BASE = '' + +async function apiFetch(path, options = {}) { + const res = await fetch(`${BASE}${path}`, { + headers: { 'Content-Type': 'application/json', ...options.headers }, + ...options, + }) + if (!res.ok) { + const err = await res.json().catch(() => ({ error: res.statusText })) + throw new Error(err.error || `HTTP ${res.status}`) + } + return res.json() +} + +// Metrics +export const getMetrics = (wsId) => + apiFetch(`/api/metrics${wsId ? `?workspace_id=${wsId}` : ''}`) + +// Agents +export const getAgents = (wsId, limit = 20, offset = 0) => + apiFetch(`/api/agents?limit=${limit}&offset=${offset}${wsId ? `&workspace_id=${wsId}` : ''}`) + +export const getAgentLogs = (agentId, since = 0) => + apiFetch(`/api/agents/${agentId}/logs?since=${since}`) + +// Issues +export const getIssues = (wsId) => + apiFetch(`/api/issues${wsId ? `?workspace_id=${wsId}` : ''}`) + +export const updateIssueStatus = (issueNumber, status, wsId) => + apiFetch(`/api/issues/${issueNumber}/status${wsId ? `?workspace_id=${wsId}` : ''}`, { + method: 'PUT', + body: JSON.stringify({ status }), + }) + +// PRs +export const getPRs = (wsId) => + apiFetch(`/api/prs${wsId ? `?workspace_id=${wsId}` : ''}`) + +// Workspaces +export const getWorkspaces = () => apiFetch('/api/workspaces') + +export const createWorkspace = (data) => + apiFetch('/api/workspaces', { method: 'POST', body: JSON.stringify(data) }) + +export const updateWorkspace = (id, data) => + apiFetch(`/api/workspaces/${id}`, { method: 'PUT', body: JSON.stringify(data) }) + +export const deleteWorkspace = (id) => + apiFetch(`/api/workspaces/${id}`, { method: 'DELETE' }) + +// Env files +export const getEnvFiles = (wsId) => + apiFetch(`/api/workspaces/${wsId}/env-files`) + +export const getEnv = (wsId, file = '.env') => + apiFetch(`/api/workspaces/${wsId}/env?env_file=${encodeURIComponent(file)}`) + +export const saveEnv = (wsId, file, vars) => + apiFetch(`/api/workspaces/${wsId}/env`, { + method: 'PUT', + body: JSON.stringify({ vars, env_file: file }), + }) + +export const deleteEnvFile = (wsId, file) => + apiFetch(`/api/workspaces/${wsId}/env?env_file=${encodeURIComponent(file)}`, { + method: 'DELETE', + }) + +export const loadEnvFromDisk = (wsId, file = '.env') => + apiFetch(`/api/workspaces/${wsId}/env-load?env_file=${encodeURIComponent(file)}`, { + method: 'POST', + }) + +// Planning +export const getPlanningSession = (sessionId) => + apiFetch(`/api/planning/${sessionId}`) + +export const getPlanningEvents = (sessionId, since = 0) => + apiFetch(`/api/planning/${sessionId}/events?since=${since}`) + +export const startPlanning = (wsId, message) => + apiFetch('/api/planning', { method: 'POST', body: JSON.stringify({ workspace_id: wsId, message }) }) + +export const refinePlan = (sessionId, message) => + apiFetch(`/api/planning/${sessionId}/messages`, { + method: 'POST', + body: JSON.stringify({ message }), + }) + +export const createIssueFromPlan = (sessionId, title = '') => + apiFetch(`/api/planning/${sessionId}/create-issue`, { + method: 'POST', + body: JSON.stringify({ title }), + }) + +export const cancelPlanning = (sessionId) => + apiFetch(`/api/planning/${sessionId}/cancel`, { method: 'POST' }) + +export const deletePlanningSession = (sessionId) => + apiFetch(`/api/planning/${sessionId}`, { method: 'DELETE' }) + +export const listPlanningSessions = (wsId) => + apiFetch(`/api/workspaces/${wsId}/planning-sessions`) diff --git a/frontend/src/components/agents/ActiveAgents.jsx b/frontend/src/components/agents/ActiveAgents.jsx new file mode 100644 index 0000000..2fed3da --- /dev/null +++ b/frontend/src/components/agents/ActiveAgents.jsx @@ -0,0 +1,69 @@ +import { useState } from 'react' +import { Bot, ChevronLeft, ChevronRight } from 'lucide-react' +import { AgentCard } from './AgentCard' +import { EmptyState } from '../ui/EmptyState' +import { Button } from '../ui/Button' +import { Spinner } from '../ui/Spinner' +import { useAgents } from '../../hooks/useAgents' +import { useWorkspaceContext } from '../../context/WorkspaceContext' + +const PAGE_SIZE = 20 + +export function ActiveAgents() { + const [offset, setOffset] = useState(0) + const { selectedWorkspaceId } = useWorkspaceContext() + const { data, isLoading } = useAgents(selectedWorkspaceId, { limit: PAGE_SIZE, offset }) + + const agents = data?.agents || [] + const total = data?.total ?? 0 + const totalPages = Math.ceil(total / PAGE_SIZE) + const currentPage = Math.floor(offset / PAGE_SIZE) + 1 + + return ( +
+
+

+ Active Agents + {total > 0 && ({total})} +

+ {totalPages > 1 && ( +
+ + + {currentPage} / {totalPages} + + +
+ )} +
+ + {isLoading ? ( +
+ +
+ ) : agents.length === 0 ? ( + + ) : ( +
+ {agents.map((agent) => ( + + ))} +
+ )} +
+ ) +} diff --git a/frontend/src/components/agents/AgentCard.jsx b/frontend/src/components/agents/AgentCard.jsx new file mode 100644 index 0000000..4654177 --- /dev/null +++ b/frontend/src/components/agents/AgentCard.jsx @@ -0,0 +1,92 @@ +import { useState, useEffect } from 'react' +import { ChevronDown, ChevronUp, ExternalLink } from 'lucide-react' +import { AgentStatusBadge } from './AgentStatusBadge' +import { AgentLogViewer } from './AgentLogViewer' +import { formatDuration, intervalToDuration } from 'date-fns' + +function ElapsedTime({ startedAt, status }) { + const [elapsed, setElapsed] = useState('') + + useEffect(() => { + if (!startedAt) return + const update = () => { + const start = new Date(startedAt) + const now = new Date() + const dur = intervalToDuration({ start, end: now }) + const parts = [] + if (dur.hours) parts.push(`${dur.hours}h`) + if (dur.minutes) parts.push(`${dur.minutes}m`) + parts.push(`${dur.seconds ?? 0}s`) + setElapsed(parts.join(' ')) + } + update() + if (status === 'running') { + const t = setInterval(update, 1000) + return () => clearInterval(t) + } + }, [startedAt, status]) + + return {elapsed} +} + +export function AgentCard({ agent }) { + const [expanded, setExpanded] = useState(false) + const isRunning = agent.status === 'running' + + return ( +
+ {/* Top bar accent for running agents */} + {isRunning && ( +
+ )} + +
setExpanded((v) => !v)} + > + + +
+
+ {agent.issue_number && ( + e.stopPropagation()} + className="text-[var(--blue)] text-xs hover:underline" + > + #{agent.issue_number} + + )} + + {agent.branch || `agent-${agent.agent_id?.slice(0, 8)}`} + +
+
+ {agent.agent_id} +
+
+ +
+ + + {agent.turns_used ?? 0} + {agent.max_turns ? `/${agent.max_turns}` : ''} turns + +
+ {expanded ? : } +
+
+
+ + {expanded && ( +
+ +
+ )} +
+ ) +} diff --git a/frontend/src/components/agents/AgentLogViewer.jsx b/frontend/src/components/agents/AgentLogViewer.jsx new file mode 100644 index 0000000..d77950d --- /dev/null +++ b/frontend/src/components/agents/AgentLogViewer.jsx @@ -0,0 +1,89 @@ +import { useEffect, useRef, useState } from 'react' +import { useAgentLogs } from '../../hooks/useAgents' +import { Spinner } from '../ui/Spinner' + +const MAX_DISPLAY = 500 + +function eventStyle(eventType) { + switch (eventType) { + case 'assistant': return 'text-[var(--text)]' + case 'tool_use': return 'text-[var(--text-dim)]' + case 'tool_result': return 'text-[var(--blue)]' + case 'error': return 'text-[var(--red)]' + case 'result': return 'text-[var(--accent)]' + default: return 'text-[var(--text-dim)]' + } +} + +function formatEvent(event) { + try { + const data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data + if (!data) return event.event_type + + if (event.event_type === 'assistant') { + const content = data.message?.content + if (Array.isArray(content)) { + return content.map((c) => c.text || c.input && JSON.stringify(c.input) || '').filter(Boolean).join(' ') + } + } + if (event.event_type === 'tool_use') { + return `[tool] ${data.name || ''} ${data.input ? JSON.stringify(data.input).slice(0, 120) : ''}` + } + if (event.event_type === 'result') { + return `[result] ${data.result || JSON.stringify(data).slice(0, 120)}` + } + return JSON.stringify(data).slice(0, 200) + } catch { + return event.event_type + } +} + +export function AgentLogViewer({ agentId, isRunning }) { + const bottomRef = useRef(null) + const [since, setSince] = useState(0) + const [allEvents, setAllEvents] = useState([]) + + const { data, isLoading } = useAgentLogs(agentId, { enabled: true, since: 0 }) + + useEffect(() => { + if (data?.events) { + setAllEvents(data.events) + if (data.events.length > 0) { + setSince(data.events[data.events.length - 1].id) + } + } + }, [data]) + + // Auto-scroll only for running agents + useEffect(() => { + if (isRunning && bottomRef.current) { + bottomRef.current.scrollIntoView({ behavior: 'smooth' }) + } + }, [allEvents, isRunning]) + + const displayEvents = allEvents.slice(-MAX_DISPLAY) + + if (isLoading && allEvents.length === 0) { + return ( +
+ +
+ ) + } + + return ( +
+ {displayEvents.length === 0 ? ( + No log events yet... + ) : ( + displayEvents.map((event) => ( +
+ + {formatEvent(event)} +
+ )) + )} +
+
+ ) +} diff --git a/frontend/src/components/agents/AgentStatusBadge.jsx b/frontend/src/components/agents/AgentStatusBadge.jsx new file mode 100644 index 0000000..751db2f --- /dev/null +++ b/frontend/src/components/agents/AgentStatusBadge.jsx @@ -0,0 +1,14 @@ +import { Badge } from '../ui/Badge' + +const STATUS_MAP = { + running: { variant: 'green', label: 'Running' }, + completed: { variant: 'blue', label: 'Completed' }, + failed: { variant: 'red', label: 'Failed' }, + rate_limited: { variant: 'yellow', label: 'Rate Limited' }, + timed_out: { variant: 'red', label: 'Timed Out' }, +} + +export function AgentStatusBadge({ status }) { + const { variant, label } = STATUS_MAP[status] || { variant: 'dim', label: status } + return {label} +} diff --git a/frontend/src/components/issues/IssueQueue.jsx b/frontend/src/components/issues/IssueQueue.jsx new file mode 100644 index 0000000..366c507 --- /dev/null +++ b/frontend/src/components/issues/IssueQueue.jsx @@ -0,0 +1,117 @@ +import { ExternalLink, RefreshCw, Inbox } from 'lucide-react' +import { IssueStatusBadge } from './IssueStatusBadge' +import { EmptyState } from '../ui/EmptyState' +import { Button } from '../ui/Button' +import { Spinner } from '../ui/Spinner' +import { useIssues, useUpdateIssueStatus } from '../../hooks/useIssues' +import { useWorkspaceContext } from '../../context/WorkspaceContext' +import { formatDistanceToNow } from 'date-fns' + +export function IssueQueue() { + const { selectedWorkspaceId } = useWorkspaceContext() + const { data, isLoading } = useIssues(selectedWorkspaceId) + const { mutate: updateStatus, isPending: isUpdating } = useUpdateIssueStatus(selectedWorkspaceId) + + const issues = data?.issues || [] + + return ( +
+
+

+ Issue Queue + {issues.length > 0 && ( + ({issues.length}) + )} +

+
+ + {isLoading ? ( +
+ +
+ ) : issues.length === 0 ? ( + + ) : ( +
+ + + + + + + + + + + + + {issues.map((issue) => ( + + + + + + + + + + ))} + +
#TitleStatusAttemptsPRUpdated +
+ {issue.issue_number} + + + {issue.title || `Issue #${issue.issue_number}`} + + + + + + {issue.attempts ?? 0} + + {issue.pr_number ? ( + + #{issue.pr_number} + + ) : ( + + )} + + {issue.updated_at + ? formatDistanceToNow(new Date(issue.updated_at), { addSuffix: true }) + : '—'} + + {issue.status === 'needs_human' && ( + + )} +
+
+ )} +
+ ) +} diff --git a/frontend/src/components/issues/IssueStatusBadge.jsx b/frontend/src/components/issues/IssueStatusBadge.jsx new file mode 100644 index 0000000..5c0cf91 --- /dev/null +++ b/frontend/src/components/issues/IssueStatusBadge.jsx @@ -0,0 +1,14 @@ +import { Badge } from '../ui/Badge' + +const STATUS_MAP = { + resolved: { variant: 'green', label: 'Resolved' }, + in_progress: { variant: 'purple', label: 'In Progress' }, + pending: { variant: 'yellow', label: 'Pending' }, + needs_human: { variant: 'red', label: 'Needs Human' }, + pr_created: { variant: 'blue', label: 'PR Created' }, +} + +export function IssueStatusBadge({ status }) { + const { variant, label } = STATUS_MAP[status] || { variant: 'dim', label: status } + return {label} +} diff --git a/frontend/src/components/layout/Header.jsx b/frontend/src/components/layout/Header.jsx new file mode 100644 index 0000000..2522f78 --- /dev/null +++ b/frontend/src/components/layout/Header.jsx @@ -0,0 +1,42 @@ +import { Settings, Plus } from 'lucide-react' +import { WorkspaceSwitcher } from './WorkspaceSwitcher' +import { useMetrics } from '../../hooks/useMetrics' +import { useWorkspaceContext } from '../../context/WorkspaceContext' +import { formatDistanceToNow } from 'date-fns' + +export function Header({ onAddWorkspace, onOpenSettings }) { + const { selectedWorkspaceId } = useWorkspaceContext() + const { dataUpdatedAt } = useMetrics(selectedWorkspaceId) + + const lastUpdated = dataUpdatedAt + ? formatDistanceToNow(new Date(dataUpdatedAt), { addSuffix: true }) + : null + + return ( +
+
+ +

Claude Code Swarm

+
+
+ {lastUpdated && ( + Updated {lastUpdated} + )} + + +
+
+ ) +} diff --git a/frontend/src/components/layout/TabNav.jsx b/frontend/src/components/layout/TabNav.jsx new file mode 100644 index 0000000..055a457 --- /dev/null +++ b/frontend/src/components/layout/TabNav.jsx @@ -0,0 +1,40 @@ +const TABS = [ + { id: 'agents', label: 'Agents' }, + { id: 'issues', label: 'Issues' }, + { id: 'prs', label: 'PRs' }, +] + +export function TabNav({ activeTab, onTabChange, counts = {} }) { + return ( +
+ {TABS.map((tab) => { + const count = counts[tab.id] + const isActive = activeTab === tab.id + return ( + + ) + })} +
+ ) +} diff --git a/frontend/src/components/layout/WorkspaceSwitcher.jsx b/frontend/src/components/layout/WorkspaceSwitcher.jsx new file mode 100644 index 0000000..73d384a --- /dev/null +++ b/frontend/src/components/layout/WorkspaceSwitcher.jsx @@ -0,0 +1,100 @@ +import { useState, useRef, useEffect } from 'react' +import { ChevronDown, Plus, Layers } from 'lucide-react' +import { useWorkspaces } from '../../hooks/useWorkspaces' +import { useWorkspaceContext } from '../../context/WorkspaceContext' + +function StatusDot({ status }) { + const color = status === 'active' ? 'var(--green)' : status === 'cloning' ? 'var(--yellow)' : 'var(--red)' + return ( + + ) +} + +export function WorkspaceSwitcher({ onAddWorkspace }) { + const [open, setOpen] = useState(false) + const ref = useRef(null) + const { data } = useWorkspaces() + const { selectedWorkspaceId, setSelectedWorkspaceId } = useWorkspaceContext() + + const workspaces = data?.workspaces || [] + const selected = workspaces.find((w) => w.id === selectedWorkspaceId) + + useEffect(() => { + const handler = (e) => { + if (ref.current && !ref.current.contains(e.target)) setOpen(false) + } + document.addEventListener('mousedown', handler) + return () => document.removeEventListener('mousedown', handler) + }, []) + + // If selected workspace was deleted, fall back to null + useEffect(() => { + if (selectedWorkspaceId && workspaces.length > 0 && !selected) { + setSelectedWorkspaceId(null) + } + }, [selectedWorkspaceId, workspaces, selected, setSelectedWorkspaceId]) + + return ( +
+ + + {open && ( +
+
{ setSelectedWorkspaceId(null); setOpen(false) }} + > + + All Workspaces +
+ + {workspaces.length > 0 && ( +
+ )} + + {workspaces.map((ws) => ( +
{ setSelectedWorkspaceId(ws.id); setOpen(false) }} + > + + {ws.name || ws.repo_url} + + {ws.repo_url?.replace(/^https?:\/\/github\.com\//, '')} + +
+ ))} + +
+
{ onAddWorkspace?.(); setOpen(false) }} + > + + Add Workspace +
+
+ )} +
+ ) +} diff --git a/frontend/src/components/metrics/MetricCard.jsx b/frontend/src/components/metrics/MetricCard.jsx new file mode 100644 index 0000000..232f7af --- /dev/null +++ b/frontend/src/components/metrics/MetricCard.jsx @@ -0,0 +1,29 @@ +export function MetricCard({ label, value, color }) { + const colorMap = { + green: 'var(--green)', + blue: 'var(--blue)', + yellow: 'var(--yellow)', + red: 'var(--red)', + accent: 'var(--accent)', + text: 'var(--text)', + } + const c = colorMap[color] || 'var(--text)' + + return ( +
+
+ + {value ?? '—'} + + + {label} + +
+ ) +} diff --git a/frontend/src/components/metrics/MetricsBar.jsx b/frontend/src/components/metrics/MetricsBar.jsx new file mode 100644 index 0000000..1abb2bf --- /dev/null +++ b/frontend/src/components/metrics/MetricsBar.jsx @@ -0,0 +1,27 @@ +import { MetricCard } from './MetricCard' +import { useMetrics } from '../../hooks/useMetrics' +import { useWorkspaceContext } from '../../context/WorkspaceContext' + +export function MetricsBar() { + const { selectedWorkspaceId } = useWorkspaceContext() + const { data, isLoading } = useMetrics(selectedWorkspaceId) + + const m = data || {} + const cards = [ + { label: 'Resolved', value: isLoading ? '—' : (m.resolved ?? 0), color: 'green' }, + { label: 'In Queue', value: isLoading ? '—' : (m.pending ?? 0), color: 'text' }, + { label: 'In Progress', value: isLoading ? '—' : (m.in_progress ?? 0), color: 'accent' }, + { label: 'PRs Open', value: isLoading ? '—' : (m.prs_open ?? 0), color: 'blue' }, + { label: 'Needs Human', value: isLoading ? '—' : (m.needs_human ?? 0), color: 'red' }, + { label: 'Rate Limited', value: isLoading ? '—' : (m.rate_limited ?? 0), color: 'yellow' }, + { label: 'Avg Turns', value: isLoading ? '—' : (m.avg_turns != null ? m.avg_turns.toFixed(1) : '0.0'), color: 'text' }, + ] + + return ( +
+ {cards.map((card) => ( + + ))} +
+ ) +} diff --git a/frontend/src/components/modals/AddWorkspaceModal.jsx b/frontend/src/components/modals/AddWorkspaceModal.jsx new file mode 100644 index 0000000..bc4661c --- /dev/null +++ b/frontend/src/components/modals/AddWorkspaceModal.jsx @@ -0,0 +1,81 @@ +import { useState } from 'react' +import { Modal } from '../ui/Modal' +import { Button } from '../ui/Button' +import { useCreateWorkspace } from '../../hooks/useWorkspaces' +import { useWorkspaceContext } from '../../context/WorkspaceContext' + +export function AddWorkspaceModal({ open, onClose }) { + const { mutate: create, isPending } = useCreateWorkspace() + const { setSelectedWorkspaceId } = useWorkspaceContext() + const [form, setForm] = useState({ repo_url: '', name: '', base_branch: 'main' }) + const [error, setError] = useState('') + + const set = (field) => (e) => setForm((f) => ({ ...f, [field]: e.target.value })) + + const handleSubmit = (e) => { + e.preventDefault() + setError('') + if (!form.repo_url.trim()) { + setError('Repository URL is required') + return + } + create( + { repo_url: form.repo_url.trim(), name: form.name.trim() || undefined, base_branch: form.base_branch || 'main' }, + { + onSuccess: (data) => { + if (data?.workspace?.id) setSelectedWorkspaceId(data.workspace.id) + onClose() + setForm({ repo_url: '', name: '', base_branch: 'main' }) + }, + onError: (err) => setError(err.message), + } + ) + } + + return ( + +
+
+ + +
+
+ + +
+
+ + +
+ + {error &&

{error}

} + + {isPending && ( +

Cloning repository...

+ )} + +
+ + +
+
+
+ ) +} diff --git a/frontend/src/components/modals/EnvEditor.jsx b/frontend/src/components/modals/EnvEditor.jsx new file mode 100644 index 0000000..36827a4 --- /dev/null +++ b/frontend/src/components/modals/EnvEditor.jsx @@ -0,0 +1,176 @@ +import { useState, useEffect } from 'react' +import { Plus, Trash2, Upload, Save } from 'lucide-react' +import { Button } from '../ui/Button' +import { Spinner } from '../ui/Spinner' +import { getEnvFiles, getEnv, saveEnv, deleteEnvFile, loadEnvFromDisk } from '../../api/client' +import { useQueryClient } from '@tanstack/react-query' + +function parseEnvText(text) { + const vars = {} + for (const line of text.split('\n')) { + const trimmed = line.trim() + if (!trimmed || trimmed.startsWith('#')) continue + const idx = trimmed.indexOf('=') + if (idx < 0) continue + const key = trimmed.slice(0, idx).trim() + const val = trimmed.slice(idx + 1).trim().replace(/^["']|["']$/g, '') + if (key) vars[key] = val + } + return vars +} + +export function EnvEditor({ workspaceId }) { + const queryClient = useQueryClient() + const [envFiles, setEnvFiles] = useState([]) + const [activeFile, setActiveFile] = useState('.env') + const [rows, setRows] = useState([]) + const [loading, setLoading] = useState(false) + const [saving, setSaving] = useState(false) + const [pasteText, setPasteText] = useState('') + const [showPaste, setShowPaste] = useState(false) + + useEffect(() => { + if (!workspaceId) return + getEnvFiles(workspaceId).then((data) => { + const files = [...new Set(['.env', ...(data.managed || []), ...(data.discovered || [])])] + setEnvFiles(files) + }).catch(() => setEnvFiles(['.env'])) + }, [workspaceId]) + + useEffect(() => { + if (!workspaceId || !activeFile) return + setLoading(true) + getEnv(workspaceId, activeFile).then((data) => { + const entries = Object.entries(data.vars || {}).map(([k, v]) => ({ key: k, value: v })) + setRows(entries) + }).catch(() => setRows([])) + .finally(() => setLoading(false)) + }, [workspaceId, activeFile]) + + const setRow = (i, field, val) => { + setRows((prev) => prev.map((r, idx) => idx === i ? { ...r, [field]: val } : r)) + } + + const addRow = () => setRows((prev) => [...prev, { key: '', value: '' }]) + + const removeRow = (i) => setRows((prev) => prev.filter((_, idx) => idx !== i)) + + const handleSave = async () => { + if (!workspaceId) return + setSaving(true) + const vars = {} + for (const { key, value } of rows) { + if (key.trim()) vars[key.trim()] = value + } + await saveEnv(workspaceId, activeFile, vars).catch(() => {}) + setSaving(false) + } + + const handlePaste = () => { + const parsed = parseEnvText(pasteText) + const newRows = Object.entries(parsed).map(([k, v]) => ({ key: k, value: v })) + setRows((prev) => { + const existing = new Set(prev.map((r) => r.key)) + return [...prev, ...newRows.filter((r) => !existing.has(r.key))] + }) + setPasteText('') + setShowPaste(false) + } + + const handleFileUpload = (e) => { + const file = e.target.files?.[0] + if (!file) return + const reader = new FileReader() + reader.onload = (ev) => { + const parsed = parseEnvText(ev.target.result) + setRows(Object.entries(parsed).map(([k, v]) => ({ key: k, value: v }))) + } + reader.readAsText(file) + } + + if (loading) { + return
+ } + + return ( +
+ {/* File tabs */} +
+ {envFiles.map((f) => ( + + ))} +
+ + {/* Rows */} +
+ {rows.map((row, i) => ( +
+ setRow(i, 'key', e.target.value)} + placeholder="KEY" + className="flex-1 px-2 py-1.5 text-[11px] bg-[var(--bg)] border border-[var(--border)] rounded text-[var(--text)] font-mono focus:border-[var(--accent)] outline-none" + /> + setRow(i, 'value', e.target.value)} + placeholder="value" + className="flex-[2] px-2 py-1.5 text-[11px] bg-[var(--bg)] border border-[var(--border)] rounded text-[var(--text)] font-mono focus:border-[var(--accent)] outline-none" + /> + +
+ ))} +
+ + {showPaste && ( +
+ -
- - -
-
- - -
-
-
- - - - - - + + + + Claude Code Swarm Dashboard + + + + +
+ diff --git a/run.sh b/run.sh index eadc219..ff2cf62 100755 --- a/run.sh +++ b/run.sh @@ -218,6 +218,7 @@ cmd_install() { _ensure_deps _ensure_venv _ensure_env + cmd_build_ui # Build PATH that includes claude, gh, node, etc. SVC_PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" @@ -282,6 +283,21 @@ cmd_uninstall() { fi } +# ── UI Build ───────────────────────────────────────────── + +cmd_build_ui() { + FRONTEND_DIR="$DIR/frontend" + if [ ! -d "$FRONTEND_DIR" ]; then + echo "ERROR: frontend/ directory not found at $FRONTEND_DIR" + exit 1 + fi + echo "Building dashboard UI..." + cd "$FRONTEND_DIR" + npm install --silent + npm run build + echo "Dashboard UI built successfully — output in orchestrator/static/" +} + # ── Skills (powered by skills.sh) ───────────────────────── # Default skill repos to install. Each entry is "owner/repo". @@ -475,6 +491,7 @@ case "${1:-help}" in logs) cmd_logs ;; install) _require_sudo "$@"; cmd_install ;; uninstall) _require_sudo "$@"; cmd_uninstall ;; + build-ui) cmd_build_ui ;; install-skills) cmd_install_skills "$@" ;; uninstall-skills) cmd_uninstall_skills "$@" ;; list-skills) cmd_list_skills ;; @@ -491,6 +508,7 @@ case "${1:-help}" in echo " logs Tail live logs" echo " install Install as systemd service (auto-start on boot, auto-restart on crash)" echo " uninstall Remove the systemd service" + echo " build-ui Build the React dashboard UI (frontend/ → orchestrator/static/)" echo "" echo "Skills (powered by skills.sh):" echo " install-skills Install default skills into target repo" From d47be936fb202c5f0f583e6c4809826d602ff5cd Mon Sep 17 00:00:00 2001 From: rekpero Date: Sat, 14 Mar 2026 04:30:42 +0000 Subject: [PATCH 02/94] fix: address review comments on PR #4 Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/components/agents/AgentCard.jsx | 5 +++-- .../src/components/agents/AgentLogViewer.jsx | 2 +- frontend/src/components/modals/EnvEditor.jsx | 14 ++++++++++++-- .../modals/WorkspaceSettingsModal.jsx | 18 ++++++++++++++---- frontend/src/components/prs/PRTracker.jsx | 2 +- orchestrator/dashboard.py | 4 +++- 6 files changed, 34 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/agents/AgentCard.jsx b/frontend/src/components/agents/AgentCard.jsx index 4654177..20a17ac 100644 --- a/frontend/src/components/agents/AgentCard.jsx +++ b/frontend/src/components/agents/AgentCard.jsx @@ -20,10 +20,11 @@ function ElapsedTime({ startedAt, status }) { setElapsed(parts.join(' ')) } update() + let t if (status === 'running') { - const t = setInterval(update, 1000) - return () => clearInterval(t) + t = setInterval(update, 1000) } + return () => clearInterval(t) }, [startedAt, status]) return {elapsed} diff --git a/frontend/src/components/agents/AgentLogViewer.jsx b/frontend/src/components/agents/AgentLogViewer.jsx index d77950d..4ba5390 100644 --- a/frontend/src/components/agents/AgentLogViewer.jsx +++ b/frontend/src/components/agents/AgentLogViewer.jsx @@ -43,7 +43,7 @@ export function AgentLogViewer({ agentId, isRunning }) { const [since, setSince] = useState(0) const [allEvents, setAllEvents] = useState([]) - const { data, isLoading } = useAgentLogs(agentId, { enabled: true, since: 0 }) + const { data, isLoading } = useAgentLogs(agentId, { enabled: true, since }) useEffect(() => { if (data?.events) { diff --git a/frontend/src/components/modals/EnvEditor.jsx b/frontend/src/components/modals/EnvEditor.jsx index 36827a4..bfcdb49 100644 --- a/frontend/src/components/modals/EnvEditor.jsx +++ b/frontend/src/components/modals/EnvEditor.jsx @@ -26,6 +26,7 @@ export function EnvEditor({ workspaceId }) { const [rows, setRows] = useState([]) const [loading, setLoading] = useState(false) const [saving, setSaving] = useState(false) + const [saveError, setSaveError] = useState(null) const [pasteText, setPasteText] = useState('') const [showPaste, setShowPaste] = useState(false) @@ -58,12 +59,18 @@ export function EnvEditor({ workspaceId }) { const handleSave = async () => { if (!workspaceId) return setSaving(true) + setSaveError(null) const vars = {} for (const { key, value } of rows) { if (key.trim()) vars[key.trim()] = value } - await saveEnv(workspaceId, activeFile, vars).catch(() => {}) - setSaving(false) + try { + await saveEnv(workspaceId, activeFile, vars) + } catch (err) { + setSaveError(err?.message || 'Failed to save') + } finally { + setSaving(false) + } } const handlePaste = () => { @@ -152,6 +159,9 @@ export function EnvEditor({ workspaceId }) {
)} + {saveError && ( +

{saveError}

+ )}
+ ) } From a0ac56d0cf8f8c0dd659a49b0bd63478b7da7b6e Mon Sep 17 00:00:00 2001 From: rekpero Date: Sat, 14 Mar 2026 06:29:56 +0000 Subject: [PATCH 19/94] fix: address review comments on PR #4 --- frontend/src/components/modals/WorkspaceSettingsModal.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/components/modals/WorkspaceSettingsModal.jsx b/frontend/src/components/modals/WorkspaceSettingsModal.jsx index aeb848a..cf02298 100644 --- a/frontend/src/components/modals/WorkspaceSettingsModal.jsx +++ b/frontend/src/components/modals/WorkspaceSettingsModal.jsx @@ -35,6 +35,8 @@ export function WorkspaceSettingsModal({ open, onClose }) { useEffect(() => { if (!open) { setConfirmDelete(false) + } else { + setUpdateError(null) } }, [open]) From 87a862fa619e08749c3ef635bcab287f1a280009 Mon Sep 17 00:00:00 2001 From: rekpero Date: Sat, 14 Mar 2026 06:41:45 +0000 Subject: [PATCH 20/94] fix: address review comments on PR #4 Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/components/agents/AgentLogViewer.jsx | 2 +- frontend/src/components/modals/EnvEditor.jsx | 4 ++++ frontend/src/components/modals/WorkspaceSettingsModal.jsx | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/agents/AgentLogViewer.jsx b/frontend/src/components/agents/AgentLogViewer.jsx index 16201d4..11bc864 100644 --- a/frontend/src/components/agents/AgentLogViewer.jsx +++ b/frontend/src/components/agents/AgentLogViewer.jsx @@ -30,7 +30,7 @@ function formatEvent(event) { return `[tool] ${data.name || ''} ${data.input ? JSON.stringify(data.input).slice(0, 120) : ''}` } if (event.event_type === 'result') { - return `[result] ${data.result || JSON.stringify(data).slice(0, 120)}` + return `[result] ${data.result != null ? data.result : JSON.stringify(data).slice(0, 120)}` } return JSON.stringify(data).slice(0, 200) } catch { diff --git a/frontend/src/components/modals/EnvEditor.jsx b/frontend/src/components/modals/EnvEditor.jsx index 3102019..065f48b 100644 --- a/frontend/src/components/modals/EnvEditor.jsx +++ b/frontend/src/components/modals/EnvEditor.jsx @@ -30,6 +30,10 @@ export function EnvEditor({ workspaceId }) { const [pasteText, setPasteText] = useState('') const [showPaste, setShowPaste] = useState(false) + useEffect(() => { + setActiveFile('.env') + }, [workspaceId]) + useEffect(() => { if (!workspaceId) return getEnvFiles(workspaceId).then((data) => { diff --git a/frontend/src/components/modals/WorkspaceSettingsModal.jsx b/frontend/src/components/modals/WorkspaceSettingsModal.jsx index cf02298..ef646a1 100644 --- a/frontend/src/components/modals/WorkspaceSettingsModal.jsx +++ b/frontend/src/components/modals/WorkspaceSettingsModal.jsx @@ -30,6 +30,7 @@ export function WorkspaceSettingsModal({ open, onClose }) { } else { setForm({ name: '', repo_url: '', base_branch: '' }) } + setConfirmDelete(false) }, [workspace?.id, workspace?.name, workspace?.repo_url, workspace?.base_branch]) useEffect(() => { From ee900984c7014f59cb286e899a4fdc076fdaa7c0 Mon Sep 17 00:00:00 2001 From: rekpero Date: Sat, 14 Mar 2026 06:45:59 +0000 Subject: [PATCH 21/94] fix: address review comments on PR #4 Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/components/modals/AddWorkspaceModal.jsx | 9 ++++++++- orchestrator/dashboard.py | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/modals/AddWorkspaceModal.jsx b/frontend/src/components/modals/AddWorkspaceModal.jsx index bc4661c..8767926 100644 --- a/frontend/src/components/modals/AddWorkspaceModal.jsx +++ b/frontend/src/components/modals/AddWorkspaceModal.jsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useState, useEffect } from 'react' import { Modal } from '../ui/Modal' import { Button } from '../ui/Button' import { useCreateWorkspace } from '../../hooks/useWorkspaces' @@ -10,6 +10,13 @@ export function AddWorkspaceModal({ open, onClose }) { const [form, setForm] = useState({ repo_url: '', name: '', base_branch: 'main' }) const [error, setError] = useState('') + useEffect(() => { + if (!open) { + setForm({ repo_url: '', name: '', base_branch: 'main' }) + setError(null) + } + }, [open]) + const set = (field) => (e) => setForm((f) => ({ ...f, [field]: e.target.value })) const handleSubmit = (e) => { diff --git a/orchestrator/dashboard.py b/orchestrator/dashboard.py index fe71121..0909e33 100644 --- a/orchestrator/dashboard.py +++ b/orchestrator/dashboard.py @@ -394,6 +394,8 @@ async def get_metrics(workspace_id: str | None = Query(None)): async def spa_fallback(full_path: str): if full_path == "api" or full_path.startswith("api/"): raise HTTPException(status_code=404, detail="Not found") + if full_path.startswith("assets/") or full_path.startswith("static/"): + raise HTTPException(status_code=404) index = STATIC_DIR / "index.html" if not index.exists(): raise HTTPException(status_code=404, detail="Frontend not built") From 253711a12fccc802d9f97c25748ed3bec525ef05 Mon Sep 17 00:00:00 2001 From: rekpero Date: Sat, 14 Mar 2026 06:52:46 +0000 Subject: [PATCH 22/94] fix: address review comments on PR #4 --- frontend/src/components/modals/EnvEditor.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/src/components/modals/EnvEditor.jsx b/frontend/src/components/modals/EnvEditor.jsx index 065f48b..f9badf4 100644 --- a/frontend/src/components/modals/EnvEditor.jsx +++ b/frontend/src/components/modals/EnvEditor.jsx @@ -110,6 +110,9 @@ export function EnvEditor({ workspaceId }) { return Array.from(existingMap.values()) }) } + reader.onerror = () => { + setSaveError('Failed to read file') + } reader.readAsText(file) e.target.value = '' } From 18a2617982b3a1b4ec54e4e7161dfa7f878cd379 Mon Sep 17 00:00:00 2001 From: rekpero Date: Sat, 14 Mar 2026 06:57:28 +0000 Subject: [PATCH 23/94] fix: address review comments on PR #4 Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/components/agents/AgentLogViewer.jsx | 6 +++++- frontend/src/components/modals/AddWorkspaceModal.jsx | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/agents/AgentLogViewer.jsx b/frontend/src/components/agents/AgentLogViewer.jsx index 11bc864..27f20a6 100644 --- a/frontend/src/components/agents/AgentLogViewer.jsx +++ b/frontend/src/components/agents/AgentLogViewer.jsx @@ -23,7 +23,11 @@ function formatEvent(event) { if (event.event_type === 'assistant') { const content = data.message?.content if (Array.isArray(content)) { - return content.map((c) => c.text || c.input && JSON.stringify(c.input) || '').filter(Boolean).join(' ') + return content.map((c) => { + if (c.text != null) return c.text + if (c.input != null) return JSON.stringify(c.input) + return '' + }).filter(Boolean).join(' ') } } if (event.event_type === 'tool_use') { diff --git a/frontend/src/components/modals/AddWorkspaceModal.jsx b/frontend/src/components/modals/AddWorkspaceModal.jsx index 8767926..83daf7f 100644 --- a/frontend/src/components/modals/AddWorkspaceModal.jsx +++ b/frontend/src/components/modals/AddWorkspaceModal.jsx @@ -13,7 +13,7 @@ export function AddWorkspaceModal({ open, onClose }) { useEffect(() => { if (!open) { setForm({ repo_url: '', name: '', base_branch: 'main' }) - setError(null) + setError('') } }, [open]) From faeeab316315f75814e1c5c278b9d9f614f93aa8 Mon Sep 17 00:00:00 2001 From: rekpero Date: Sat, 14 Mar 2026 07:05:52 +0000 Subject: [PATCH 24/94] fix: address review comments on PR #4 - useAgents.js: capture cursor snapshot in queryFn to eliminate race condition when concurrent polls share the same cursorRef value - dashboard.py: conditionally mount /assets only when directory exists, logging a warning instead of crashing with RuntimeError at request time - EnvEditor.jsx: introduce separate fileReadError state so file-read errors are not silently cleared by save actions; preserve existing row ids when merging on matching keys in handlePaste and handleFileUpload to avoid unnecessary unmount/remount of matched rows Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/components/modals/EnvEditor.jsx | 23 +++++++++++++++++--- frontend/src/hooks/useAgents.js | 5 ++++- orchestrator/dashboard.py | 13 +++++++++-- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/modals/EnvEditor.jsx b/frontend/src/components/modals/EnvEditor.jsx index f9badf4..45972d1 100644 --- a/frontend/src/components/modals/EnvEditor.jsx +++ b/frontend/src/components/modals/EnvEditor.jsx @@ -27,6 +27,7 @@ export function EnvEditor({ workspaceId }) { const [loading, setLoading] = useState(false) const [saving, setSaving] = useState(false) const [saveError, setSaveError] = useState(null) + const [fileReadError, setFileReadError] = useState(null) const [pasteText, setPasteText] = useState('') const [showPaste, setShowPaste] = useState(false) @@ -90,7 +91,13 @@ export function EnvEditor({ workspaceId }) { const newRows = Object.entries(parsed).map(([k, v]) => ({ id: crypto.randomUUID(), key: k, value: v })) setRows((prev) => { const existingMap = new Map(prev.map((r) => [r.key, r])) - newRows.forEach((r) => existingMap.set(r.key, r)) + newRows.forEach((r) => { + if (existingMap.has(r.key)) { + existingMap.set(r.key, { ...existingMap.get(r.key), value: r.value }) + } else { + existingMap.set(r.key, r) + } + }) return Array.from(existingMap.values()) }) setPasteText('') @@ -102,16 +109,23 @@ export function EnvEditor({ workspaceId }) { if (!file) return const reader = new FileReader() reader.onload = (ev) => { + setFileReadError(null) const parsed = parseEnvText(ev.target.result) const newRows = Object.entries(parsed).map(([k, v]) => ({ id: crypto.randomUUID(), key: k, value: v })) setRows((prev) => { const existingMap = new Map(prev.map((r) => [r.key, r])) - newRows.forEach((r) => existingMap.set(r.key, r)) + newRows.forEach((r) => { + if (existingMap.has(r.key)) { + existingMap.set(r.key, { ...existingMap.get(r.key), value: r.value }) + } else { + existingMap.set(r.key, r) + } + }) return Array.from(existingMap.values()) }) } reader.onerror = () => { - setSaveError('Failed to read file') + setFileReadError('Failed to read file') } reader.readAsText(file) e.target.value = '' @@ -181,6 +195,9 @@ export function EnvEditor({ workspaceId }) {
)} + {fileReadError && ( +

{fileReadError}

+ )} {saveError && (

{saveError}

)} diff --git a/frontend/src/hooks/useAgents.js b/frontend/src/hooks/useAgents.js index f3b3fae..6fbb1eb 100644 --- a/frontend/src/hooks/useAgents.js +++ b/frontend/src/hooks/useAgents.js @@ -33,7 +33,10 @@ export function useAgentLogs(agentId, { since = 0, refetchInterval = 3000 } = {} const query = useQuery({ queryKey: ['agent-logs', agentId], - queryFn: () => getAgentLogs(agentId, cursorRef.current), + queryFn: () => { + const cursor = cursorRef.current + return getAgentLogs(agentId, cursor) + }, refetchInterval, staleTime: 0, }) diff --git a/orchestrator/dashboard.py b/orchestrator/dashboard.py index 0909e33..0645b64 100644 --- a/orchestrator/dashboard.py +++ b/orchestrator/dashboard.py @@ -1,6 +1,7 @@ """FastAPI dashboard server for the swarm orchestrator.""" import json +import logging import uuid from pathlib import Path @@ -384,8 +385,16 @@ async def get_metrics(workspace_id: str | None = Query(None)): app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static") # Serve Vite-built assets at /assets/ (index.html references them with root-relative /assets/... paths) -# check_dir=False prevents a RuntimeError at startup when the frontend hasn't been built yet -app.mount("/assets", StaticFiles(directory=str(STATIC_DIR / "assets"), check_dir=False), name="assets") +# Only mount if the directory exists; otherwise log a warning and let the spa_fallback return 404. +_assets_dir = STATIC_DIR / "assets" +if _assets_dir.exists(): + app.mount("/assets", StaticFiles(directory=str(_assets_dir)), name="assets") +else: + logging.getLogger(__name__).warning( + "Assets directory '%s' not found — frontend build may be missing. " + "Requests to /assets/... will return 404.", + _assets_dir, + ) # SPA catch-all — must be registered LAST, after all /api/* routes and static mount. From ab513ac8ae1b6771eba5ecee711a3c9748c253c5 Mon Sep 17 00:00:00 2001 From: Mitra Date: Sat, 14 Mar 2026 15:12:20 +0000 Subject: [PATCH 25/94] fix: UI overhaul, planner improvements, and PR/issue status sorting Redesign frontend components with updated tokens and styling across all views. Add PlannerModal with support for creating issues from specific assistant messages. Enrich PR statuses from issue state (merged/needs_human) and sort PRs and issues by status priority. Sort agents with running instances first. Build UI on restart. Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/index.html | 5 +- frontend/src/App.jsx | 20 +- frontend/src/api/client.js | 7 +- .../src/components/agents/ActiveAgents.jsx | 24 +- frontend/src/components/agents/AgentCard.jsx | 80 ++- .../src/components/agents/AgentLogViewer.jsx | 142 ++-- .../components/agents/AgentStatusBadge.jsx | 4 +- frontend/src/components/issues/IssueQueue.jsx | 74 ++- frontend/src/components/layout/Header.jsx | 41 +- frontend/src/components/layout/TabNav.jsx | 17 +- .../components/layout/WorkspaceSwitcher.jsx | 42 +- .../src/components/metrics/MetricCard.jsx | 21 +- .../src/components/metrics/MetricsBar.jsx | 16 +- .../components/modals/AddWorkspaceModal.jsx | 37 +- frontend/src/components/modals/EnvEditor.jsx | 126 ++-- .../modals/WorkspaceSettingsModal.jsx | 172 ++++- .../src/components/planner/PlannerModal.jsx | 608 ++++++++++++++++++ frontend/src/components/prs/PRTracker.jsx | 33 +- frontend/src/components/prs/ReviewThreads.jsx | 145 ++++- frontend/src/components/ui/Badge.jsx | 14 +- frontend/src/components/ui/Button.jsx | 12 +- frontend/src/components/ui/Card.jsx | 6 +- frontend/src/components/ui/EmptyState.jsx | 10 +- frontend/src/components/ui/Modal.jsx | 12 +- frontend/src/hooks/usePlanning.js | 285 ++++++++ frontend/src/index.css | 80 ++- frontend/src/tokens.css | 31 +- frontend/tailwind.config.js | 7 +- orchestrator/dashboard.py | 28 +- orchestrator/db.py | 14 +- orchestrator/planner.py | 38 +- orchestrator/static/assets/index-CBLLoVu6.js | 151 ----- orchestrator/static/assets/index-Cq8fv3JE.js | 187 ++++++ orchestrator/static/assets/index-DnzmWivT.css | 1 + orchestrator/static/assets/index-Z2XrvsZb.css | 1 - orchestrator/static/index.html | 9 +- run.sh | 1 + 37 files changed, 1969 insertions(+), 532 deletions(-) create mode 100644 frontend/src/components/planner/PlannerModal.jsx create mode 100644 frontend/src/hooks/usePlanning.js delete mode 100644 orchestrator/static/assets/index-CBLLoVu6.js create mode 100644 orchestrator/static/assets/index-Cq8fv3JE.js create mode 100644 orchestrator/static/assets/index-DnzmWivT.css delete mode 100644 orchestrator/static/assets/index-Z2XrvsZb.css diff --git a/frontend/index.html b/frontend/index.html index ae962dd..f0a7be6 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,7 +3,10 @@ - Claude Code Swarm Dashboard + Claude Code Swarm + + +
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 22e4ffd..31c9380 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -7,6 +7,7 @@ import { IssueQueue } from './components/issues/IssueQueue' import { PRTracker } from './components/prs/PRTracker' import { AddWorkspaceModal } from './components/modals/AddWorkspaceModal' import { WorkspaceSettingsModal } from './components/modals/WorkspaceSettingsModal' +import { PlannerModal } from './components/planner/PlannerModal' import { useMetrics } from './hooks/useMetrics' import { useAgents } from './hooks/useAgents' import { useIssues } from './hooks/useIssues' @@ -16,8 +17,8 @@ import { useWorkspaceContext } from './context/WorkspaceContext' function ErrorBanner({ error }) { if (!error) return null return ( -
- Cannot connect to backend — {error.message} +
+ Cannot connect to backend \u2014 {error.message}
) } @@ -26,6 +27,7 @@ export function App() { const [activeTab, setActiveTab] = useState('agents') const [addWorkspaceOpen, setAddWorkspaceOpen] = useState(false) const [settingsOpen, setSettingsOpen] = useState(false) + const [plannerOpen, setPlannerOpen] = useState(false) const { selectedWorkspaceId } = useWorkspaceContext() const { error: metricsError } = useMetrics(selectedWorkspaceId) @@ -44,19 +46,27 @@ export function App() {
setAddWorkspaceOpen(true)} onOpenSettings={() => setSettingsOpen(true)} + onOpenPlanner={() => setPlannerOpen(true)} />
- {activeTab === 'agents' && } - {activeTab === 'issues' && } - {activeTab === 'prs' && } +
+ +
+
+ +
+
+ +
setAddWorkspaceOpen(false)} /> setSettingsOpen(false)} /> + setPlannerOpen(false)} />
) } diff --git a/frontend/src/api/client.js b/frontend/src/api/client.js index e23e933..341c394 100644 --- a/frontend/src/api/client.js +++ b/frontend/src/api/client.js @@ -49,6 +49,9 @@ export const updateWorkspace = (id, data) => export const deleteWorkspace = (id) => apiFetch(`/api/workspaces/${id}`, { method: 'DELETE' }) +export const getWorkspaceStructure = (wsId) => + apiFetch(`/api/workspaces/${wsId}/structure`) + // Env files export const getEnvFiles = (wsId) => apiFetch(`/api/workspaces/${wsId}/env-files`) @@ -88,10 +91,10 @@ export const refinePlan = (sessionId, message) => body: JSON.stringify({ message }), }) -export const createIssueFromPlan = (sessionId, title = '') => +export const createIssueFromPlan = (sessionId, title = '', messageIndex = null) => apiFetch(`/api/planning/${sessionId}/create-issue`, { method: 'POST', - body: JSON.stringify({ title }), + body: JSON.stringify({ title, message_index: messageIndex }), }) export const cancelPlanning = (sessionId) => diff --git a/frontend/src/components/agents/ActiveAgents.jsx b/frontend/src/components/agents/ActiveAgents.jsx index b439407..2e6f10a 100644 --- a/frontend/src/components/agents/ActiveAgents.jsx +++ b/frontend/src/components/agents/ActiveAgents.jsx @@ -6,12 +6,16 @@ import { Button } from '../ui/Button' import { Spinner } from '../ui/Spinner' import { useAgents } from '../../hooks/useAgents' import { useWorkspaceContext } from '../../context/WorkspaceContext' +import { useWorkspaces } from '../../hooks/useWorkspaces' const PAGE_SIZE = 20 export function ActiveAgents() { const [offset, setOffset] = useState(0) const { selectedWorkspaceId } = useWorkspaceContext() + const { data: wsData } = useWorkspaces() + const wsMap = Object.fromEntries((wsData?.workspaces || []).map(w => [w.id, w.name || w.repo_url])) + const showWorkspace = !selectedWorkspaceId useEffect(() => { setOffset(0) }, [selectedWorkspaceId]) const { data, isLoading } = useAgents(selectedWorkspaceId, { limit: PAGE_SIZE, offset }) @@ -22,23 +26,23 @@ export function ActiveAgents() { const currentPage = Math.floor(offset / PAGE_SIZE) + 1 return ( -
-
-

+
+
+

Active Agents - {total > 0 && ({total})} + {total > 0 && ({total})}

{totalPages > 1 && ( -
+
- + {currentPage} / {totalPages}
)}
{isLoading ? ( -
+
) : agents.length === 0 ? ( @@ -62,7 +66,7 @@ export function ActiveAgents() { ) : (
{agents.map((agent) => ( - + ))}
)} diff --git a/frontend/src/components/agents/AgentCard.jsx b/frontend/src/components/agents/AgentCard.jsx index d6878ed..b0b690c 100644 --- a/frontend/src/components/agents/AgentCard.jsx +++ b/frontend/src/components/agents/AgentCard.jsx @@ -1,9 +1,14 @@ import { useState, useEffect } from 'react' -import { ChevronDown, ChevronUp, ExternalLink } from 'lucide-react' +import { ChevronDown, ChevronUp, Code, MessageSquare } from 'lucide-react' import { AgentStatusBadge } from './AgentStatusBadge' import { AgentLogViewer } from './AgentLogViewer' import { formatDuration, intervalToDuration } from 'date-fns' +const AGENT_TYPE_META = { + implement: { label: 'Implementing Issue', icon: Code, color: 'text-[var(--accent)]' }, + fix_review: { label: 'Fixing PR Review', icon: MessageSquare, color: 'text-[var(--blue)]' }, +} + function ElapsedTime({ startedAt, status }) { const [elapsed, setElapsed] = useState('') @@ -27,67 +32,86 @@ function ElapsedTime({ startedAt, status }) { return () => clearInterval(t) }, [startedAt, status]) - return {elapsed} + return {elapsed} } -export function AgentCard({ agent }) { - const [expanded, setExpanded] = useState(false) +export function AgentCard({ agent, workspaceName }) { const isRunning = agent.status === 'running' + const [expanded, setExpanded] = useState(isRunning) return (
- {/* Top bar accent for running agents */} + {/* Running accent bar */} {isRunning && ( -
+
)}
setExpanded((v) => !v)} >
+ {(() => { + const meta = AGENT_TYPE_META[agent.agent_type] + const TypeIcon = meta?.icon + return meta ? ( + + {TypeIcon && } + {meta.label} + + ) : ( + + {agent.agent_type || 'agent'} + + ) + })()} {agent.issue_number && ( - { e.preventDefault(); e.stopPropagation() }} - className="text-[var(--blue)] text-xs hover:underline" - > - #{agent.issue_number} - + + issue #{agent.issue_number} + + )} + {agent.pr_number && ( + + PR #{agent.pr_number} + )} - - {agent.branch || `agent-${agent.agent_id?.slice(0, 8)}`} -
-
- {agent.agent_id} +
+ + {agent.branch_name || agent.agent_id} + + {workspaceName && ( + + {workspaceName} + + )}
- + {agent.turns_used ?? 0} {agent.max_turns ? `/${agent.max_turns}` : ''} turns -
- {expanded ? : } +
+ {expanded ? : }
- {expanded && ( -
- -
- )} +
+ +
) } diff --git a/frontend/src/components/agents/AgentLogViewer.jsx b/frontend/src/components/agents/AgentLogViewer.jsx index 27f20a6..62f3378 100644 --- a/frontend/src/components/agents/AgentLogViewer.jsx +++ b/frontend/src/components/agents/AgentLogViewer.jsx @@ -4,56 +4,97 @@ import { Spinner } from '../ui/Spinner' const MAX_DISPLAY = 500 -function eventStyle(eventType) { +function eventTypeClass(eventType) { switch (eventType) { - case 'assistant': return 'text-[var(--text)]' - case 'tool_use': return 'text-[var(--text-dim)]' - case 'tool_result': return 'text-[var(--blue)]' + case 'assistant': return 'text-[var(--accent)]' + case 'tool_use': return 'text-[var(--yellow)]' + case 'tool_result': return 'text-[var(--text-muted)]' + case 'result': return 'text-[var(--green)]' case 'error': return 'text-[var(--red)]' - case 'result': return 'text-[var(--accent)]' - default: return 'text-[var(--text-dim)]' + case 'system': return 'text-[var(--blue)]' + case 'user': return 'text-[var(--text-muted)]' + case 'rate_limit_event': return 'text-[var(--yellow)]' + default: return 'text-[var(--text-muted)]' } } -function formatEvent(event) { +function formatLogTime(ts) { + if (!ts) return '' try { - const data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data - if (!data) return event.event_type + const d = new Date(ts) + return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }) + } catch { + return '' + } +} + +function formatToolUse(b) { + const tool = b.name || 'unknown' + const input = b.input || {} + if (tool === 'Bash') return `$ ${(input.command || '').substring(0, 120)}` + if (tool === 'Read') return `Read ${input.file_path || '?'}` + if (tool === 'Edit' || tool === 'Write') return `${tool} ${input.file_path || '?'}` + if (tool === 'Grep') return `Grep "${input.pattern || ''}"` + if (tool === 'Glob') return `Glob ${input.pattern || ''}` + if (tool === 'Skill') return `Skill: ${input.skill || '?'}` + return `${tool}` +} - if (event.event_type === 'assistant') { - const content = data.message?.content - if (Array.isArray(content)) { - return content.map((c) => { - if (c.text != null) return c.text - if (c.input != null) return JSON.stringify(c.input) - return '' - }).filter(Boolean).join(' ') +function tryParseEventData(raw, eventType) { + try { + const d = typeof raw === 'string' ? JSON.parse(raw) : raw + if (eventType === 'assistant' || d?.type === 'assistant') { + const blocks = d.message?.content || [] + const parts = [] + for (const b of blocks) { + if (b.type === 'text' && b.text) parts.push(b.text) + else if (b.type === 'tool_use') parts.push(formatToolUse(b)) + else if (b.type === 'thinking' && b.thinking) parts.push('(thinking) ' + b.thinking) + else if (typeof b === 'string') parts.push(b) } + return parts.join(' ') || null + } + if (eventType === 'user' || d?.type === 'user') return null + if (eventType === 'tool_use' || d?.type === 'tool_use') { + const tool = d.tool || d.name || 'unknown' + const input = d.input || {} + if (tool === 'Bash') return `$ ${input.command || ''}` + if (tool === 'Read') return `Read ${input.file_path || '?'}` + if (tool === 'Edit' || tool === 'Write') return `${tool} ${input.file_path || '?'}` + if (tool === 'Grep') return `Grep "${input.pattern || ''}"` + if (tool === 'Glob') return `Glob ${input.pattern || ''}` + return `${tool}: ${JSON.stringify(input)}` } - if (event.event_type === 'tool_use') { - return `[tool] ${data.name || ''} ${data.input ? JSON.stringify(data.input).slice(0, 120) : ''}` + if (eventType === 'tool_result' || d?.type === 'tool_result') return null + if (eventType === 'result' || d?.type === 'result') { + const r = d.result + if (typeof r === 'string') return r + if (r && typeof r === 'object') return JSON.stringify(r) + return 'Agent finished' } - if (event.event_type === 'result') { - return `[result] ${data.result != null ? data.result : JSON.stringify(data).slice(0, 120)}` + if (eventType === 'error' || d?.type === 'error') { + const err = d.error + if (typeof err === 'string') return err + if (err && err.message) return err.message + return 'Error occurred' } - return JSON.stringify(data).slice(0, 200) + if (eventType === 'system' || d?.type === 'system') { + if (d.subtype === 'init') return `Session started in ${d.cwd || '?'}` + return d.message || d.text || null + } + if (eventType === 'rate_limit_event') return 'Rate limit event' + return null } catch { - return event.event_type + return raw || null } } export function AgentLogViewer({ agentId, isRunning }) { - const bottomRef = useRef(null) + const containerRef = useRef(null) const [allEvents, setAllEvents] = useState([]) const { data, isLoading, cursorRef } = useAgentLogs(agentId, { refetchInterval: isRunning ? 3000 : false }) - // Declared before the agentId effect so it runs first in the same effects flush. - // When the user switches agents, TanStack Query immediately returns stale cached - // data for the new agent (staleTime=0), causing both effects to fire together. - // By running this effect first, it may advance cursorRef to the stale last-event - // ID — but the agentId effect below always resets cursorRef.current = 0 afterward, - // so the last write wins before the background refetch's queryFn fires. useEffect(() => { if (data?.events?.length > 0) { setAllEvents(prev => { @@ -67,16 +108,17 @@ export function AgentLogViewer({ agentId, isRunning }) { useEffect(() => { setAllEvents([]) - // Reset cursor here (in addition to the render-body guard in useAgentLogs) so - // this effect runs after the data effect above in the same flush, guaranteeing - // cursorRef.current === 0 when the background refetch's queryFn fires. cursorRef.current = 0 }, [agentId]) - // Auto-scroll only for running agents + // Only auto-scroll if the user is already near the bottom (within 50px). + // This prevents hijacking the scroll when the user is reading earlier logs. useEffect(() => { - if (isRunning && bottomRef.current) { - bottomRef.current.scrollIntoView({ behavior: 'smooth' }) + const el = containerRef.current + if (!isRunning || !el) return + const nearBottom = (el.scrollHeight - el.scrollTop - el.clientHeight) < 50 + if (nearBottom) { + el.scrollTop = el.scrollHeight } }, [allEvents, isRunning]) @@ -85,24 +127,34 @@ export function AgentLogViewer({ agentId, isRunning }) { if (isLoading && allEvents.length === 0) { return (
- +
) } + const formattedEvents = displayEvents + .map((event) => { + const summary = tryParseEventData(event.event_data, event.event_type) + if (!summary) return null + return { ...event, summary } + }) + .filter(Boolean) + return ( -
- {displayEvents.length === 0 ? ( - No log events yet... +
+ {formattedEvents.length === 0 ? ( + Waiting for events... ) : ( - displayEvents.map((event) => ( -
- - {formatEvent(event)} + formattedEvents.map((event) => ( +
+ {formatLogTime(event.timestamp)} + + {event.event_type} + + {event.summary}
)) )} -
) } diff --git a/frontend/src/components/agents/AgentStatusBadge.jsx b/frontend/src/components/agents/AgentStatusBadge.jsx index 751db2f..18c618b 100644 --- a/frontend/src/components/agents/AgentStatusBadge.jsx +++ b/frontend/src/components/agents/AgentStatusBadge.jsx @@ -1,8 +1,8 @@ import { Badge } from '../ui/Badge' const STATUS_MAP = { - running: { variant: 'green', label: 'Running' }, - completed: { variant: 'blue', label: 'Completed' }, + running: { variant: 'purple', label: 'Running' }, + completed: { variant: 'green', label: 'Completed' }, failed: { variant: 'red', label: 'Failed' }, rate_limited: { variant: 'yellow', label: 'Rate Limited' }, timed_out: { variant: 'red', label: 'Timed Out' }, diff --git a/frontend/src/components/issues/IssueQueue.jsx b/frontend/src/components/issues/IssueQueue.jsx index 83b337a..47af51d 100644 --- a/frontend/src/components/issues/IssueQueue.jsx +++ b/frontend/src/components/issues/IssueQueue.jsx @@ -6,44 +6,51 @@ import { Button } from '../ui/Button' import { Spinner } from '../ui/Spinner' import { useIssues, useUpdateIssueStatus } from '../../hooks/useIssues' import { useWorkspaceContext } from '../../context/WorkspaceContext' +import { useWorkspaces } from '../../hooks/useWorkspaces' import { formatDistanceToNow } from 'date-fns' export function IssueQueue() { const { selectedWorkspaceId } = useWorkspaceContext() const { data, isLoading } = useIssues(selectedWorkspaceId) const { mutate: updateStatus } = useUpdateIssueStatus(selectedWorkspaceId) + const { data: wsData } = useWorkspaces() + const workspaces = wsData?.workspaces || [] + const wsMap = Object.fromEntries(workspaces.map(w => [w.id, w.name || w.repo_url])) + const wsRepoMap = Object.fromEntries(workspaces.map(w => [w.id, w.github_repo])) + const showWorkspace = !selectedWorkspaceId const [retryingIssue, setRetryingIssue] = useState(null) const issues = data?.issues || [] return ( -
-
-

+
+
+

Issue Queue {issues.length > 0 && ( - ({issues.length}) + ({issues.length}) )}

{isLoading ? ( -
+
) : issues.length === 0 ? ( ) : ( -
- +
+
- - - - - - - + + + + {showWorkspace && } + + + + @@ -51,48 +58,55 @@ export function IssueQueue() { {issues.map((issue) => ( - - - + )} + - - - -
#TitleStatusAttemptsPRUpdated
#TitleWorkspaceStatusTriesPRUpdated
+ {issue.issue_number} + {issue.title || `Issue #${issue.issue_number}`} - + + {showWorkspace && ( + + + {wsMap[issue.workspace_id] || '\u2014'} + + + {issue.attempts ?? 0} + {issue.pr_number ? ( #{issue.pr_number} ) : ( - + \u2014 )} + {issue.updated_at ? formatDistanceToNow(new Date(issue.updated_at), { addSuffix: true }) - : '—'} + : '\u2014'} + {issue.status === 'needs_human' && ( )} diff --git a/frontend/src/components/layout/Header.jsx b/frontend/src/components/layout/Header.jsx index 2522f78..2656964 100644 --- a/frontend/src/components/layout/Header.jsx +++ b/frontend/src/components/layout/Header.jsx @@ -4,7 +4,7 @@ import { useMetrics } from '../../hooks/useMetrics' import { useWorkspaceContext } from '../../context/WorkspaceContext' import { formatDistanceToNow } from 'date-fns' -export function Header({ onAddWorkspace, onOpenSettings }) { +export function Header({ onAddWorkspace, onOpenSettings, onOpenPlanner }) { const { selectedWorkspaceId } = useWorkspaceContext() const { dataUpdatedAt } = useMetrics(selectedWorkspaceId) @@ -13,29 +13,42 @@ export function Header({ onAddWorkspace, onOpenSettings }) { : null return ( -
-
+
+
-

Claude Code Swarm

+
+
+

Claude Code Swarm

+
-
+
{lastUpdated && ( - Updated {lastUpdated} + + {lastUpdated} + )} + {selectedWorkspaceId && ( + + )}
) diff --git a/frontend/src/components/layout/TabNav.jsx b/frontend/src/components/layout/TabNav.jsx index 055a457..f3d1511 100644 --- a/frontend/src/components/layout/TabNav.jsx +++ b/frontend/src/components/layout/TabNav.jsx @@ -6,7 +6,7 @@ const TABS = [ export function TabNav({ activeTab, onTabChange, counts = {} }) { return ( -
+
{TABS.map((tab) => { const count = counts[tab.id] const isActive = activeTab === tab.id @@ -14,24 +14,27 @@ export function TabNav({ activeTab, onTabChange, counts = {} }) { ) })} diff --git a/frontend/src/components/layout/WorkspaceSwitcher.jsx b/frontend/src/components/layout/WorkspaceSwitcher.jsx index 73d384a..e129ab7 100644 --- a/frontend/src/components/layout/WorkspaceSwitcher.jsx +++ b/frontend/src/components/layout/WorkspaceSwitcher.jsx @@ -4,12 +4,13 @@ import { useWorkspaces } from '../../hooks/useWorkspaces' import { useWorkspaceContext } from '../../context/WorkspaceContext' function StatusDot({ status }) { - const color = status === 'active' ? 'var(--green)' : status === 'cloning' ? 'var(--yellow)' : 'var(--red)' + const colors = { + active: 'bg-[var(--green)] shadow-[0_0_4px_var(--green)]', + cloning: 'bg-[var(--yellow)] shadow-[0_0_4px_var(--yellow)]', + error: 'bg-[var(--red)] shadow-[0_0_4px_var(--red)]', + } return ( - + ) } @@ -30,7 +31,6 @@ export function WorkspaceSwitcher({ onAddWorkspace }) { return () => document.removeEventListener('mousedown', handler) }, []) - // If selected workspace was deleted, fall back to null useEffect(() => { if (selectedWorkspaceId && workspaces.length > 0 && !selected) { setSelectedWorkspaceId(null) @@ -41,56 +41,56 @@ export function WorkspaceSwitcher({ onAddWorkspace }) {
{open && ( -
+
{ setSelectedWorkspaceId(null); setOpen(false) }} > - - All Workspaces + + All Workspaces
{workspaces.length > 0 && ( -
+
)} {workspaces.map((ws) => (
{ setSelectedWorkspaceId(ws.id); setOpen(false) }} > - {ws.name || ws.repo_url} - + {ws.name || ws.repo_url} + {ws.repo_url?.replace(/^https?:\/\/github\.com\//, '')}
))} -
+
{ onAddWorkspace?.(); setOpen(false) }} > - + Add Workspace
diff --git a/frontend/src/components/metrics/MetricCard.jsx b/frontend/src/components/metrics/MetricCard.jsx index 232f7af..948ce4b 100644 --- a/frontend/src/components/metrics/MetricCard.jsx +++ b/frontend/src/components/metrics/MetricCard.jsx @@ -7,21 +7,30 @@ export function MetricCard({ label, value, color }) { accent: 'var(--accent)', text: 'var(--text)', } + const dimMap = { + green: 'var(--green-dim)', + blue: 'var(--blue-dim)', + yellow: 'var(--yellow-dim)', + red: 'var(--red-dim)', + accent: 'var(--accent-dim)', + text: 'rgba(220,223,232,0.04)', + } const c = colorMap[color] || 'var(--text)' + const bg = dimMap[color] || 'rgba(220,223,232,0.04)' return ( -
+
- {value ?? '—'} + {value ?? '\u2014'} - + {label}
diff --git a/frontend/src/components/metrics/MetricsBar.jsx b/frontend/src/components/metrics/MetricsBar.jsx index 1abb2bf..5f4398b 100644 --- a/frontend/src/components/metrics/MetricsBar.jsx +++ b/frontend/src/components/metrics/MetricsBar.jsx @@ -8,17 +8,17 @@ export function MetricsBar() { const m = data || {} const cards = [ - { label: 'Resolved', value: isLoading ? '—' : (m.resolved ?? 0), color: 'green' }, - { label: 'In Queue', value: isLoading ? '—' : (m.pending ?? 0), color: 'text' }, - { label: 'In Progress', value: isLoading ? '—' : (m.in_progress ?? 0), color: 'accent' }, - { label: 'PRs Open', value: isLoading ? '—' : (m.prs_open ?? 0), color: 'blue' }, - { label: 'Needs Human', value: isLoading ? '—' : (m.needs_human ?? 0), color: 'red' }, - { label: 'Rate Limited', value: isLoading ? '—' : (m.rate_limited ?? 0), color: 'yellow' }, - { label: 'Avg Turns', value: isLoading ? '—' : (m.avg_turns != null ? m.avg_turns.toFixed(1) : '0.0'), color: 'text' }, + { label: 'Resolved', value: isLoading ? '\u2014' : (m.resolved ?? 0), color: 'green' }, + { label: 'In Queue', value: isLoading ? '\u2014' : (m.pending ?? 0), color: 'text' }, + { label: 'In Progress', value: isLoading ? '\u2014' : (m.in_progress ?? 0), color: 'accent' }, + { label: 'PRs Open', value: isLoading ? '\u2014' : (m.prs_open ?? 0), color: 'blue' }, + { label: 'Needs Human', value: isLoading ? '\u2014' : (m.needs_human ?? 0), color: 'red' }, + { label: 'Rate Limited', value: isLoading ? '\u2014' : (m.rate_limited ?? 0), color: 'yellow' }, + { label: 'Avg Turns', value: isLoading ? '\u2014' : (m.avg_turns != null ? m.avg_turns.toFixed(1) : '0.0'), color: 'text' }, ] return ( -
+
{cards.map((card) => ( ))} diff --git a/frontend/src/components/modals/AddWorkspaceModal.jsx b/frontend/src/components/modals/AddWorkspaceModal.jsx index 83daf7f..2134e6d 100644 --- a/frontend/src/components/modals/AddWorkspaceModal.jsx +++ b/frontend/src/components/modals/AddWorkspaceModal.jsx @@ -19,6 +19,8 @@ export function AddWorkspaceModal({ open, onClose }) { const set = (field) => (e) => setForm((f) => ({ ...f, [field]: e.target.value })) + const inputClass = 'w-full px-3 py-2 text-[11px] bg-[var(--bg)] border border-[var(--border)] rounded-md text-[var(--text)] font-mono focus:border-[var(--accent)] outline-none placeholder:text-[var(--text-muted)] transition-colors' + const handleSubmit = (e) => { e.preventDefault() setError('') @@ -41,42 +43,27 @@ export function AddWorkspaceModal({ open, onClose }) { return ( -
+
- - + +
- - + +
- - + +
- {error &&

{error}

} + {error &&

{error}

} {isPending && ( -

Cloning repository...

+

Cloning repository...

)} -
+
- {/* Rows */} -
+
{rows.map((row, i) => (
- setRow(i, 'key', e.target.value)} - placeholder="KEY" - className="flex-1 px-2 py-1.5 text-[11px] bg-[var(--bg)] border border-[var(--border)] rounded text-[var(--text)] font-mono focus:border-[var(--accent)] outline-none" - /> - setRow(i, 'value', e.target.value)} - placeholder="value" - className="flex-[2] px-2 py-1.5 text-[11px] bg-[var(--bg)] border border-[var(--border)] rounded text-[var(--text)] font-mono focus:border-[var(--accent)] outline-none" - /> -
))}
{showPaste && ( -
+