From 0b0b1e708bfe5e45adc70ab6fd6459e8f9079031 Mon Sep 17 00:00:00 2001 From: hhftechnologies Date: Mon, 1 Dec 2025 18:32:52 +0530 Subject: [PATCH] bug-fixes-optimizations --- package-lock.json | 7 - pnpm-lock.yaml | 4823 +++++++++++++++++ src/components/Header.tsx | 1 + src/components/SidebarUI.tsx | 50 +- .../compose-builder/NetworkForm.tsx | 185 + .../compose-builder/ServiceForm.tsx | 1395 +++++ .../compose-builder/ServiceListSidebar.tsx | 152 + src/components/compose-builder/VolumeForm.tsx | 193 + .../compose-builder/VpnConfigSection.tsx | 771 +++ src/components/templates/TemplateCard.tsx | 71 +- .../templates/TemplateStoreModal.tsx | 243 +- src/components/ui/sidebar.tsx | 43 +- src/hooks/use-search-params.ts | 26 + src/hooks/useNetworkVolumeManager.ts | 21 + src/hooks/useSelectionState.ts | 64 + src/hooks/useServiceUpdater.ts | 398 ++ src/hooks/useTemplateStore.ts | 202 + src/hooks/useVpnConfig.ts | 202 + src/routes/config-builder.tsx | 4 +- src/routes/docker/compose-builder.tsx | 4119 ++------------ src/routes/scheduler-builder.tsx | 4 +- src/types/vpn-configs.ts | 2 + src/utils/default-configs.ts | 1 + src/utils/template-import.ts | 388 ++ src/utils/vpn-generator.ts | 39 +- src/utils/yaml-generator.ts | 13 +- 26 files changed, 9553 insertions(+), 3864 deletions(-) create mode 100644 pnpm-lock.yaml create mode 100644 src/components/compose-builder/NetworkForm.tsx create mode 100644 src/components/compose-builder/ServiceForm.tsx create mode 100644 src/components/compose-builder/ServiceListSidebar.tsx create mode 100644 src/components/compose-builder/VolumeForm.tsx create mode 100644 src/components/compose-builder/VpnConfigSection.tsx create mode 100644 src/hooks/use-search-params.ts create mode 100644 src/hooks/useSelectionState.ts create mode 100644 src/hooks/useServiceUpdater.ts create mode 100644 src/hooks/useTemplateStore.ts create mode 100644 src/hooks/useVpnConfig.ts create mode 100644 src/utils/template-import.ts diff --git a/package-lock.json b/package-lock.json index f9c9cf4..0eca80c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,6 @@ "dependencies": { "@codemirror/lang-yaml": "^6.1.2", "@hookform/resolvers": "^5.1.1", - "@iarna/toml": "^2.2.5", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-collapsible": "^1.1.11", "@radix-ui/react-dialog": "^1.1.14", @@ -1368,12 +1367,6 @@ "react-hook-form": "^7.55.0" } }, - "node_modules/@iarna/toml": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", - "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", - "license": "ISC" - }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..ef9b81d --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,4823 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@codemirror/lang-yaml': + specifier: ^6.1.2 + version: 6.1.2 + '@hookform/resolvers': + specifier: ^5.1.1 + version: 5.2.2(react-hook-form@7.67.0(react@19.2.0)) + '@radix-ui/react-checkbox': + specifier: ^1.3.3 + version: 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-collapsible': + specifier: ^1.1.11 + version: 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-dialog': + specifier: ^1.1.14 + version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.15 + version: 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-label': + specifier: ^2.1.7 + version: 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-select': + specifier: ^2.2.6 + version: 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-separator': + specifier: ^1.1.7 + version: 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': + specifier: ^1.2.3 + version: 1.2.4(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-toggle': + specifier: ^1.1.9 + version: 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-tooltip': + specifier: ^1.2.7 + version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@tailwindcss/vite': + specifier: ^4.0.6 + version: 4.1.17(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)) + '@tanstack/react-router': + specifier: ^1.121.2 + version: 1.139.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@tanstack/react-router-devtools': + specifier: ^1.121.2 + version: 1.139.12(@tanstack/react-router@1.139.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.139.12)(@types/node@24.10.1)(csstype@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(solid-js@1.9.10)(tsx@4.21.0) + '@tanstack/router-plugin': + specifier: ^1.121.2 + version: 1.139.12(@tanstack/react-router@1.139.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)) + '@uiw/codemirror-extensions-hyper-link': + specifier: ^4.23.14 + version: 4.25.3(@codemirror/state@6.5.2)(@codemirror/view@6.38.8) + '@uiw/codemirror-theme-monokai-dimmed': + specifier: ^4.23.14 + version: 4.25.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.8) + '@uiw/react-codemirror': + specifier: ^4.23.14 + version: 4.25.3(@babel/runtime@7.28.4)(@codemirror/autocomplete@6.20.0)(@codemirror/language@6.11.3)(@codemirror/lint@6.9.2)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.38.8)(codemirror@6.0.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + axios: + specifier: ^1.10.0 + version: 1.13.2 + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + js-yaml: + specifier: ^4.1.0 + version: 4.1.1 + lucide-react: + specifier: ^0.476.0 + version: 0.476.0(react@19.2.0) + react: + specifier: ^19.0.0 + version: 19.2.0 + react-dom: + specifier: ^19.0.0 + version: 19.2.0(react@19.2.0) + react-hook-form: + specifier: ^7.60.0 + version: 7.67.0(react@19.2.0) + tailwind-merge: + specifier: ^3.0.2 + version: 3.4.0 + tailwindcss: + specifier: ^4.0.6 + version: 4.1.17 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@4.1.17) + zod: + specifier: ^4.0.2 + version: 4.1.13 + devDependencies: + '@biomejs/biome': + specifier: 1.9.4 + version: 1.9.4 + '@testing-library/dom': + specifier: ^10.4.0 + version: 10.4.1 + '@testing-library/react': + specifier: ^16.2.0 + version: 16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@types/js-yaml': + specifier: ^4.0.9 + version: 4.0.9 + '@types/node': + specifier: ^24.0.10 + version: 24.10.1 + '@types/react': + specifier: ^19.0.8 + version: 19.2.7 + '@types/react-dom': + specifier: ^19.0.3 + version: 19.2.3(@types/react@19.2.7) + '@vitejs/plugin-react': + specifier: ^4.3.4 + version: 4.7.0(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)) + jsdom: + specifier: ^26.0.0 + version: 26.1.0 + typescript: + specifier: ^5.7.2 + version: 5.9.3 + vite: + specifier: ^6.1.0 + version: 6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0) + vitest: + specifier: ^3.0.5 + version: 3.2.4(@types/node@24.10.1)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.2)(tsx@4.21.0) + web-vitals: + specifier: ^4.2.4 + version: 4.2.4 + +packages: + + '@asamuzakjp/css-color@3.2.0': + resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.5': + resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.5': + resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.28.5': + resolution: {integrity: sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-replace-supers@7.27.1': + resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.4': + resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-jsx@7.27.1': + resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.27.1': + resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.27.1': + resolution: {integrity: sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.28.5': + resolution: {integrity: sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-typescript@7.28.5': + resolution: {integrity: sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.5': + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: '>=6.9.0'} + + '@biomejs/biome@1.9.4': + resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@1.9.4': + resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@1.9.4': + resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@1.9.4': + resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-arm64@1.9.4': + resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-x64-musl@1.9.4': + resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@1.9.4': + resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@1.9.4': + resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@1.9.4': + resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + + '@codemirror/autocomplete@6.20.0': + resolution: {integrity: sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==} + + '@codemirror/commands@6.10.0': + resolution: {integrity: sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==} + + '@codemirror/lang-yaml@6.1.2': + resolution: {integrity: sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==} + + '@codemirror/language@6.11.3': + resolution: {integrity: sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==} + + '@codemirror/lint@6.9.2': + resolution: {integrity: sha512-sv3DylBiIyi+xKwRCJAAsBZZZWo82shJ/RTMymLabAdtbkV5cSKwWDeCgtUq3v8flTaXS2y1kKkICuRYtUswyQ==} + + '@codemirror/search@6.5.11': + resolution: {integrity: sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==} + + '@codemirror/state@6.5.2': + resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==} + + '@codemirror/theme-one-dark@6.1.3': + resolution: {integrity: sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==} + + '@codemirror/view@6.38.8': + resolution: {integrity: sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==} + + '@csstools/color-helpers@5.1.0': + resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.4': + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-color-parser@3.1.0': + resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} + engines: {node: '>=18'} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.27.0': + resolution: {integrity: sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.27.0': + resolution: {integrity: sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.27.0': + resolution: {integrity: sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.27.0': + resolution: {integrity: sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.27.0': + resolution: {integrity: sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.0': + resolution: {integrity: sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.27.0': + resolution: {integrity: sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.0': + resolution: {integrity: sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.27.0': + resolution: {integrity: sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.27.0': + resolution: {integrity: sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.27.0': + resolution: {integrity: sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.27.0': + resolution: {integrity: sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.27.0': + resolution: {integrity: sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.27.0': + resolution: {integrity: sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.0': + resolution: {integrity: sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.27.0': + resolution: {integrity: sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.27.0': + resolution: {integrity: sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.27.0': + resolution: {integrity: sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.0': + resolution: {integrity: sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.27.0': + resolution: {integrity: sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.0': + resolution: {integrity: sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/openharmony-arm64@0.27.0': + resolution: {integrity: sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.0': + resolution: {integrity: sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.27.0': + resolution: {integrity: sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.27.0': + resolution: {integrity: sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.27.0': + resolution: {integrity: sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@floating-ui/core@1.7.3': + resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} + + '@floating-ui/dom@1.7.4': + resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} + + '@floating-ui/react-dom@2.1.6': + resolution: {integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + + '@hookform/resolvers@5.2.2': + resolution: {integrity: sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==} + peerDependencies: + react-hook-form: ^7.55.0 + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@lezer/common@1.4.0': + resolution: {integrity: sha512-DVeMRoGrgn/k45oQNu189BoW4SZwgZFzJ1+1TV5j2NJ/KFC83oa/enRqZSGshyeMk5cPWMhsKs9nx+8o0unwGg==} + + '@lezer/highlight@1.2.3': + resolution: {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==} + + '@lezer/lr@1.4.4': + resolution: {integrity: sha512-LHL17Mq0OcFXm1pGQssuGTQFPPdxARjKM8f7GA5+sGtHi0K3R84YaSbmche0+RKWHnCsx9asEe5OWOI4FHfe4A==} + + '@lezer/yaml@1.0.3': + resolution: {integrity: sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==} + + '@marijn/find-cluster-break@1.0.2': + resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} + + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-checkbox@1.3.3': + resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collapsible@1.1.12': + resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.15': + resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.11': + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dropdown-menu@2.1.16': + resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.3': + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-label@2.1.8': + resolution: {integrity: sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-menu@2.1.16': + resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.2.8': + resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.4': + resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.11': + resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-select@2.2.6': + resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-separator@1.1.8': + resolution: {integrity: sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.2.4': + resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-toggle@1.1.10': + resolution: {integrity: sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tooltip@1.2.8': + resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.3': + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/rollup-android-arm-eabi@4.53.3': + resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.53.3': + resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.53.3': + resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.53.3': + resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.53.3': + resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.53.3': + resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.53.3': + resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.53.3': + resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.53.3': + resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.53.3': + resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.53.3': + resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.53.3': + resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.53.3': + resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.53.3': + resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.53.3': + resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.53.3': + resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openharmony-arm64@4.53.3': + resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.53.3': + resolution: {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.53.3': + resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.53.3': + resolution: {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.53.3': + resolution: {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==} + cpu: [x64] + os: [win32] + + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + + '@tailwindcss/node@4.1.17': + resolution: {integrity: sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==} + + '@tailwindcss/oxide-android-arm64@4.1.17': + resolution: {integrity: sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.17': + resolution: {integrity: sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.17': + resolution: {integrity: sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.17': + resolution: {integrity: sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17': + resolution: {integrity: sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.17': + resolution: {integrity: sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.17': + resolution: {integrity: sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.17': + resolution: {integrity: sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.17': + resolution: {integrity: sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.17': + resolution: {integrity: sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.17': + resolution: {integrity: sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.17': + resolution: {integrity: sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.17': + resolution: {integrity: sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==} + engines: {node: '>= 10'} + + '@tailwindcss/vite@4.1.17': + resolution: {integrity: sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 + + '@tanstack/history@1.139.0': + resolution: {integrity: sha512-l6wcxwDBeh/7Dhles23U1O8lp9kNJmAb2yNjekR6olZwCRNAVA8TCXlVCrueELyFlYZqvQkh0ofxnzG62A1Kkg==} + engines: {node: '>=12'} + + '@tanstack/react-router-devtools@1.139.12': + resolution: {integrity: sha512-deMQGaojEJGFio95o0rDT4OhgtwfgrQIBZAGnXhfyC395n94IuE43uvvv7tkfBzWHQwYK0IvZIeyKMavbvAj7Q==} + engines: {node: '>=12'} + peerDependencies: + '@tanstack/react-router': ^1.139.12 + '@tanstack/router-core': ^1.139.12 + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + peerDependenciesMeta: + '@tanstack/router-core': + optional: true + + '@tanstack/react-router@1.139.12': + resolution: {integrity: sha512-qrIxb8c6XXih6MERZKKwdnYg0OannsQLJ/s+4/wRqKqGCG+QmvAMvnmNP7bfYLgFKi+KsE27HqUkHaSpZSenwQ==} + engines: {node: '>=12'} + peerDependencies: + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + + '@tanstack/react-store@0.8.0': + resolution: {integrity: sha512-1vG9beLIuB7q69skxK9r5xiLN3ztzIPfSQSs0GfeqWGO2tGIyInZx0x1COhpx97RKaONSoAb8C3dxacWksm1ow==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/router-core@1.139.12': + resolution: {integrity: sha512-HCDi4fpnAFeDDogT0C61yd2nJn0FrIyFDhyHG3xJji8emdn8Ni4rfyrN4Av46xKkXTPUGdbsqih45+uuNtunew==} + engines: {node: '>=12'} + + '@tanstack/router-devtools-core@1.139.12': + resolution: {integrity: sha512-VARlT9alLnROnPsZtHrSZsqYksIdBBQ24yGzEper5K1+1e0fzpcKLnMYLK9cwr//uWA2xmQayznvBnwcTmnUlg==} + engines: {node: '>=12'} + peerDependencies: + '@tanstack/router-core': ^1.139.12 + csstype: ^3.0.10 + solid-js: '>=1.9.5' + peerDependenciesMeta: + csstype: + optional: true + + '@tanstack/router-generator@1.139.12': + resolution: {integrity: sha512-HGs35aBml+2TVwoynsEc00/9Duw19GeT1fX+JzrY0TKNfMzq/nbjR+xxU8M1w3+gHqfKiITmW70XSZoWkXu9tw==} + engines: {node: '>=12'} + + '@tanstack/router-plugin@1.139.12': + resolution: {integrity: sha512-xX39CcU6GLMaahr6YGNQYRZOQsd1WefgCH99PtY0cxZr9VNAIpJMYPsQY8h/g8A4JI10rHI1tdKxZAvodWjZxw==} + engines: {node: '>=12'} + peerDependencies: + '@rsbuild/core': '>=1.0.2' + '@tanstack/react-router': ^1.139.12 + vite: '>=5.0.0 || >=6.0.0 || >=7.0.0' + vite-plugin-solid: ^2.11.10 + webpack: '>=5.92.0' + peerDependenciesMeta: + '@rsbuild/core': + optional: true + '@tanstack/react-router': + optional: true + vite: + optional: true + vite-plugin-solid: + optional: true + webpack: + optional: true + + '@tanstack/router-utils@1.139.0': + resolution: {integrity: sha512-jT7D6NimWqoFSkid4vCno8gvTyfL1+NHpgm3es0B2UNhKKRV3LngOGilm1m6v8Qvk/gy6Fh/tvB+s+hBl6GhOg==} + engines: {node: '>=12'} + + '@tanstack/store@0.8.0': + resolution: {integrity: sha512-Om+BO0YfMZe//X2z0uLF2j+75nQga6TpTJgLJQBiq85aOyZNIhkCgleNcud2KQg4k4v9Y9l+Uhru3qWMPGTOzQ==} + + '@tanstack/virtual-file-routes@1.139.0': + resolution: {integrity: sha512-9PImF1d1tovTUIpjFVa0W7Fwj/MHif7BaaczgJJfbv3sDt1Gh+oW9W9uCw9M3ndEJynnp5ZD/TTs0RGubH5ssg==} + engines: {node: '>=12'} + + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/react@16.3.0': + resolution: {integrity: sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/js-yaml@4.0.9': + resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + + '@types/node@24.10.1': + resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.7': + resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==} + + '@uiw/codemirror-extensions-basic-setup@4.25.3': + resolution: {integrity: sha512-F1doRyD50CWScwGHG2bBUtUpwnOv/zqSnzkZqJcX5YAHQx6Z1CuX8jdnFMH6qktRrPU1tfpNYftTWu3QIoHiMA==} + peerDependencies: + '@codemirror/autocomplete': '>=6.0.0' + '@codemirror/commands': '>=6.0.0' + '@codemirror/language': '>=6.0.0' + '@codemirror/lint': '>=6.0.0' + '@codemirror/search': '>=6.0.0' + '@codemirror/state': '>=6.0.0' + '@codemirror/view': '>=6.0.0' + + '@uiw/codemirror-extensions-hyper-link@4.25.3': + resolution: {integrity: sha512-/d2VOx9NZmzsZYrkkJBHCT3EkQbXoekNEfP4EhqrZYmp3yh7H2rTEIFj8sWt8MgkG+anAKYYGe54ky96d2/pOg==} + peerDependencies: + '@codemirror/state': '>=6.0.0' + '@codemirror/view': '>=6.0.0' + + '@uiw/codemirror-theme-monokai-dimmed@4.25.3': + resolution: {integrity: sha512-k6llkWGUfoWQuEgA2KtiB8BC2+OnycmVCnbGH+3Pd8Kk9uwM0uAWihluszYcUSjkYztU5ni0X28ClOFW0F9uWQ==} + + '@uiw/codemirror-themes@4.25.3': + resolution: {integrity: sha512-k7/B7Vf4jU/WcdewgJWP9tMFxbjB6UpUymZ3fx/TsbGwt2JXAouw0uyqCn1RlYBfr7YQnvEs3Ju9ECkd2sKzdg==} + peerDependencies: + '@codemirror/language': '>=6.0.0' + '@codemirror/state': '>=6.0.0' + '@codemirror/view': '>=6.0.0' + + '@uiw/react-codemirror@4.25.3': + resolution: {integrity: sha512-1wtBZTXPIp8u6F/xjHvsUAYlEeF5Dic4xZBnqJyLzv7o7GjGYEUfSz9Z7bo9aK9GAx2uojG/AuBMfhA4uhvIVQ==} + peerDependencies: + '@babel/runtime': '>=7.11.0' + '@codemirror/state': '>=6.0.0' + '@codemirror/theme-one-dark': '>=6.0.0' + '@codemirror/view': '>=6.0.0' + codemirror: '>=6.0.0' + react: '>=17.0.0' + react-dom: '>=17.0.0' + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansis@4.2.0: + resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} + engines: {node: '>=14'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-types@0.16.1: + resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} + engines: {node: '>=4'} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.13.2: + resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} + + babel-dead-code-elimination@1.0.10: + resolution: {integrity: sha512-DV5bdJZTzZ0zn0DC24v3jD7Mnidh6xhKa4GfKCbq3sfW8kaWhDdZjP3i81geA8T33tdYqWKw4D3fVv0CwEgKVA==} + + baseline-browser-mapping@2.8.32: + resolution: {integrity: sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==} + hasBin: true + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.0: + resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + caniuse-lite@1.0.30001757: + resolution: {integrity: sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==} + + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + codemirror@6.0.2: + resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-es@2.0.0: + resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==} + + crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + + cssstyle@4.6.0: + resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} + engines: {node: '>=18'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + + diff@8.0.2: + resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} + engines: {node: '>=0.3.1'} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + electron-to-chromium@1.5.262: + resolution: {integrity: sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==} + + enhanced-resolve@5.18.3: + resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} + engines: {node: '>=10.13.0'} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.27.0: + resolution: {integrity: sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.2.2: + resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} + engines: {node: '>=12.0.0'} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.13.0: + resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + goober@2.1.18: + resolution: {integrity: sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==} + peerDependencies: + csstype: ^3.0.10 + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + isbot@5.1.32: + resolution: {integrity: sha512-VNfjM73zz2IBZmdShMfAUg10prm6t7HFUQmNAEOAVS4YH92ZrZcvkMcGX6cIgBJAzWDzPent/EeAtYEHNPNPBQ==} + engines: {node: '>=18'} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsdom@26.1.0: + resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + lightningcss-android-arm64@1.30.2: + resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.30.2: + resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.2: + resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.2: + resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.2: + resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.2: + resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.2: + resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.2: + resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.2: + resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.2: + resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.2: + resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.2: + resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} + engines: {node: '>= 12.0.0'} + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-react@0.476.0: + resolution: {integrity: sha512-x6cLTk8gahdUPje0hSgLN1/MgiJH+Xl90Xoxy9bkPAsMPOUiyRSKR4JCDPGVCEpyqnZXH3exFWNItcvra9WzUQ==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + nwsapi@2.2.22: + resolution: {integrity: sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prettier@3.7.3: + resolution: {integrity: sha512-QgODejq9K3OzoBbuyobZlUhznP5SKwPqp+6Q6xw6o8gnhr4O85L2U915iM2IDcfF2NPXVaM9zlo9tdwipnYwzg==} + engines: {node: '>=14'} + hasBin: true + + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + react-dom@19.2.0: + resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==} + peerDependencies: + react: ^19.2.0 + + react-hook-form@7.67.0: + resolution: {integrity: sha512-E55EOwKJHHIT/I6J9DmQbCWToAYSw9nN5R57MZw9rMtjh+YQreMDxRLfdjfxQbiJ3/qbg3Z02wGzBX4M+5fMtQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react@19.2.0: + resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} + engines: {node: '>=0.10.0'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + recast@0.23.11: + resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} + engines: {node: '>= 4'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + rollup@4.53.3: + resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + seroval-plugins@1.3.3: + resolution: {integrity: sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + + seroval-plugins@1.4.0: + resolution: {integrity: sha512-zir1aWzoiax6pbBVjoYVd0O1QQXgIL3eVGBMsBsNmM8Ukq90yGaWlfx0AB9dTS8GPqrOrbXn79vmItCUP9U3BQ==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + + seroval@1.3.2: + resolution: {integrity: sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==} + engines: {node: '>=10'} + + seroval@1.4.0: + resolution: {integrity: sha512-BdrNXdzlofomLTiRnwJTSEAaGKyHHZkbMXIywOh7zlzp4uZnXErEwl9XZ+N1hJSNpeTtNxWvVwN0wUzAIQ4Hpg==} + engines: {node: '>=10'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + solid-js@1.9.10: + resolution: {integrity: sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + + style-mod@4.1.3: + resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + tailwind-merge@3.4.0: + resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} + + tailwindcss-animate@1.0.7: + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + + tailwindcss@4.1.17: + resolution: {integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==} + + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + tiny-warning@1.0.3: + resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + engines: {node: '>=14.0.0'} + + tldts-core@6.1.86: + resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} + + tldts@6.1.86: + resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} + hasBin: true + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tough-cookie@5.1.2: + resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} + engines: {node: '>=16'} + + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: '>=18'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + unplugin@2.3.11: + resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} + engines: {node: '>=18.12.0'} + + update-browserslist-db@1.1.4: + resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite@6.4.1: + resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vite@7.2.6: + resolution: {integrity: sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + web-vitals@4.2.4: + resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + + zod@4.1.13: + resolution: {integrity: sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==} + +snapshots: + + '@asamuzakjp/css-color@3.2.0': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 10.4.3 + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.5': {} + + '@babel/core@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) + '@babel/helpers': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.5': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.28.5 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.5 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.0 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.28.5 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-member-expression-to-functions@7.28.5': + dependencies: + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.27.1': + dependencies: + '@babel/types': 7.28.5 + + '@babel/helper-plugin-utils@7.27.1': {} + + '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + dependencies: + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.4': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-typescript@7.28.5(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + + '@babel/preset-typescript@7.28.5(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + + '@babel/runtime@7.28.4': {} + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + + '@babel/traverse@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@biomejs/biome@1.9.4': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 1.9.4 + '@biomejs/cli-darwin-x64': 1.9.4 + '@biomejs/cli-linux-arm64': 1.9.4 + '@biomejs/cli-linux-arm64-musl': 1.9.4 + '@biomejs/cli-linux-x64': 1.9.4 + '@biomejs/cli-linux-x64-musl': 1.9.4 + '@biomejs/cli-win32-arm64': 1.9.4 + '@biomejs/cli-win32-x64': 1.9.4 + + '@biomejs/cli-darwin-arm64@1.9.4': + optional: true + + '@biomejs/cli-darwin-x64@1.9.4': + optional: true + + '@biomejs/cli-linux-arm64-musl@1.9.4': + optional: true + + '@biomejs/cli-linux-arm64@1.9.4': + optional: true + + '@biomejs/cli-linux-x64-musl@1.9.4': + optional: true + + '@biomejs/cli-linux-x64@1.9.4': + optional: true + + '@biomejs/cli-win32-arm64@1.9.4': + optional: true + + '@biomejs/cli-win32-x64@1.9.4': + optional: true + + '@codemirror/autocomplete@6.20.0': + dependencies: + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + '@lezer/common': 1.4.0 + + '@codemirror/commands@6.10.0': + dependencies: + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + '@lezer/common': 1.4.0 + + '@codemirror/lang-yaml@6.1.2': + dependencies: + '@codemirror/autocomplete': 6.20.0 + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@lezer/common': 1.4.0 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.4 + '@lezer/yaml': 1.0.3 + + '@codemirror/language@6.11.3': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + '@lezer/common': 1.4.0 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.4 + style-mod: 4.1.3 + + '@codemirror/lint@6.9.2': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + crelt: 1.0.6 + + '@codemirror/search@6.5.11': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + crelt: 1.0.6 + + '@codemirror/state@6.5.2': + dependencies: + '@marijn/find-cluster-break': 1.0.2 + + '@codemirror/theme-one-dark@6.1.3': + dependencies: + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + '@lezer/highlight': 1.2.3 + + '@codemirror/view@6.38.8': + dependencies: + '@codemirror/state': 6.5.2 + crelt: 1.0.6 + style-mod: 4.1.3 + w3c-keyname: 2.2.8 + + '@csstools/color-helpers@5.1.0': {} + + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/color-helpers': 5.1.0 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-tokenizer@3.0.4': {} + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/aix-ppc64@0.27.0': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.27.0': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-arm@0.27.0': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/android-x64@0.27.0': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.27.0': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.27.0': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.27.0': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.27.0': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.27.0': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-arm@0.27.0': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.27.0': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.27.0': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.27.0': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.27.0': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.27.0': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.27.0': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/linux-x64@0.27.0': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.27.0': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.27.0': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.27.0': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.27.0': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.27.0': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.27.0': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.27.0': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.27.0': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@esbuild/win32-x64@0.27.0': + optional: true + + '@floating-ui/core@1.7.3': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.7.4': + dependencies: + '@floating-ui/core': 1.7.3 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/react-dom@2.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@floating-ui/dom': 1.7.4 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@floating-ui/utils@0.2.10': {} + + '@hookform/resolvers@5.2.2(react-hook-form@7.67.0(react@19.2.0))': + dependencies: + '@standard-schema/utils': 0.3.0 + react-hook-form: 7.67.0(react@19.2.0) + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@lezer/common@1.4.0': {} + + '@lezer/highlight@1.2.3': + dependencies: + '@lezer/common': 1.4.0 + + '@lezer/lr@1.4.4': + dependencies: + '@lezer/common': 1.4.0 + + '@lezer/yaml@1.0.3': + dependencies: + '@lezer/common': 1.4.0 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.4 + + '@marijn/find-cluster-break@1.0.2': {} + + '@radix-ui/number@1.1.1': {} + + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.7)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.7)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.7)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.7 + + '@radix-ui/react-context@1.1.2(@types/react@19.2.7)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.7 + + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.0) + aria-hidden: 1.2.6 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-remove-scroll: 2.7.2(@types/react@19.2.7)(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@radix-ui/react-direction@1.1.1(@types/react@19.2.7)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.7 + + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.7)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.7)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.7 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@radix-ui/react-id@1.1.1(@types/react@19.2.7)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.7 + + '@radix-ui/react-label@2.1.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.0) + aria-hidden: 1.2.6 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-remove-scroll: 2.7.2(@types/react@19.2.7)(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@floating-ui/react-dom': 2.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/rect': 1.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.7)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-slot': 1.2.4(@types/react@19.2.7)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + aria-hidden: 1.2.6 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-remove-scroll: 2.7.2(@types/react@19.2.7)(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@radix-ui/react-separator@1.1.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@radix-ui/react-slot@1.2.3(@types/react@19.2.7)(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.7 + + '@radix-ui/react-slot@1.2.4(@types/react@19.2.7)(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.7 + + '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.7)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.7 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.7)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.7 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.7)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.7 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.7)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.7 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.7)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.7 + + '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.7)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.7 + + '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.7)(react@19.2.0)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.7 + + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.7)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.7 + + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@radix-ui/rect@1.1.1': {} + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/rollup-android-arm-eabi@4.53.3': + optional: true + + '@rollup/rollup-android-arm64@4.53.3': + optional: true + + '@rollup/rollup-darwin-arm64@4.53.3': + optional: true + + '@rollup/rollup-darwin-x64@4.53.3': + optional: true + + '@rollup/rollup-freebsd-arm64@4.53.3': + optional: true + + '@rollup/rollup-freebsd-x64@4.53.3': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.53.3': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.53.3': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.53.3': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-x64-musl@4.53.3': + optional: true + + '@rollup/rollup-openharmony-arm64@4.53.3': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.53.3': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.53.3': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.53.3': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.53.3': + optional: true + + '@standard-schema/utils@0.3.0': {} + + '@tailwindcss/node@4.1.17': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.18.3 + jiti: 2.6.1 + lightningcss: 1.30.2 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.1.17 + + '@tailwindcss/oxide-android-arm64@4.1.17': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.17': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.17': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.17': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.17': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.17': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.17': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.17': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.17': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.17': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.17': + optional: true + + '@tailwindcss/oxide@4.1.17': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.17 + '@tailwindcss/oxide-darwin-arm64': 4.1.17 + '@tailwindcss/oxide-darwin-x64': 4.1.17 + '@tailwindcss/oxide-freebsd-x64': 4.1.17 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.17 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.17 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.17 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.17 + '@tailwindcss/oxide-linux-x64-musl': 4.1.17 + '@tailwindcss/oxide-wasm32-wasi': 4.1.17 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.17 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.17 + + '@tailwindcss/vite@4.1.17(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))': + dependencies: + '@tailwindcss/node': 4.1.17 + '@tailwindcss/oxide': 4.1.17 + tailwindcss: 4.1.17 + vite: 6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0) + + '@tanstack/history@1.139.0': {} + + '@tanstack/react-router-devtools@1.139.12(@tanstack/react-router@1.139.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.139.12)(@types/node@24.10.1)(csstype@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(solid-js@1.9.10)(tsx@4.21.0)': + dependencies: + '@tanstack/react-router': 1.139.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@tanstack/router-devtools-core': 1.139.12(@tanstack/router-core@1.139.12)(@types/node@24.10.1)(csstype@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(solid-js@1.9.10)(tsx@4.21.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0) + optionalDependencies: + '@tanstack/router-core': 1.139.12 + transitivePeerDependencies: + - '@types/node' + - csstype + - jiti + - less + - lightningcss + - sass + - sass-embedded + - solid-js + - stylus + - sugarss + - terser + - tsx + - yaml + + '@tanstack/react-router@1.139.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@tanstack/history': 1.139.0 + '@tanstack/react-store': 0.8.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@tanstack/router-core': 1.139.12 + isbot: 5.1.32 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + tiny-invariant: 1.3.3 + tiny-warning: 1.0.3 + + '@tanstack/react-store@0.8.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@tanstack/store': 0.8.0 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + use-sync-external-store: 1.6.0(react@19.2.0) + + '@tanstack/router-core@1.139.12': + dependencies: + '@tanstack/history': 1.139.0 + '@tanstack/store': 0.8.0 + cookie-es: 2.0.0 + seroval: 1.4.0 + seroval-plugins: 1.4.0(seroval@1.4.0) + tiny-invariant: 1.3.3 + tiny-warning: 1.0.3 + + '@tanstack/router-devtools-core@1.139.12(@tanstack/router-core@1.139.12)(@types/node@24.10.1)(csstype@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(solid-js@1.9.10)(tsx@4.21.0)': + dependencies: + '@tanstack/router-core': 1.139.12 + clsx: 2.1.1 + goober: 2.1.18(csstype@3.2.3) + solid-js: 1.9.10 + tiny-invariant: 1.3.3 + vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0) + optionalDependencies: + csstype: 3.2.3 + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + + '@tanstack/router-generator@1.139.12': + dependencies: + '@tanstack/router-core': 1.139.12 + '@tanstack/router-utils': 1.139.0 + '@tanstack/virtual-file-routes': 1.139.0 + prettier: 3.7.3 + recast: 0.23.11 + source-map: 0.7.6 + tsx: 4.21.0 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + + '@tanstack/router-plugin@1.139.12(@tanstack/react-router@1.139.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))': + dependencies: + '@babel/core': 7.28.5 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5) + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + '@tanstack/router-core': 1.139.12 + '@tanstack/router-generator': 1.139.12 + '@tanstack/router-utils': 1.139.0 + '@tanstack/virtual-file-routes': 1.139.0 + babel-dead-code-elimination: 1.0.10 + chokidar: 3.6.0 + unplugin: 2.3.11 + zod: 3.25.76 + optionalDependencies: + '@tanstack/react-router': 1.139.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + vite: 6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0) + transitivePeerDependencies: + - supports-color + + '@tanstack/router-utils@1.139.0': + dependencies: + '@babel/core': 7.28.5 + '@babel/generator': 7.28.5 + '@babel/parser': 7.28.5 + '@babel/preset-typescript': 7.28.5(@babel/core@7.28.5) + ansis: 4.2.0 + diff: 8.0.2 + pathe: 2.0.3 + tinyglobby: 0.2.15 + transitivePeerDependencies: + - supports-color + + '@tanstack/store@0.8.0': {} + + '@tanstack/virtual-file-routes@1.139.0': {} + + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/runtime': 7.28.4 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/react@16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@babel/runtime': 7.28.4 + '@testing-library/dom': 10.4.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@types/aria-query@5.0.4': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.28.5 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.28.5 + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.8': {} + + '@types/js-yaml@4.0.9': {} + + '@types/node@24.10.1': + dependencies: + undici-types: 7.16.0 + + '@types/react-dom@19.2.3(@types/react@19.2.7)': + dependencies: + '@types/react': 19.2.7 + + '@types/react@19.2.7': + dependencies: + csstype: 3.2.3 + + '@uiw/codemirror-extensions-basic-setup@4.25.3(@codemirror/autocomplete@6.20.0)(@codemirror/commands@6.10.0)(@codemirror/language@6.11.3)(@codemirror/lint@6.9.2)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/view@6.38.8)': + dependencies: + '@codemirror/autocomplete': 6.20.0 + '@codemirror/commands': 6.10.0 + '@codemirror/language': 6.11.3 + '@codemirror/lint': 6.9.2 + '@codemirror/search': 6.5.11 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + + '@uiw/codemirror-extensions-hyper-link@4.25.3(@codemirror/state@6.5.2)(@codemirror/view@6.38.8)': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + + '@uiw/codemirror-theme-monokai-dimmed@4.25.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.8)': + dependencies: + '@uiw/codemirror-themes': 4.25.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.8) + transitivePeerDependencies: + - '@codemirror/language' + - '@codemirror/state' + - '@codemirror/view' + + '@uiw/codemirror-themes@4.25.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.8)': + dependencies: + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + + '@uiw/react-codemirror@4.25.3(@babel/runtime@7.28.4)(@codemirror/autocomplete@6.20.0)(@codemirror/language@6.11.3)(@codemirror/lint@6.9.2)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.38.8)(codemirror@6.0.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@babel/runtime': 7.28.4 + '@codemirror/commands': 6.10.0 + '@codemirror/state': 6.5.2 + '@codemirror/theme-one-dark': 6.1.3 + '@codemirror/view': 6.38.8 + '@uiw/codemirror-extensions-basic-setup': 4.25.3(@codemirror/autocomplete@6.20.0)(@codemirror/commands@6.10.0)(@codemirror/language@6.11.3)(@codemirror/lint@6.9.2)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/view@6.38.8) + codemirror: 6.0.2 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + transitivePeerDependencies: + - '@codemirror/autocomplete' + - '@codemirror/language' + - '@codemirror/lint' + - '@codemirror/search' + + '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))': + dependencies: + '@babel/core': 7.28.5 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.4(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0) + + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.4': + dependencies: + '@vitest/utils': 3.2.4 + pathe: 2.0.3 + strip-literal: 3.1.0 + + '@vitest/snapshot@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.4 + + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + + acorn@8.15.0: {} + + agent-base@7.1.4: {} + + ansi-regex@5.0.1: {} + + ansi-styles@5.2.0: {} + + ansis@4.2.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + argparse@2.0.1: {} + + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + assertion-error@2.0.1: {} + + ast-types@0.16.1: + dependencies: + tslib: 2.8.1 + + asynckit@0.4.0: {} + + axios@1.13.2: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.5 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + babel-dead-code-elimination@1.0.10: + dependencies: + '@babel/core': 7.28.5 + '@babel/parser': 7.28.5 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + transitivePeerDependencies: + - supports-color + + baseline-browser-mapping@2.8.32: {} + + binary-extensions@2.3.0: {} + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.0: + dependencies: + baseline-browser-mapping: 2.8.32 + caniuse-lite: 1.0.30001757 + electron-to-chromium: 1.5.262 + node-releases: 2.0.27 + update-browserslist-db: 1.1.4(browserslist@4.28.0) + + cac@6.7.14: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + caniuse-lite@1.0.30001757: {} + + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + + check-error@2.1.1: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + clsx@2.1.1: {} + + codemirror@6.0.2: + dependencies: + '@codemirror/autocomplete': 6.20.0 + '@codemirror/commands': 6.10.0 + '@codemirror/language': 6.11.3 + '@codemirror/lint': 6.9.2 + '@codemirror/search': 6.5.11 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + convert-source-map@2.0.0: {} + + cookie-es@2.0.0: {} + + crelt@1.0.6: {} + + cssstyle@4.6.0: + dependencies: + '@asamuzakjp/css-color': 3.2.0 + rrweb-cssom: 0.8.0 + + csstype@3.2.3: {} + + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decimal.js@10.6.0: {} + + deep-eql@5.0.2: {} + + delayed-stream@1.0.0: {} + + dequal@2.0.3: {} + + detect-libc@2.1.2: {} + + detect-node-es@1.1.0: {} + + diff@8.0.2: {} + + dom-accessibility-api@0.5.16: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + electron-to-chromium@1.5.262: {} + + enhanced-resolve@5.18.3: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + + entities@6.0.1: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + esbuild@0.27.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.0 + '@esbuild/android-arm': 0.27.0 + '@esbuild/android-arm64': 0.27.0 + '@esbuild/android-x64': 0.27.0 + '@esbuild/darwin-arm64': 0.27.0 + '@esbuild/darwin-x64': 0.27.0 + '@esbuild/freebsd-arm64': 0.27.0 + '@esbuild/freebsd-x64': 0.27.0 + '@esbuild/linux-arm': 0.27.0 + '@esbuild/linux-arm64': 0.27.0 + '@esbuild/linux-ia32': 0.27.0 + '@esbuild/linux-loong64': 0.27.0 + '@esbuild/linux-mips64el': 0.27.0 + '@esbuild/linux-ppc64': 0.27.0 + '@esbuild/linux-riscv64': 0.27.0 + '@esbuild/linux-s390x': 0.27.0 + '@esbuild/linux-x64': 0.27.0 + '@esbuild/netbsd-arm64': 0.27.0 + '@esbuild/netbsd-x64': 0.27.0 + '@esbuild/openbsd-arm64': 0.27.0 + '@esbuild/openbsd-x64': 0.27.0 + '@esbuild/openharmony-arm64': 0.27.0 + '@esbuild/sunos-x64': 0.27.0 + '@esbuild/win32-arm64': 0.27.0 + '@esbuild/win32-ia32': 0.27.0 + '@esbuild/win32-x64': 0.27.0 + + escalade@3.2.0: {} + + esprima@4.0.1: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + expect-type@1.2.2: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + follow-redirects@1.15.11: {} + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-nonce@1.0.1: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-tsconfig@4.13.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + goober@2.1.18(csstype@3.2.3): + dependencies: + csstype: 3.2.3 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-potential-custom-element-name@1.0.1: {} + + isbot@5.1.32: {} + + jiti@2.6.1: {} + + js-tokens@4.0.0: {} + + js-tokens@9.0.1: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsdom@26.1.0: + dependencies: + cssstyle: 4.6.0 + data-urls: 5.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.22 + parse5: 7.3.0 + rrweb-cssom: 0.8.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.1.2 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + ws: 8.18.3 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + jsesc@3.1.0: {} + + json5@2.2.3: {} + + lightningcss-android-arm64@1.30.2: + optional: true + + lightningcss-darwin-arm64@1.30.2: + optional: true + + lightningcss-darwin-x64@1.30.2: + optional: true + + lightningcss-freebsd-x64@1.30.2: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.2: + optional: true + + lightningcss-linux-arm64-gnu@1.30.2: + optional: true + + lightningcss-linux-arm64-musl@1.30.2: + optional: true + + lightningcss-linux-x64-gnu@1.30.2: + optional: true + + lightningcss-linux-x64-musl@1.30.2: + optional: true + + lightningcss-win32-arm64-msvc@1.30.2: + optional: true + + lightningcss-win32-x64-msvc@1.30.2: + optional: true + + lightningcss@1.30.2: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.30.2 + lightningcss-darwin-arm64: 1.30.2 + lightningcss-darwin-x64: 1.30.2 + lightningcss-freebsd-x64: 1.30.2 + lightningcss-linux-arm-gnueabihf: 1.30.2 + lightningcss-linux-arm64-gnu: 1.30.2 + lightningcss-linux-arm64-musl: 1.30.2 + lightningcss-linux-x64-gnu: 1.30.2 + lightningcss-linux-x64-musl: 1.30.2 + lightningcss-win32-arm64-msvc: 1.30.2 + lightningcss-win32-x64-msvc: 1.30.2 + + loupe@3.2.1: {} + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@0.476.0(react@19.2.0): + dependencies: + react: 19.2.0 + + lz-string@1.5.0: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + node-releases@2.0.27: {} + + normalize-path@3.0.0: {} + + nwsapi@2.2.22: {} + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + pathe@2.0.3: {} + + pathval@2.0.1: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prettier@3.7.3: {} + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + proxy-from-env@1.1.0: {} + + punycode@2.3.1: {} + + react-dom@19.2.0(react@19.2.0): + dependencies: + react: 19.2.0 + scheduler: 0.27.0 + + react-hook-form@7.67.0(react@19.2.0): + dependencies: + react: 19.2.0 + + react-is@17.0.2: {} + + react-refresh@0.17.0: {} + + react-remove-scroll-bar@2.3.8(@types/react@19.2.7)(react@19.2.0): + dependencies: + react: 19.2.0 + react-style-singleton: 2.2.3(@types/react@19.2.7)(react@19.2.0) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.7 + + react-remove-scroll@2.7.2(@types/react@19.2.7)(react@19.2.0): + dependencies: + react: 19.2.0 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.7)(react@19.2.0) + react-style-singleton: 2.2.3(@types/react@19.2.7)(react@19.2.0) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.2.7)(react@19.2.0) + use-sidecar: 1.1.3(@types/react@19.2.7)(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.7 + + react-style-singleton@2.2.3(@types/react@19.2.7)(react@19.2.0): + dependencies: + get-nonce: 1.0.1 + react: 19.2.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.7 + + react@19.2.0: {} + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + recast@0.23.11: + dependencies: + ast-types: 0.16.1 + esprima: 4.0.1 + source-map: 0.6.1 + tiny-invariant: 1.3.3 + tslib: 2.8.1 + + resolve-pkg-maps@1.0.0: {} + + rollup@4.53.3: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.53.3 + '@rollup/rollup-android-arm64': 4.53.3 + '@rollup/rollup-darwin-arm64': 4.53.3 + '@rollup/rollup-darwin-x64': 4.53.3 + '@rollup/rollup-freebsd-arm64': 4.53.3 + '@rollup/rollup-freebsd-x64': 4.53.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.53.3 + '@rollup/rollup-linux-arm-musleabihf': 4.53.3 + '@rollup/rollup-linux-arm64-gnu': 4.53.3 + '@rollup/rollup-linux-arm64-musl': 4.53.3 + '@rollup/rollup-linux-loong64-gnu': 4.53.3 + '@rollup/rollup-linux-ppc64-gnu': 4.53.3 + '@rollup/rollup-linux-riscv64-gnu': 4.53.3 + '@rollup/rollup-linux-riscv64-musl': 4.53.3 + '@rollup/rollup-linux-s390x-gnu': 4.53.3 + '@rollup/rollup-linux-x64-gnu': 4.53.3 + '@rollup/rollup-linux-x64-musl': 4.53.3 + '@rollup/rollup-openharmony-arm64': 4.53.3 + '@rollup/rollup-win32-arm64-msvc': 4.53.3 + '@rollup/rollup-win32-ia32-msvc': 4.53.3 + '@rollup/rollup-win32-x64-gnu': 4.53.3 + '@rollup/rollup-win32-x64-msvc': 4.53.3 + fsevents: 2.3.3 + + rrweb-cssom@0.8.0: {} + + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + seroval-plugins@1.3.3(seroval@1.3.2): + dependencies: + seroval: 1.3.2 + + seroval-plugins@1.4.0(seroval@1.4.0): + dependencies: + seroval: 1.4.0 + + seroval@1.3.2: {} + + seroval@1.4.0: {} + + siginfo@2.0.0: {} + + solid-js@1.9.10: + dependencies: + csstype: 3.2.3 + seroval: 1.3.2 + seroval-plugins: 1.3.3(seroval@1.3.2) + + source-map-js@1.2.1: {} + + source-map@0.6.1: {} + + source-map@0.7.6: {} + + stackback@0.0.2: {} + + std-env@3.10.0: {} + + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + + style-mod@4.1.3: {} + + symbol-tree@3.2.4: {} + + tailwind-merge@3.4.0: {} + + tailwindcss-animate@1.0.7(tailwindcss@4.1.17): + dependencies: + tailwindcss: 4.1.17 + + tailwindcss@4.1.17: {} + + tapable@2.3.0: {} + + tiny-invariant@1.3.3: {} + + tiny-warning@1.0.3: {} + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.4: {} + + tldts-core@6.1.86: {} + + tldts@6.1.86: + dependencies: + tldts-core: 6.1.86 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tough-cookie@5.1.2: + dependencies: + tldts: 6.1.86 + + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + + tslib@2.8.1: {} + + tsx@4.21.0: + dependencies: + esbuild: 0.27.0 + get-tsconfig: 4.13.0 + optionalDependencies: + fsevents: 2.3.3 + + typescript@5.9.3: {} + + undici-types@7.16.0: {} + + unplugin@2.3.11: + dependencies: + '@jridgewell/remapping': 2.3.5 + acorn: 8.15.0 + picomatch: 4.0.3 + webpack-virtual-modules: 0.6.2 + + update-browserslist-db@1.1.4(browserslist@4.28.0): + dependencies: + browserslist: 4.28.0 + escalade: 3.2.0 + picocolors: 1.1.1 + + use-callback-ref@1.3.3(@types/react@19.2.7)(react@19.2.0): + dependencies: + react: 19.2.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.7 + + use-sidecar@1.1.3(@types/react@19.2.7)(react@19.2.0): + dependencies: + detect-node-es: 1.1.0 + react: 19.2.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.7 + + use-sync-external-store@1.6.0(react@19.2.0): + dependencies: + react: 19.2.0 + + vite-node@3.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.53.3 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.10.1 + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.30.2 + tsx: 4.21.0 + + vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.53.3 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.10.1 + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.30.2 + tsx: 4.21.0 + + vitest@3.2.4(@types/node@24.10.1)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.2)(tsx@4.21.0): + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.2.2 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0) + vite-node: 3.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.10.1 + jsdom: 26.1.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + w3c-keyname@2.2.8: {} + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + web-vitals@4.2.4: {} + + webidl-conversions@7.0.0: {} + + webpack-virtual-modules@0.6.2: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + ws@8.18.3: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + yallist@3.1.1: {} + + zod@3.25.76: {} + + zod@4.1.13: {} diff --git a/src/components/Header.tsx b/src/components/Header.tsx index a922457..0769980 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -34,6 +34,7 @@ export function Header() { }} /> Dock-Dploy + by HHF Technology
+
+ ))} + +

+ For ipvlan networks, define the subnet and gateway for IP allocation +

+ + + + ); +} + diff --git a/src/components/compose-builder/ServiceForm.tsx b/src/components/compose-builder/ServiceForm.tsx new file mode 100644 index 0000000..9dd4c52 --- /dev/null +++ b/src/components/compose-builder/ServiceForm.tsx @@ -0,0 +1,1395 @@ +import { Label } from "../../components/ui/label"; +import { Input } from "../../components/ui/input"; +import { Button } from "../../components/ui/button"; +import { Toggle } from "../../components/ui/toggle"; +import { + Collapsible, + CollapsibleTrigger, + CollapsibleContent, +} from "../../components/ui/collapsible"; +import { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, +} from "../../components/ui/dropdown-menu"; +import { + Tooltip, + TooltipTrigger, + TooltipContent, +} from "../../components/ui/tooltip"; +import { AlertCircle } from "lucide-react"; +import type { ServiceConfig, Healthcheck } from "../../types/compose"; + +interface ServiceFormProps { + service: ServiceConfig; + restartOptions: Array<{ value: string; label: string }>; + selectedIdx: number | null; + services: ServiceConfig[]; + setServices: React.Dispatch>; + updateServiceField: (field: keyof ServiceConfig, value: any) => void; + updatePortField: ( + idx: number, + field: "host" | "container" | "protocol", + value: string + ) => void; + addPortField: () => void; + removePortField: (idx: number) => void; + updateListField: ( + field: keyof ServiceConfig, + idx: number, + value: any + ) => void; + addListField: (field: keyof ServiceConfig) => void; + removeListField: (field: keyof ServiceConfig, idx: number) => void; + updateVolumeField: ( + idx: number, + field: "host" | "container" | "read_only", + value: string | boolean + ) => void; + addVolumeField: () => void; + removeVolumeField: (idx: number) => void; + updateHealthcheckField: (field: keyof Healthcheck, value: string) => void; + updateDependsOn: (idx: number, value: string) => void; + addDependsOn: () => void; + removeDependsOn: (idx: number) => void; + updateResourceField: ( + type: "limits" | "reservations", + field: "cpus" | "memory", + value: string + ) => void; + updateSecurityOpt: (idx: number, value: string) => void; + addSecurityOpt: () => void; + removeSecurityOpt: (idx: number) => void; + updateCapAdd: (idx: number, value: string) => void; + addCapAdd: () => void; + removeCapAdd: (idx: number) => void; + updateCapDrop: (idx: number, value: string) => void; + addCapDrop: () => void; + removeCapDrop: (idx: number) => void; + updateSysctl: (idx: number, field: "key" | "value", value: string) => void; + addSysctl: () => void; + removeSysctl: (idx: number) => void; + updateDevice: (idx: number, value: string) => void; + addDevice: () => void; + removeDevice: (idx: number) => void; + updateTmpfs: (idx: number, value: string) => void; + addTmpfs: () => void; + removeTmpfs: (idx: number) => void; + updateUlimit: ( + idx: number, + field: "name" | "soft" | "hard", + value: string + ) => void; + addUlimit: () => void; + removeUlimit: (idx: number) => void; +} + +export function ServiceForm({ + service: svc, + restartOptions, + selectedIdx, + services, + setServices, + updateServiceField, + updatePortField, + addPortField, + removePortField, + updateListField, + addListField, + removeListField, + updateVolumeField, + addVolumeField, + removeVolumeField, + updateHealthcheckField, + updateDependsOn, + addDependsOn, + removeDependsOn, + updateResourceField, + updateSecurityOpt, + addSecurityOpt, + removeSecurityOpt, + updateCapAdd, + addCapAdd, + removeCapAdd, + updateCapDrop, + addCapDrop, + removeCapDrop, + updateSysctl, + addSysctl, + removeSysctl, + updateDevice, + addDevice, + removeDevice, + updateTmpfs, + addTmpfs, + removeTmpfs, + updateUlimit, + addUlimit, + removeUlimit, +}: ServiceFormProps) { + return ( +
+ {/* Header */} +
+
+

Service Configuration

+
+ + {/* Basic Info Section */} +
+

+
+ Basic Information +

+
+ + updateServiceField("name", e.target.value)} + placeholder="e.g. app, database, proxy" + className="shadow-sm" + /> +
+ +
+ + + updateServiceField("container_name", e.target.value) + } + placeholder="e.g. my-app-container" + className="shadow-sm" + /> +

Optional: Custom container name

+
+ +
+ + updateServiceField("image", e.target.value)} + placeholder="e.g. nginx:latest, mysql:8.0" + className="shadow-sm" + /> +
+ +
+
+ + updateServiceField("command", e.target.value)} + placeholder="e.g. npm start" + className="shadow-sm" + /> +
+
+ + + + + + + {restartOptions.map((opt) => ( + updateServiceField("restart", opt.value)} + > + {opt.label} + + ))} + + +
+
+
+ + {/* Networking Section */} +
+

+
+ Networking +

+ {/* Ports */} +
+
+ + +
+
+ {svc.ports.length > 0 ? ( + svc.ports.map((port, idx) => ( +
+ updatePortField(idx, "host", e.target.value)} + placeholder="Host" + className="flex-1 shadow-sm" + /> + → + + updatePortField(idx, "container", e.target.value) + } + placeholder="Container" + className="flex-1 shadow-sm" + /> + + + + + + updatePortField(idx, "protocol", "none")} + > + None + + updatePortField(idx, "protocol", "tcp")} + > + TCP + + updatePortField(idx, "protocol", "udp")} + > + UDP + + + + +
+ )) + ) : ( +
+ No port mappings added +
+ )} +
+
+ + {/* Expose */} +
+
+ + +
+
+ {svc.expose && svc.expose.length > 0 ? ( + svc.expose.map((port, idx) => ( +
+ + updateListField( + "expose", + idx, + e.target.value.replace(/[^0-9]/g, "") + ) + } + placeholder="Port number" + className="flex-1 shadow-sm" + /> + +
+ )) + ) : ( +
+ No exposed ports +
+ )} +
+
+
+ + {/* Storage Section */} +
+

+
+ Storage +

+ {/* Volumes */} +
+
+ +
+ Syntax: + + updateServiceField( + "volumes_syntax", + pressed ? "array" : "dict" + ) + } + aria-label="Array syntax" + className="border rounded px-2 py-1 text-xs" + > + Array + + + updateServiceField( + "volumes_syntax", + pressed ? "dict" : "array" + ) + } + aria-label="Dictionary syntax" + className="border rounded px-2 py-1 text-xs" + > + Dict + +
+
+
+ {svc.volumes.map((vol, idx) => ( +
+ + updateVolumeField(idx, "host", e.target.value) + } + placeholder="Host path/volume" + className="w-1/2" + /> + → + + updateVolumeField(idx, "container", e.target.value) + } + placeholder="Container path" + className="w-1/2" + /> +
+ + updateVolumeField(idx, "read_only", v) + } + aria-label="Read Only" + className="border rounded px-2 py-1" + > + RO + +
+ +
+ ))} + +
+
+
+ + {/* Environment Section */} +
+

+
+ Environment +

+ {/* Environment Variables */} +
+
+
+ + + + + + +

Privacy Notice

+

+ All information you add here stays in your browser and is + never sent to any server. Click the × button on each line to + remove variables, or use "Clear All" to remove them all at + once. +

+
+
+
+
+ Syntax: + + updateServiceField( + "environment_syntax", + pressed ? "array" : "dict" + ) + } + aria-label="Array syntax" + className="border rounded px-2 py-1 text-xs" + > + Array + + + updateServiceField( + "environment_syntax", + pressed ? "dict" : "array" + ) + } + aria-label="Dictionary syntax" + className="border rounded px-2 py-1 text-xs" + > + Dict + +
+
+
+ {svc.environment.map((env, idx) => ( +
+ + updateListField("environment", idx, { + ...env, + key: e.target.value, + }) + } + placeholder="KEY" + className="w-1/2" + /> + + updateListField("environment", idx, { + ...env, + value: e.target.value, + }) + } + placeholder="value" + className="w-1/2" + /> + +
+ ))} +
+ + {svc.environment.length > 0 && ( + + )} +
+
+
+
+ + {/* Advanced Section */} +
+ + + + + + {/* Healthcheck */} +
+ + + updateHealthcheckField("test", e.target.value) + } + placeholder="Test command (e.g. CMD curl -f http://localhost)" + /> +
+ + updateHealthcheckField("interval", e.target.value) + } + placeholder="Interval (e.g. 1m30s)" + className="w-1/2" + /> + + updateHealthcheckField("timeout", e.target.value) + } + placeholder="Timeout (e.g. 10s)" + className="w-1/2" + /> +
+
+ + updateHealthcheckField("retries", e.target.value) + } + placeholder="Retries (e.g. 3)" + className="w-1/2" + /> + + updateHealthcheckField("start_period", e.target.value) + } + placeholder="Start period (e.g. 40s)" + className="w-1/2" + /> +
+ + updateHealthcheckField("start_interval", e.target.value) + } + placeholder="Start interval (e.g. 5s)" + className="mt-2" + /> +
+ {/* Depends On */} +
+ +
+ {svc.depends_on?.map((dep, idx) => ( +
+ updateDependsOn(idx, e.target.value)} + placeholder="Service name" + /> + +
+ ))} + +
+
+ {/* Resource Allocation */} +
+ +
+
+ +
+
+ + + updateResourceField("limits", "cpus", e.target.value) + } + placeholder="e.g. 0.5 or 2" + /> +
+
+ + + updateResourceField("limits", "memory", e.target.value) + } + placeholder="e.g. 512m or 2g" + /> +
+
+
+
+ +
+
+ + + updateResourceField( + "reservations", + "cpus", + e.target.value + ) + } + placeholder="e.g. 0.25 or 1" + /> +
+
+ + + updateResourceField( + "reservations", + "memory", + e.target.value + ) + } + placeholder="e.g. 256m or 1g" + /> +
+
+
+
+
+ {/* Entrypoint */} +
+ + + updateServiceField("entrypoint", e.target.value) + } + placeholder="Entrypoint" + /> +
+ {/* Env File */} +
+ + updateServiceField("env_file", e.target.value)} + placeholder=".env file path" + /> +
+ {/* Extra Hosts */} +
+ + + updateServiceField("extra_hosts", e.target.value.split(",")) + } + placeholder="host1:ip1,host2:ip2" + /> +
+ {/* DNS */} +
+ + + updateServiceField("dns", e.target.value.split(",")) + } + placeholder="8.8.8.8,8.8.4.4" + /> +
+ {/* Networks */} +
+ + + updateServiceField("networks", e.target.value.split(",")) + } + placeholder="network1,network2" + /> +
+ {/* User */} +
+ + updateServiceField("user", e.target.value)} + placeholder="user" + /> +
+ {/* Working Dir */} +
+ + + updateServiceField("working_dir", e.target.value) + } + placeholder="/app" + /> +
+ {/* Labels */} +
+ +
+ {svc.labels?.map((label, idx) => ( +
+ { + const newLabels = [...(svc.labels || [])]; + newLabels[idx] = { + ...newLabels[idx], + key: e.target.value, + }; + updateServiceField("labels", newLabels); + }} + placeholder="Key" + className="w-1/2" + /> + { + const newLabels = [...(svc.labels || [])]; + newLabels[idx] = { + ...newLabels[idx], + value: e.target.value, + }; + updateServiceField("labels", newLabels); + }} + placeholder="Value" + className="w-1/2" + /> + +
+ ))} + +
+
+ {/* Privileged */} +
+ updateServiceField("privileged", v)} + aria-label="Privileged" + className="border rounded px-2 py-1" + > + Privileged + +
+ {/* Read Only */} +
+ updateServiceField("read_only", v)} + aria-label="Read Only" + className="border rounded px-2 py-1" + > + Read Only + +
+ {/* Shared Memory Size */} +
+ + updateServiceField("shm_size", e.target.value)} + placeholder="e.g. 1gb, 512m" + /> +
+ {/* Security Options */} +
+ +
+ {svc.security_opt?.map((opt, idx) => ( +
+ updateSecurityOpt(idx, e.target.value)} + placeholder="e.g. seccomp:unconfined" + /> + +
+ ))} + +
+
+ {/* Network Mode */} +
+ + + updateServiceField("network_mode", e.target.value) + } + placeholder="e.g. host, bridge, none, service:name" + /> +

+ Options: host, bridge, none, service:service_name +

+
+ {/* Cap Add */} +
+ +
+ {svc.cap_add?.map((cap, idx) => ( +
+ updateCapAdd(idx, e.target.value)} + placeholder="e.g. NET_ADMIN, SYS_MODULE" + /> + +
+ ))} + +
+
+ {/* Cap Drop */} +
+ +
+ {svc.cap_drop?.map((cap, idx) => ( +
+ updateCapDrop(idx, e.target.value)} + placeholder="e.g. ALL, CHOWN" + /> + +
+ ))} + +
+
+ {/* Sysctls */} +
+ +
+ {svc.sysctls?.map((sysctl, idx) => ( +
+ + updateSysctl(idx, "key", e.target.value) + } + placeholder="Key (e.g. net.ipv4.ip_forward)" + className="w-1/2" + /> + + updateSysctl(idx, "value", e.target.value) + } + placeholder="Value (e.g. 1)" + className="w-1/2" + /> + +
+ ))} + +
+
+ {/* Devices */} +
+ +
+ {svc.devices?.map((device, idx) => ( +
+ updateDevice(idx, e.target.value)} + placeholder="e.g. /dev/ttyUSB0:/dev/ttyUSB0" + /> + +
+ ))} + +
+
+ {/* Tmpfs */} +
+ +
+ {svc.tmpfs?.map((tmpfs, idx) => ( +
+ updateTmpfs(idx, e.target.value)} + placeholder="e.g. /tmp:rw,noexec,nosuid,size=100m" + /> + +
+ ))} + +
+
+ {/* Ulimits */} +
+ +
+ {svc.ulimits?.map((ulimit, idx) => ( +
+ + updateUlimit(idx, "name", e.target.value) + } + placeholder="Name (e.g. nofile)" + className="w-1/3" + /> + + updateUlimit(idx, "soft", e.target.value) + } + placeholder="Soft limit" + className="w-1/3" + /> + + updateUlimit(idx, "hard", e.target.value) + } + placeholder="Hard limit" + className="w-1/3" + /> + +
+ ))} + +
+
+ {/* Init */} +
+ updateServiceField("init", v)} + aria-label="Init" + className="border rounded px-2 py-1" + > + Init (PID 1) + +
+ {/* Stop Grace Period */} +
+ + + updateServiceField("stop_grace_period", e.target.value) + } + placeholder="e.g. 10s, 1m30s" + /> +
+ {/* Stop Signal */} +
+ + + updateServiceField("stop_signal", e.target.value) + } + placeholder="e.g. SIGTERM, SIGKILL" + /> +
+ {/* TTY */} +
+ updateServiceField("tty", v)} + aria-label="TTY" + className="border rounded px-2 py-1" + > + TTY + +
+ {/* Stdin Open */} +
+ updateServiceField("stdin_open", v)} + aria-label="Stdin Open" + className="border rounded px-2 py-1" + > + Stdin Open + +
+ {/* Hostname */} +
+ + updateServiceField("hostname", e.target.value)} + placeholder="Container hostname" + /> +
+ {/* Domainname */} +
+ + + updateServiceField("domainname", e.target.value) + } + placeholder="Container domainname" + /> +
+ {/* MAC Address */} +
+ + + updateServiceField("mac_address", e.target.value) + } + placeholder="e.g. 02:42:ac:11:65:43" + /> +
+ {/* IPC Mode */} +
+ + updateServiceField("ipc_mode", e.target.value)} + placeholder="e.g. host, container:name, shareable" + /> +
+ {/* PID */} +
+ + updateServiceField("pid", e.target.value)} + placeholder="e.g. host, container:name" + /> +
+ {/* UTS */} +
+ + updateServiceField("uts", e.target.value)} + placeholder="e.g. host, container:name" + /> +
+ {/* Cgroup Parent */} +
+ + + updateServiceField("cgroup_parent", e.target.value) + } + placeholder="e.g. /system.slice" + /> +
+ {/* Isolation */} +
+ + + updateServiceField("isolation", e.target.value) + } + placeholder="e.g. default, process, hyperv" + /> +
+
+
+
+
+ ); +} + diff --git a/src/components/compose-builder/ServiceListSidebar.tsx b/src/components/compose-builder/ServiceListSidebar.tsx new file mode 100644 index 0000000..ed0211e --- /dev/null +++ b/src/components/compose-builder/ServiceListSidebar.tsx @@ -0,0 +1,152 @@ +import { Button } from "../ui/button"; +import { Card } from "../ui/card"; +import { Separator } from "../ui/separator"; +import type { ServiceConfig } from "../../types/compose"; +import type { UseTemplateStoreReturn } from "../../hooks/useTemplateStore"; +import { TemplateStoreModal } from "../templates/TemplateStoreModal"; + +interface ServiceListSidebarProps { + services: ServiceConfig[]; + selectedIdx: number | null; + selectedType: "service" | "network" | "volume"; + onSelectService: (idx: number) => void; + onAddService: () => void; + onRemoveService: (idx: number) => void; + templateStore: UseTemplateStoreReturn; +} + +export function ServiceListSidebar({ + services, + selectedIdx, + selectedType, + onSelectService, + onAddService, + onRemoveService, + templateStore, +}: ServiceListSidebarProps) { + return ( + + ); +} + diff --git a/src/components/compose-builder/VolumeForm.tsx b/src/components/compose-builder/VolumeForm.tsx new file mode 100644 index 0000000..1208d85 --- /dev/null +++ b/src/components/compose-builder/VolumeForm.tsx @@ -0,0 +1,193 @@ +import { Label } from "../../components/ui/label"; +import { Input } from "../../components/ui/input"; +import { Button } from "../../components/ui/button"; +import { Toggle } from "../../components/ui/toggle"; +import type { VolumeConfig } from "../../types/compose"; + +interface VolumeFormProps { + volume: VolumeConfig; + onUpdate: (field: keyof VolumeConfig, value: any) => void; +} + +export function VolumeForm({ volume, onUpdate }: VolumeFormProps) { + return ( +
+ {/* Header */} +
+
+

Volume Configuration

+
+ + {/* Basic Settings */} +
+
+ + onUpdate("name", e.target.value)} + placeholder="e.g. app-data" + className="shadow-sm" + /> +
+ +
+ + onUpdate("driver", e.target.value)} + placeholder="e.g. local" + className="shadow-sm" + /> +

Default: local

+
+
+ + {/* Driver Options */} +
+

+
+ Driver Options +

+
+
+ + onUpdate("driver_opts_type", e.target.value)} + placeholder="e.g. none, nfs" + className="shadow-sm" + /> +
+
+ + onUpdate("driver_opts_device", e.target.value)} + placeholder="e.g. /path/to/device or nfs-server:/path" + className="shadow-sm" + /> +
+
+ + onUpdate("driver_opts_o", e.target.value)} + placeholder="e.g. bind, ro" + className="shadow-sm" + /> +
+
+
+ {/* Labels */} +
+
+

+
+ Labels +

+ +
+
+ {volume.labels && volume.labels.length > 0 ? ( + volume.labels.map((label, idx) => ( +
+ { + const newLabels = [...(volume.labels || [])]; + newLabels[idx] = { + ...newLabels[idx], + key: e.target.value, + }; + onUpdate("labels", newLabels); + }} + placeholder="Key" + className="flex-1 shadow-sm" + /> + { + const newLabels = [...(volume.labels || [])]; + newLabels[idx] = { + ...newLabels[idx], + value: e.target.value, + }; + onUpdate("labels", newLabels); + }} + placeholder="Value" + className="flex-1 shadow-sm" + /> + +
+ )) + ) : ( +
+ No labels added +
+ )} +
+
+ + {/* External Volume */} +
+

+
+ External Volume +

+ onUpdate("external", v)} + aria-label="External" + className="border rounded-md px-3 py-2 h-auto w-full justify-center data-[state=on]:bg-primary/10 data-[state=on]:border-primary transition-all" + > + Use External Volume + + {volume.external && ( +
+ + onUpdate("name_external", e.target.value)} + placeholder="Existing volume name" + className="shadow-sm" + /> +

Reference an existing volume

+
+ )} +
+
+ ); +} + diff --git a/src/components/compose-builder/VpnConfigSection.tsx b/src/components/compose-builder/VpnConfigSection.tsx new file mode 100644 index 0000000..5a23162 --- /dev/null +++ b/src/components/compose-builder/VpnConfigSection.tsx @@ -0,0 +1,771 @@ +import { Button } from "../ui/button"; +import { Input } from "../ui/input"; +import { Label } from "../ui/label"; +import { Separator } from "../ui/separator"; +import { Checkbox } from "../ui/checkbox"; +import { + Collapsible, + CollapsibleTrigger, + CollapsibleContent, +} from "../ui/collapsible"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "../ui/select"; +import { Alert, AlertTitle, AlertDescription } from "../ui/alert"; +import { Shield, ChevronDown, ChevronRight, AlertCircle } from "lucide-react"; +import type { VPNConfig } from "../../types/vpn-configs"; +import type { ServiceConfig, NetworkConfig } from "../../types/compose"; + +interface VpnConfigSectionProps { + vpnConfig: VPNConfig; + vpnConfigOpen: boolean; + setVpnConfigOpen: (open: boolean) => void; + updateVpnType: (type: VPNConfig["type"] | null) => void; + updateTailscaleConfig: (updates: Partial) => void; + updateNewtConfig: (updates: Partial) => void; + updateCloudflaredConfig: (updates: Partial) => void; + updateWireguardConfig: (updates: Partial) => void; + updateZerotierConfig: (updates: Partial) => void; + updateNetbirdConfig: (updates: Partial) => void; + updateServicesUsingVpn: (services: string[]) => void; + updateVpnNetworks: (networks: string[]) => void; + services: ServiceConfig[]; + networks: NetworkConfig[]; +} + +export function VpnConfigSection({ + vpnConfig, + vpnConfigOpen, + setVpnConfigOpen, + updateVpnType, + updateTailscaleConfig, + updateNewtConfig, + updateCloudflaredConfig, + updateWireguardConfig, + updateZerotierConfig, + updateNetbirdConfig, + updateServicesUsingVpn, + updateVpnNetworks, + services, + networks, +}: VpnConfigSectionProps) { + return ( + <> + +
+ + + +
+ +
+
+ + +
+ + {vpnConfig && + vpnConfig.enabled && + vpnConfig.type === "tailscale" && + vpnConfig.tailscale && ( +
+
+ + + updateTailscaleConfig({ authKey: e.target.value }) + } + placeholder="${TS_AUTHKEY}" + /> +

+ Get from Tailscale admin console +

+
+
+ + + updateTailscaleConfig({ hostname: e.target.value }) + } + placeholder="my-service" + /> +
+
+ + updateTailscaleConfig({ acceptDns: checked === true }) + } + /> + +
+
+ + updateTailscaleConfig({ authOnce: checked === true }) + } + /> + +
+
+ + updateTailscaleConfig({ userspace: checked === true }) + } + /> + +
+
+ + + updateTailscaleConfig({ exitNode: e.target.value }) + } + placeholder="Exit node IP or hostname" + /> +
+ {vpnConfig.tailscale.exitNode && ( +
+ + updateTailscaleConfig({ + exitNodeAllowLan: checked === true, + }) + } + /> + +
+ )} +
+ + updateTailscaleConfig({ enableServe: checked === true }) + } + /> + +
+ {vpnConfig.tailscale.enableServe && ( +
+
+ + +
+
+ + + updateTailscaleConfig({ + serveExternalPort: e.target.value, + }) + } + placeholder="443" + /> +
+
+ + + updateTailscaleConfig({ + serveInternalPort: e.target.value, + }) + } + placeholder="8080" + /> +
+
+ + + updateTailscaleConfig({ + servePath: e.target.value, + }) + } + placeholder="/" + /> +
+
+ + +
+
+ + +

+ Protocol used to connect to your internal service +

+
+
+ + + updateTailscaleConfig({ + certDomain: e.target.value, + }) + } + placeholder="${TS_CERT_DOMAIN}" + /> +
+
+ )} +
+ )} + + {vpnConfig && + vpnConfig.enabled && + vpnConfig.type === "newt" && + vpnConfig.newt && ( +
+
+ + + updateNewtConfig({ endpoint: e.target.value }) + } + placeholder="https://app.pangolin.net" + /> +
+
+ + + updateNewtConfig({ newtId: e.target.value }) + } + placeholder="${NEWT_ID}" + /> +
+
+ + + updateNewtConfig({ newtSecret: e.target.value }) + } + placeholder="${NEWT_SECRET}" + type="password" + /> +
+
+ + + updateNewtConfig({ networkName: e.target.value }) + } + placeholder="newt" + /> +
+
+ )} + + {vpnConfig && + vpnConfig.enabled && + vpnConfig.type === "cloudflared" && + vpnConfig.cloudflared && ( +
+
+ + + updateCloudflaredConfig({ + tunnelToken: e.target.value, + }) + } + placeholder="${TUNNEL_TOKEN}" + type="password" + /> +

+ Get from Cloudflare dashboard +

+
+
+ + updateCloudflaredConfig({ + noAutoupdate: checked === true, + }) + } + /> + +
+
+ )} + + {vpnConfig && + vpnConfig.enabled && + vpnConfig.type === "wireguard" && + vpnConfig.wireguard && ( +
+
+ + + updateWireguardConfig({ + configPath: e.target.value, + }) + } + placeholder="/etc/wireguard/wg0.conf" + /> +
+
+ + + updateWireguardConfig({ + interfaceName: e.target.value, + }) + } + placeholder="wg0" + /> +
+
+ )} + + {vpnConfig && + vpnConfig.enabled && + vpnConfig.type === "zerotier" && + vpnConfig.zerotier && ( +
+
+ + + updateZerotierConfig({ + networkId: e.target.value, + }) + } + placeholder="${ZT_NETWORK_ID}" + /> +
+
+ + + updateZerotierConfig({ + identityPath: e.target.value, + }) + } + placeholder="/var/lib/zerotier-one" + /> +
+
+ )} + + {vpnConfig && + vpnConfig.enabled && + vpnConfig.type === "netbird" && + vpnConfig.netbird && ( +
+
+ + + updateNetbirdConfig({ + setupKey: e.target.value, + }) + } + placeholder="${NETBIRD_SETUP_KEY}" + type="password" + /> +
+
+ + + updateNetbirdConfig({ + managementUrl: e.target.value, + }) + } + placeholder="https://api.netbird.io" + /> +
+
+ )} + + {vpnConfig && vpnConfig.enabled && ( + <> + {(() => { + let hasErrors = false; + let errorMessage = ""; + + if (!vpnConfig) return null; + + if ( + vpnConfig.type === "tailscale" && + vpnConfig.tailscale + ) { + if (!vpnConfig.tailscale.authKey) { + hasErrors = true; + errorMessage = "Tailscale Auth Key is required"; + } + if ( + vpnConfig.tailscale.enableServe && + !vpnConfig.tailscale.serveTargetService + ) { + hasErrors = true; + errorMessage = + "Target service is required when Serve is enabled"; + } + } else if ( + vpnConfig.type === "newt" && + vpnConfig.newt + ) { + if ( + !vpnConfig.newt.newtId || + !vpnConfig.newt.newtSecret + ) { + hasErrors = true; + errorMessage = "Newt ID and Secret are required"; + } + } else if ( + vpnConfig.type === "cloudflared" && + vpnConfig.cloudflared + ) { + if (!vpnConfig.cloudflared.tunnelToken) { + hasErrors = true; + errorMessage = + "Cloudflared Tunnel Token is required"; + } + } else if ( + vpnConfig.type === "zerotier" && + vpnConfig.zerotier + ) { + if (!vpnConfig.zerotier.networkId) { + hasErrors = true; + errorMessage = "ZeroTier Network ID is required"; + } + } else if ( + vpnConfig.type === "netbird" && + vpnConfig.netbird + ) { + if (!vpnConfig.netbird.setupKey) { + hasErrors = true; + errorMessage = "Netbird Setup Key is required"; + } + } + + if (vpnConfig.servicesUsingVpn.length === 0) { + hasErrors = true; + errorMessage = + "At least one service must be selected to use VPN"; + } + + return hasErrors ? ( + + + Configuration Warning + + {errorMessage} + + + ) : null; + })()} +
+ + {services.filter((s) => s.name).length === 0 ? ( +

+ Add services first +

+ ) : ( +
+ {services + .filter((s) => s.name) + .map((svc) => ( +
+ { + const newServices = checked + ? [ + ...vpnConfig.servicesUsingVpn, + svc.name, + ] + : vpnConfig.servicesUsingVpn.filter( + (n) => n !== svc.name + ); + updateServicesUsingVpn(newServices); + }} + /> + + {vpnConfig.type && + ["tailscale", "cloudflared"].includes( + vpnConfig.type + ) && + vpnConfig.servicesUsingVpn.includes( + svc.name + ) && ( + + (network_mode) + + )} +
+ ))} +
+ )} +
+ +
+ + {networks.filter((n) => n.name).length === 0 ? ( +

+ Add networks first +

+ ) : ( +
+ {networks + .filter((n) => n.name) + .map((net) => ( +
+ { + const currentNetworks = vpnConfig.networks || []; + const newNetworks = checked + ? [...currentNetworks, net.name] + : currentNetworks.filter((n) => n !== net.name); + updateVpnNetworks(newNetworks); + }} + /> + +
+ ))} +
+ )} +
+ + )} +
+
+
+ + + ); +} + diff --git a/src/components/templates/TemplateCard.tsx b/src/components/templates/TemplateCard.tsx index 65e924e..43c1d42 100644 --- a/src/components/templates/TemplateCard.tsx +++ b/src/components/templates/TemplateCard.tsx @@ -1,5 +1,6 @@ import { Settings } from "lucide-react"; import { Card, CardContent } from "../ui/card"; +import { Button } from "../ui/button"; export interface TemplateCardProps { id: string; @@ -21,33 +22,35 @@ export function TemplateCard({ }: TemplateCardProps) { return ( - + {/* Header with logo and name */} -
+
{logo ? ( {name} { (e.target as HTMLImageElement).style.display = "none"; }} /> ) : ( -
+
)} -
-

- {name} -

+
+
+

+ {name} +

+
{version && ( -

- v{version} +

+ {version}

)}
@@ -55,29 +58,39 @@ export function TemplateCard({ {/* Description */} {description && ( -

+

{description}

)} {/* Tags */} - {tags && tags.length > 0 && ( -
- {tags.slice(0, 3).map((tag) => ( - - {tag} - - ))} - {tags.length > 3 && ( - - +{tags.length - 3} - - )} -
- )} +
+ {tags && tags.length > 0 && ( +
+ {tags.slice(0, 3).map((tag) => ( + + {tag} + + ))} + {tags.length > 3 && ( + + +{tags.length - 3} + + )} +
+ )} + + +
); diff --git a/src/components/templates/TemplateStoreModal.tsx b/src/components/templates/TemplateStoreModal.tsx index 073e712..ef8ffde 100644 --- a/src/components/templates/TemplateStoreModal.tsx +++ b/src/components/templates/TemplateStoreModal.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, useMemo, useEffect } from "react"; import { Dialog, DialogContent, @@ -10,8 +10,17 @@ import { Input } from "../ui/input"; import { Button } from "../ui/button"; import { EmptyState } from "../ui/empty-state"; import { Skeleton } from "../ui/skeleton"; -import { RefreshCw, Package, AlertCircle } from "lucide-react"; +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "../ui/dropdown-menu"; +import { RefreshCw, Package, AlertCircle, X, ChevronDown } from "lucide-react"; import { TemplateCard } from "./TemplateCard"; +import { useSearchParams } from "../../hooks/use-search-params"; export interface Template { id: string; @@ -43,83 +52,187 @@ export function TemplateStoreModal({ onRefresh, onTemplateSelect, }: TemplateStoreModalProps) { + const [searchParams, setSearchParams] = useSearchParams(); const [searchQuery, setSearchQuery] = useState(""); + const [selectedTags, setSelectedTags] = useState([]); + const [filteredTemplates, setFilteredTemplates] = useState(templates); - const filteredTemplates = templates.filter( - (template) => - template.name?.toLowerCase().includes(searchQuery.toLowerCase()) || - template.description?.toLowerCase().includes(searchQuery.toLowerCase()) || - template.tags?.some((tag) => - tag.toLowerCase().includes(searchQuery.toLowerCase()) - ) - ); + // Get all unique tags, sorted with selected ones first + const uniqueTags = useMemo(() => { + if (!templates || templates.length === 0) return []; + + const allTags = Array.from( + new Set(templates.flatMap((template) => template.tags || [])) + ).sort(); + + if (selectedTags.length === 0) return allTags; + + const selected = allTags.filter(tag => selectedTags.includes(tag)); + const unselected = allTags.filter(tag => !selectedTags.includes(tag)); + + return [...selected, ...unselected]; + }, [templates, selectedTags]); + + // Initialize search query from URL params + useEffect(() => { + const queryFromUrl = searchParams.get("q") || ""; + if (queryFromUrl !== searchQuery) { + setSearchQuery(queryFromUrl); + } + }, [searchParams]); + + // Apply filters + useEffect(() => { + if (templates) { + const filtered = templates.filter((template) => { + const searchTerm = searchQuery.toLowerCase(); + const matchesSearch = + template.name?.toLowerCase().includes(searchTerm) || + template.description?.toLowerCase().includes(searchTerm); + + const matchesTags = + selectedTags.length === 0 || + selectedTags.every((tag) => template.tags?.includes(tag)); + + return matchesSearch && matchesTags; + }); + setFilteredTemplates(filtered); + } + }, [templates, searchQuery, selectedTags]); + + // Update URL params when search query changes + const handleSearchChange = (e: React.ChangeEvent) => { + const newQuery = e.target.value; + setSearchQuery(newQuery); + if (newQuery) { + setSearchParams({ q: newQuery }); + } else { + searchParams.delete("q"); + setSearchParams(searchParams); + } + }; + + const toggleTag = (tag: string) => { + setSelectedTags((prev) => + prev.includes(tag) + ? prev.filter((t) => t !== tag) + : [...prev, tag] + ); + }; return ( - - + +
-
- +
+ Template Marketplace - - Browse and import pre-configured Docker Compose templates. - -
- - Templates from{" "} - - Dokploy/templates - - + + Browse and import templates with pre-configured Docker Compose files. {cacheTimestamp && ( - <> - • - - Cached{" "} - {Math.round((Date.now() - cacheTimestamp) / 60000)}m ago - - + + (Cached {Math.round((Date.now() - cacheTimestamp) / 60000)}m ago) + )} + +
+ Templates from{" "} + + hhftechnology/Marketplace + {" "} + repository.
- +
+ + +
- {/* Search Bar */} -
- setSearchQuery(e.target.value)} - className="h-10" - /> + {/* Search and Filter Bar */} +
+
+ + Available Templates + + + {filteredTemplates.length} + +
+
+ + + + + + + Filter by Tags + + {uniqueTags.map((tag) => ( + toggleTag(tag)} + > + {tag} + + ))} + {uniqueTags.length === 0 && ( +
+ No tags available +
+ )} +
+
+
{/* Content Area */} -
+
{loading ? ( ) : error ? ( -
+
) : filteredTemplates.length === 0 ? ( -
+
) : ( -
+
{filteredTemplates.map((template) => ( onTemplateSelect(template)} @@ -181,18 +294,18 @@ export function TemplateStoreModal({ function TemplateGridSkeleton() { return ( -
+
{Array.from({ length: 8 }).map((_, i) => (
- -
+ +
-
+
diff --git a/src/components/ui/sidebar.tsx b/src/components/ui/sidebar.tsx index 3c084de..9fc77e4 100644 --- a/src/components/ui/sidebar.tsx +++ b/src/components/ui/sidebar.tsx @@ -68,7 +68,7 @@ function SidebarProvider({ const isMobile = useIsMobile(); const [openMobile, setOpenMobile] = React.useState(false); - // Read sidebar state from cookie on mount + // Read sidebar state from cookie on mount, but respect defaultOpen if no cookie exists const [_open, _setOpen] = React.useState(() => { if (typeof document === "undefined") return defaultOpen; const cookies = document.cookie.split("; "); @@ -261,24 +261,31 @@ function SidebarTrigger({ onClick, ...props }: React.ComponentProps) { - const { toggleSidebar } = useSidebar(); + const { toggleSidebar, open } = useSidebar(); return ( - + + + + + +

{open ? "Close sidebar" : "Open sidebar"} (Ctrl/Cmd+B)

+
+
); } @@ -340,7 +347,7 @@ function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
); diff --git a/src/hooks/use-search-params.ts b/src/hooks/use-search-params.ts new file mode 100644 index 0000000..22540d7 --- /dev/null +++ b/src/hooks/use-search-params.ts @@ -0,0 +1,26 @@ +import { useState, useEffect, useCallback } from 'react'; + +export function useSearchParams() { + const [searchParams, setSearchParamsState] = useState( + new URLSearchParams(window.location.search) + ); + + useEffect(() => { + const handlePopState = () => { + setSearchParamsState(new URLSearchParams(window.location.search)); + }; + window.addEventListener('popstate', handlePopState); + return () => window.removeEventListener('popstate', handlePopState); + }, []); + + const setSearchParams = useCallback((newParams: Record | URLSearchParams) => { + const nextParams = new URLSearchParams( + newParams instanceof URLSearchParams ? newParams : newParams + ); + const newUrl = `${window.location.pathname}?${nextParams.toString()}`; + window.history.pushState({}, '', newUrl); + setSearchParamsState(nextParams); + }, []); + + return [searchParams, setSearchParams] as const; +} diff --git a/src/hooks/useNetworkVolumeManager.ts b/src/hooks/useNetworkVolumeManager.ts index 94a0893..da84d26 100644 --- a/src/hooks/useNetworkVolumeManager.ts +++ b/src/hooks/useNetworkVolumeManager.ts @@ -8,6 +8,7 @@ export interface UseNetworkVolumeManagerReturn { selectedNetworkIdx: number | null; setSelectedNetworkIdx: (idx: number | null) => void; addNetwork: () => void; + bulkAddNetworks: (networks: NetworkConfig[]) => void; updateNetwork: (idx: number, field: keyof NetworkConfig, value: any) => void; removeNetwork: (idx: number) => void; // Volumes @@ -15,6 +16,7 @@ export interface UseNetworkVolumeManagerReturn { selectedVolumeIdx: number | null; setSelectedVolumeIdx: (idx: number | null) => void; addVolume: () => void; + bulkAddVolumes: (volumes: VolumeConfig[]) => void; updateVolume: (idx: number, field: keyof VolumeConfig, value: any) => void; removeVolume: (idx: number) => void; } @@ -164,17 +166,36 @@ export function useNetworkVolumeManager({ }); }, [setServices, onSelectionChange]); + // Bulk add methods for template imports + const bulkAddNetworks = useCallback((newNetworks: NetworkConfig[]) => { + setNetworks((prev) => { + const existingNames = new Set(prev.map((n) => n.name)); + const uniqueNetworks = newNetworks.filter((n) => !existingNames.has(n.name)); + return [...prev, ...uniqueNetworks]; + }); + }, []); + + const bulkAddVolumes = useCallback((newVolumes: VolumeConfig[]) => { + setVolumes((prev) => { + const existingNames = new Set(prev.map((v) => v.name)); + const uniqueVolumes = newVolumes.filter((v) => !existingNames.has(v.name)); + return [...prev, ...uniqueVolumes]; + }); + }, []); + return { networks, selectedNetworkIdx, setSelectedNetworkIdx, addNetwork, + bulkAddNetworks, updateNetwork, removeNetwork, volumes, selectedVolumeIdx, setSelectedVolumeIdx, addVolume, + bulkAddVolumes, updateVolume, removeVolume, }; diff --git a/src/hooks/useSelectionState.ts b/src/hooks/useSelectionState.ts new file mode 100644 index 0000000..17260eb --- /dev/null +++ b/src/hooks/useSelectionState.ts @@ -0,0 +1,64 @@ +import { useState, useCallback } from "react"; + +export interface UseSelectionStateReturn { + selectedIdx: number | null; + selectedType: "service" | "network" | "volume"; + selectedNetworkIdx: number | null; + selectedVolumeIdx: number | null; + setSelectedIdx: (idx: number | null) => void; + setSelectedType: (type: "service" | "network" | "volume") => void; + setSelectedNetworkIdx: (idx: number | null) => void; + setSelectedVolumeIdx: (idx: number | null) => void; + selectService: (idx: number | null) => void; + selectNetwork: (idx: number | null) => void; + selectVolume: (idx: number | null) => void; +} + +export function useSelectionState(): UseSelectionStateReturn { + const [selectedIdx, setSelectedIdx] = useState(0); + const [selectedType, setSelectedType] = useState< + "service" | "network" | "volume" + >("service"); + const [selectedNetworkIdx, setSelectedNetworkIdx] = useState( + null + ); + const [selectedVolumeIdx, setSelectedVolumeIdx] = useState( + null + ); + + const selectService = useCallback((idx: number | null) => { + setSelectedIdx(idx); + setSelectedType("service"); + setSelectedNetworkIdx(null); + setSelectedVolumeIdx(null); + }, []); + + const selectNetwork = useCallback((idx: number | null) => { + setSelectedNetworkIdx(idx); + setSelectedType("network"); + setSelectedIdx(null); + setSelectedVolumeIdx(null); + }, []); + + const selectVolume = useCallback((idx: number | null) => { + setSelectedVolumeIdx(idx); + setSelectedType("volume"); + setSelectedIdx(null); + setSelectedNetworkIdx(null); + }, []); + + return { + selectedIdx, + selectedType, + selectedNetworkIdx, + selectedVolumeIdx, + setSelectedIdx, + setSelectedType, + setSelectedNetworkIdx, + setSelectedVolumeIdx, + selectService, + selectNetwork, + selectVolume, + }; +} + diff --git a/src/hooks/useServiceUpdater.ts b/src/hooks/useServiceUpdater.ts new file mode 100644 index 0000000..77004d9 --- /dev/null +++ b/src/hooks/useServiceUpdater.ts @@ -0,0 +1,398 @@ +import { useState, useCallback } from "react"; +import type { ServiceConfig, Healthcheck } from "../types/compose"; +import { defaultService } from "../utils/default-configs"; + +export function useServiceUpdater( + initialServices: ServiceConfig[] = [defaultService()] +) { + const [services, setServices] = useState(initialServices); + + // Helper to get new services array with validation + const getNewServices = useCallback( + (selectedIdx: number | null): [ServiceConfig[], number] | null => { + if (typeof selectedIdx !== "number") return null; + return [[...services], selectedIdx]; + }, + [services] + ); + + // Service field updates + const updateServiceField = useCallback( + (selectedIdx: number | null, field: keyof ServiceConfig, value: any) => { + const result = getNewServices(selectedIdx); + if (!result) return; + const [newServices, idx] = result; + (newServices[idx] as any)[field] = value; + setServices(newServices); + }, + [getNewServices] + ); + + // List field updates (for environment, etc.) + const updateListField = useCallback( + ( + selectedIdx: number | null, + field: keyof ServiceConfig, + idx: number, + value: any + ) => { + const result = getNewServices(selectedIdx); + if (!result) return; + const [newServices, sIdx] = result; + (newServices[sIdx][field] as any[])[idx] = value; + setServices(newServices); + }, + [getNewServices] + ); + + const addListField = useCallback( + (selectedIdx: number | null, field: keyof ServiceConfig) => { + const result = getNewServices(selectedIdx); + if (!result) return; + const [newServices, idx] = result; + if (field === "environment") { + newServices[idx].environment.push({ key: "", value: "" }); + } else { + (newServices[idx][field] as any[]).push(""); + } + setServices(newServices); + }, + [getNewServices] + ); + + const removeListField = useCallback( + (selectedIdx: number | null, field: keyof ServiceConfig, idx: number) => { + const result = getNewServices(selectedIdx); + if (!result) return; + const [newServices, sIdx] = result; + (newServices[sIdx][field] as any[]).splice(idx, 1); + setServices(newServices); + }, + [getNewServices] + ); + + // String array field updates + const updateStringArrayField = useCallback( + ( + selectedIdx: number | null, + field: keyof ServiceConfig, + idx: number, + value: string + ) => { + const result = getNewServices(selectedIdx); + if (!result) return; + const [newServices, sIdx] = result; + const service = newServices[sIdx]; + const arrayField = (service as any)[field] as string[] | undefined; + if (!arrayField) { + (service as any)[field] = []; + } + ((service as any)[field] as string[])[idx] = value; + setServices(newServices); + }, + [getNewServices] + ); + + const addStringArrayField = useCallback( + (selectedIdx: number | null, field: keyof ServiceConfig) => { + const result = getNewServices(selectedIdx); + if (!result) return; + const [newServices, idx] = result; + const service = newServices[idx]; + const arrayField = (service as any)[field] as string[] | undefined; + if (!arrayField) { + (service as any)[field] = []; + } + ((service as any)[field] as string[]).push(""); + setServices(newServices); + }, + [getNewServices] + ); + + const removeStringArrayField = useCallback( + (selectedIdx: number | null, field: keyof ServiceConfig, idx: number) => { + const result = getNewServices(selectedIdx); + if (!result) return; + const [newServices, sIdx] = result; + const service = newServices[sIdx]; + const arrayField = (service as any)[field] as string[] | undefined; + if (arrayField) { + arrayField.splice(idx, 1); + setServices(newServices); + } + }, + [getNewServices] + ); + + // Service management + const addService = useCallback(() => { + const newServices = [...services, defaultService()]; + setServices(newServices); + return services.length; // Return new index + }, [services]); + + const removeService = useCallback((idx: number) => { + const newServices = services.filter((_, i) => i !== idx); + const finalServices = + newServices.length === 0 ? [defaultService()] : newServices; + setServices(finalServices); + return finalServices.length - 1; // Return safe index + }, [services]); + + // Port field updates + const updatePortField = useCallback( + ( + selectedIdx: number | null, + idx: number, + field: "host" | "container" | "protocol", + value: string + ) => { + const result = getNewServices(selectedIdx); + if (!result) return; + const [newServices, sIdx] = result; + if (field === "protocol") { + newServices[sIdx].ports[idx][field] = value; + } else { + newServices[sIdx].ports[idx][field] = value.replace(/[^0-9]/g, ""); + } + setServices(newServices); + }, + [getNewServices] + ); + + const addPortField = useCallback( + (selectedIdx: number | null) => { + const result = getNewServices(selectedIdx); + if (!result) return; + const [newServices, idx] = result; + newServices[idx].ports.push({ + host: "", + container: "", + protocol: "none", + }); + setServices(newServices); + }, + [getNewServices] + ); + + const removePortField = useCallback( + (selectedIdx: number | null, idx: number) => { + const result = getNewServices(selectedIdx); + if (!result) return; + const [newServices, sIdx] = result; + newServices[sIdx].ports.splice(idx, 1); + setServices(newServices); + }, + [getNewServices] + ); + + // Volume field updates + const updateVolumeField = useCallback( + ( + selectedIdx: number | null, + idx: number, + field: "host" | "container" | "read_only", + value: string | boolean + ) => { + const result = getNewServices(selectedIdx); + if (!result) return; + const [newServices, sIdx] = result; + (newServices[sIdx].volumes[idx] as any)[field] = value; + setServices(newServices); + }, + [getNewServices] + ); + + const addVolumeField = useCallback( + (selectedIdx: number | null) => { + const result = getNewServices(selectedIdx); + if (!result) return; + const [newServices, idx] = result; + newServices[idx].volumes.push({ + host: "", + container: "", + read_only: false, + }); + setServices(newServices); + }, + [getNewServices] + ); + + const removeVolumeField = useCallback( + (selectedIdx: number | null, idx: number) => { + const result = getNewServices(selectedIdx); + if (!result) return; + const [newServices, sIdx] = result; + newServices[sIdx].volumes.splice(idx, 1); + setServices(newServices); + }, + [getNewServices] + ); + + // Healthcheck updates + const updateHealthcheckField = useCallback( + ( + selectedIdx: number | null, + field: keyof Healthcheck, + value: string + ) => { + const result = getNewServices(selectedIdx); + if (!result) return; + const [newServices, idx] = result; + if (!newServices[idx].healthcheck) + newServices[idx].healthcheck = { + test: "", + interval: "", + timeout: "", + retries: "", + start_period: "", + start_interval: "", + }; + newServices[idx].healthcheck![field] = value; + setServices(newServices); + }, + [getNewServices] + ); + + // Sysctl updates + const updateSysctl = useCallback( + ( + selectedIdx: number | null, + idx: number, + field: "key" | "value", + value: string + ) => { + const result = getNewServices(selectedIdx); + if (!result) return; + const [newServices, sIdx] = result; + if (!newServices[sIdx].sysctls) newServices[sIdx].sysctls = []; + newServices[sIdx].sysctls![idx] = { + ...newServices[sIdx].sysctls![idx], + [field]: value, + }; + setServices(newServices); + }, + [getNewServices] + ); + + const addSysctl = useCallback( + (selectedIdx: number | null) => { + const result = getNewServices(selectedIdx); + if (!result) return; + const [newServices, idx] = result; + if (!newServices[idx].sysctls) newServices[idx].sysctls = []; + newServices[idx].sysctls!.push({ key: "", value: "" }); + setServices(newServices); + }, + [getNewServices] + ); + + const removeSysctl = useCallback( + (selectedIdx: number | null, idx: number) => { + const result = getNewServices(selectedIdx); + if (!result) return; + const [newServices, sIdx] = result; + newServices[sIdx].sysctls!.splice(idx, 1); + setServices(newServices); + }, + [getNewServices] + ); + + // Ulimit updates + const updateUlimit = useCallback( + ( + selectedIdx: number | null, + idx: number, + field: "name" | "soft" | "hard", + value: string + ) => { + const result = getNewServices(selectedIdx); + if (!result) return; + const [newServices, sIdx] = result; + if (!newServices[sIdx].ulimits) newServices[sIdx].ulimits = []; + newServices[sIdx].ulimits![idx] = { + ...newServices[sIdx].ulimits![idx], + [field]: value, + }; + setServices(newServices); + }, + [getNewServices] + ); + + const addUlimit = useCallback( + (selectedIdx: number | null) => { + const result = getNewServices(selectedIdx); + if (!result) return; + const [newServices, idx] = result; + if (!newServices[idx].ulimits) newServices[idx].ulimits = []; + newServices[idx].ulimits!.push({ name: "", soft: "", hard: "" }); + setServices(newServices); + }, + [getNewServices] + ); + + const removeUlimit = useCallback( + (selectedIdx: number | null, idx: number) => { + const result = getNewServices(selectedIdx); + if (!result) return; + const [newServices, sIdx] = result; + newServices[sIdx].ulimits!.splice(idx, 1); + setServices(newServices); + }, + [getNewServices] + ); + + // Resource field updates + const updateResourceField = useCallback( + ( + selectedIdx: number | null, + type: "limits" | "reservations", + field: "cpus" | "memory", + value: string + ) => { + const result = getNewServices(selectedIdx); + if (!result) return; + const [newServices, idx] = result; + if (!newServices[idx].deploy) { + newServices[idx].deploy = { resources: {} }; + } + if (!newServices[idx].deploy!.resources) { + newServices[idx].deploy!.resources = {}; + } + if (!newServices[idx].deploy!.resources![type]) { + newServices[idx].deploy!.resources![type] = {}; + } + newServices[idx].deploy!.resources![type]![field] = value; + setServices(newServices); + }, + [getNewServices] + ); + + return { + services, + setServices, + updateServiceField, + updateListField, + addListField, + removeListField, + updateStringArrayField, + addStringArrayField, + removeStringArrayField, + addService, + removeService, + updatePortField, + addPortField, + removePortField, + updateVolumeField, + addVolumeField, + removeVolumeField, + updateHealthcheckField, + updateSysctl, + addSysctl, + removeSysctl, + updateUlimit, + addUlimit, + removeUlimit, + updateResourceField, + }; +} diff --git a/src/hooks/useTemplateStore.ts b/src/hooks/useTemplateStore.ts new file mode 100644 index 0000000..4eaa6a2 --- /dev/null +++ b/src/hooks/useTemplateStore.ts @@ -0,0 +1,202 @@ +import { useState, useEffect, useCallback } from "react"; + +export interface UseTemplateStoreReturn { + templateStoreOpen: boolean; + setTemplateStoreOpen: (open: boolean) => void; + templates: any[]; + setTemplates: React.Dispatch>; + templateLoading: boolean; + setTemplateLoading: (loading: boolean) => void; + templateError: string | null; + setTemplateError: (error: string | null) => void; + templateSearch: string; + setTemplateSearch: (search: string) => void; + selectedTemplate: any; + setSelectedTemplate: (template: any) => void; + templateDetailOpen: boolean; + setTemplateDetailOpen: (open: boolean) => void; + templateDetailTab: "overview" | "compose"; + setTemplateDetailTab: (tab: "overview" | "compose") => void; + templateCache: any[]; + templateCacheTimestamp: number | null; + fetchTemplatesFromGitHub: (backgroundUpdate?: boolean) => Promise; + fetchTemplateDetails: (templateId: string) => Promise; + refreshTemplateStore: () => void; +} + +const GITHUB_OWNER = "hhftechnology"; +const GITHUB_REPO = "Marketplace"; +const GITHUB_BRANCH = "main"; +const GITHUB_RAW_BASE = "https://raw.githubusercontent.com"; + +export function useTemplateStore(): UseTemplateStoreReturn { + const [templateStoreOpen, setTemplateStoreOpen] = useState(false); + const [templates, setTemplates] = useState([]); + const [templateLoading, setTemplateLoading] = useState(false); + const [templateError, setTemplateError] = useState(null); + const [templateSearch, setTemplateSearch] = useState(""); + const [selectedTemplate, setSelectedTemplate] = useState(null); + const [templateDetailOpen, setTemplateDetailOpen] = useState(false); + const [templateDetailTab, setTemplateDetailTab] = useState< + "overview" | "compose" + >("overview"); + const [templateCache, setTemplateCache] = useState(() => { + const cached = localStorage.getItem("templateStoreCache"); + return cached ? JSON.parse(cached) : []; + }); + const [templateCacheTimestamp, setTemplateCacheTimestamp] = useState< + number | null + >(() => { + const cached = localStorage.getItem("templateStoreCacheTimestamp"); + return cached ? parseInt(cached) : null; + }); + + const fetchTemplatesFromGitHub = useCallback( + async (backgroundUpdate: boolean = false) => { + if (!backgroundUpdate) { + setTemplateLoading(true); + setTemplateError(null); + } + + try { + // Fetch meta.json + const metaUrl = `${GITHUB_RAW_BASE}/${GITHUB_OWNER}/${GITHUB_REPO}/${GITHUB_BRANCH}/meta.json`; + const metaResponse = await fetch(metaUrl); + + if (!metaResponse.ok) { + throw new Error( + `Failed to fetch templates: ${metaResponse.statusText}` + ); + } + + const templatesMeta: any[] = await metaResponse.json(); + + // Store templates with metadata + setTemplates(templatesMeta); + setTemplateCache(templatesMeta); + setTemplateCacheTimestamp(Date.now()); + localStorage.setItem("templateStoreCache", JSON.stringify(templatesMeta)); + localStorage.setItem( + "templateStoreCacheTimestamp", + String(Date.now()) + ); + + if (!backgroundUpdate) { + setTemplateLoading(false); + } + } catch (error: any) { + console.error("Error fetching templates:", error); + if (!backgroundUpdate) { + setTemplateLoading(false); + setTemplateError(error.message || "Failed to load templates"); + } + } + }, + [] + ); + + const fetchTemplateDetails = useCallback( + async (templateId: string): Promise => { + const template = templates.find((t) => t.id === templateId); + if (!template) { + throw new Error(`Template ${templateId} not found`); + } + + try { + const basePath = `compose-files/${templateId}`; + + // Fetch docker-compose.yml + const composeUrl = `${GITHUB_RAW_BASE}/${GITHUB_OWNER}/${GITHUB_REPO}/${GITHUB_BRANCH}/${basePath}/docker-compose.yml`; + const composeResponse = await fetch(composeUrl); + if (!composeResponse.ok) { + throw new Error( + `Failed to fetch docker-compose.yml: ${composeResponse.statusText}` + ); + } + const composeContent = await composeResponse.text(); + + // Build logo URL if logo exists + let logoUrl = null; + if (template.logo) { + logoUrl = `${GITHUB_RAW_BASE}/${GITHUB_OWNER}/${GITHUB_REPO}/${GITHUB_BRANCH}/${basePath}/${template.logo}`; + } + + return { + ...template, + composeContent, + logoUrl, + }; + } catch (error: any) { + console.error( + `Error fetching template details for ${templateId}:`, + error + ); + throw error; + } + }, + [templates] + ); + + const refreshTemplateStore = useCallback(() => { + setTemplateCache([]); + setTemplateCacheTimestamp(null); + localStorage.removeItem("templateStoreCache"); + localStorage.removeItem("templateStoreCacheTimestamp"); + fetchTemplatesFromGitHub(false); + }, [fetchTemplatesFromGitHub]); + + // Initialize templates when store opens + useEffect(() => { + if (!templateStoreOpen) return; + + const CACHE_DURATION = 60 * 60 * 1000; // 1 hour + const now = Date.now(); + + // Check if we have valid cached data + if ( + templateCache.length > 0 && + templateCacheTimestamp && + now - templateCacheTimestamp < CACHE_DURATION + ) { + setTemplates(templateCache); + setTemplateLoading(false); + setTemplateError(null); + + // Still check for updates in the background + fetchTemplatesFromGitHub(true); + return; + } + + fetchTemplatesFromGitHub(false); + }, [ + templateStoreOpen, + templateCache, + templateCacheTimestamp, + fetchTemplatesFromGitHub, + ]); + + return { + templateStoreOpen, + setTemplateStoreOpen, + templates, + setTemplates, + templateLoading, + setTemplateLoading, + templateError, + setTemplateError, + templateSearch, + setTemplateSearch, + selectedTemplate, + setSelectedTemplate, + templateDetailOpen, + setTemplateDetailOpen, + templateDetailTab, + setTemplateDetailTab, + templateCache, + templateCacheTimestamp, + fetchTemplatesFromGitHub, + fetchTemplateDetails, + refreshTemplateStore, + }; +} + diff --git a/src/hooks/useVpnConfig.ts b/src/hooks/useVpnConfig.ts new file mode 100644 index 0000000..c857829 --- /dev/null +++ b/src/hooks/useVpnConfig.ts @@ -0,0 +1,202 @@ +import { useState, useCallback } from "react"; +import type { VPNConfig } from "../types/vpn-configs"; +import { + defaultVPNConfig, + defaultTailscaleConfig, + defaultNewtConfig, + defaultCloudflaredConfig, + defaultWireguardConfig, + defaultZerotierConfig, + defaultNetbirdConfig, +} from "../utils/default-configs"; + +export interface UseVpnConfigReturn { + vpnConfig: VPNConfig; + setVpnConfig: React.Dispatch>; + vpnConfigOpen: boolean; + setVpnConfigOpen: (open: boolean) => void; + updateVpnType: (type: VPNConfig["type"]) => void; + updateTailscaleConfig: (updates: Partial) => void; + updateNewtConfig: (updates: Partial) => void; + updateCloudflaredConfig: (updates: Partial) => void; + updateWireguardConfig: (updates: Partial) => void; + updateZerotierConfig: (updates: Partial) => void; + updateNetbirdConfig: (updates: Partial) => void; + updateServicesUsingVpn: (services: string[]) => void; + updateVpnNetworks: (networks: string[]) => void; +} + +export function useVpnConfig( + initialConfig?: VPNConfig +): UseVpnConfigReturn { + const [vpnConfig, setVpnConfig] = useState( + initialConfig || defaultVPNConfig() + ); + const [vpnConfigOpen, setVpnConfigOpen] = useState(false); + + const updateVpnType = useCallback((type: VPNConfig["type"] | "none") => { + setVpnConfig((prev) => { + const currentConfig = prev || defaultVPNConfig(); + const newType = type === "none" ? null : (type as VPNConfig["type"]); + return { + ...currentConfig, + enabled: newType !== null, + type: newType, + tailscale: + newType === "tailscale" + ? currentConfig.tailscale || defaultTailscaleConfig() + : undefined, + newt: + newType === "newt" + ? currentConfig.newt || defaultNewtConfig() + : undefined, + cloudflared: + newType === "cloudflared" + ? currentConfig.cloudflared || defaultCloudflaredConfig() + : undefined, + wireguard: + newType === "wireguard" + ? currentConfig.wireguard || defaultWireguardConfig() + : undefined, + zerotier: + newType === "zerotier" + ? currentConfig.zerotier || defaultZerotierConfig() + : undefined, + netbird: + newType === "netbird" + ? currentConfig.netbird || defaultNetbirdConfig() + : undefined, + }; + }); + }, []); + + const updateTailscaleConfig = useCallback( + (updates: Partial) => { + if (!updates) return; + setVpnConfig((prev) => { + const currentTailscale = prev.tailscale!; + const newUpdates = { ...updates }; + + // Auto-adjust port when protocol changes (unless user manually changed it) + if (currentTailscale && updates.serveProtocol && updates.serveProtocol !== currentTailscale.serveProtocol) { + const currentPort = currentTailscale.serveExternalPort; + const currentProtocol = currentTailscale.serveProtocol; + + // Only auto-adjust if port is at default for current protocol + if ( + (currentProtocol === "HTTPS" && currentPort === "443") || + (currentProtocol === "HTTP" && currentPort === "80") || + !currentPort // No port set yet + ) { + newUpdates.serveExternalPort = updates.serveProtocol === "HTTPS" ? "443" : "80"; + } + } + + return { + ...prev, + tailscale: { + ...currentTailscale, + ...newUpdates, + }, + }; + }); + }, + [] + ); + + const updateNewtConfig = useCallback( + (updates: Partial) => { + setVpnConfig((prev) => ({ + ...prev, + newt: { + ...prev.newt!, + ...updates, + }, + })); + }, + [] + ); + + const updateCloudflaredConfig = useCallback( + (updates: Partial) => { + setVpnConfig((prev) => ({ + ...prev, + cloudflared: { + ...prev.cloudflared!, + ...updates, + }, + })); + }, + [] + ); + + const updateWireguardConfig = useCallback( + (updates: Partial) => { + setVpnConfig((prev) => ({ + ...prev, + wireguard: { + ...prev.wireguard!, + ...updates, + }, + })); + }, + [] + ); + + const updateZerotierConfig = useCallback( + (updates: Partial) => { + setVpnConfig((prev) => ({ + ...prev, + zerotier: { + ...prev.zerotier!, + ...updates, + }, + })); + }, + [] + ); + + const updateNetbirdConfig = useCallback( + (updates: Partial) => { + setVpnConfig((prev) => ({ + ...prev, + netbird: { + ...prev.netbird!, + ...updates, + }, + })); + }, + [] + ); + + const updateServicesUsingVpn = useCallback((services: string[]) => { + setVpnConfig((prev) => ({ + ...prev, + servicesUsingVpn: services, + })); + }, []); + + const updateVpnNetworks = useCallback((networks: string[]) => { + setVpnConfig((prev) => ({ + ...prev, + networks, + })); + }, []); + + return { + vpnConfig, + setVpnConfig, + vpnConfigOpen, + setVpnConfigOpen, + updateVpnType, + updateTailscaleConfig, + updateNewtConfig, + updateCloudflaredConfig, + updateWireguardConfig, + updateZerotierConfig, + updateNetbirdConfig, + updateServicesUsingVpn, + updateVpnNetworks, + }; +} + diff --git a/src/routes/config-builder.tsx b/src/routes/config-builder.tsx index bba2301..62be278 100644 --- a/src/routes/config-builder.tsx +++ b/src/routes/config-builder.tsx @@ -124,8 +124,8 @@ function App() { }, [updateOutput]); return ( - - + + diff --git a/src/routes/docker/compose-builder.tsx b/src/routes/docker/compose-builder.tsx index 79ecb22..699fdef 100644 --- a/src/routes/docker/compose-builder.tsx +++ b/src/routes/docker/compose-builder.tsx @@ -1,54 +1,50 @@ -import { useState, useEffect, useRef, useLayoutEffect } from "react"; +import { useState } from "react"; import { createFileRoute } from "@tanstack/react-router"; +import { useSelectionState } from "../../hooks/useSelectionState"; +import { useVpnConfig } from "../../hooks/useVpnConfig"; +import { useTemplateStore } from "../../hooks/useTemplateStore"; +import { useYamlValidation } from "../../hooks/useYamlValidation"; +import { useNetworkVolumeManager } from "../../hooks/useNetworkVolumeManager"; +import { useEditorSize } from "../../hooks/useEditorSize"; +import { ServiceListSidebar } from "../../components/compose-builder/ServiceListSidebar"; +import { VpnConfigSection } from "../../components/compose-builder/VpnConfigSection"; +import { NetworkForm } from "../../components/compose-builder/NetworkForm"; +import { VolumeForm } from "../../components/compose-builder/VolumeForm"; +import { ServiceForm } from "../../components/compose-builder/ServiceForm"; import { CodeEditor } from "../../components/CodeEditor"; import { SidebarUI } from "../../components/SidebarUI"; import { Card } from "../../components/ui/card"; import { Button } from "../../components/ui/button"; -import { Input } from "../../components/ui/input"; import { Label } from "../../components/ui/label"; -import { Separator } from "../../components/ui/separator"; -import { - Collapsible, - CollapsibleTrigger, - CollapsibleContent, -} from "../../components/ui/collapsible"; import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, } from "../../components/ui/dropdown-menu"; -import { Toggle } from "../../components/ui/toggle"; import { Checkbox } from "../../components/ui/checkbox"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "../../components/ui/select"; -import jsyaml from "js-yaml"; import type { + ServiceConfig, + Healthcheck, +} from "../../types/compose"; + +// Re-export types (these are imported directly in export statement to avoid unused import warnings) +export type { ServiceConfig, NetworkConfig, VolumeConfig, - Healthcheck, } from "../../types/compose"; -import type { VPNConfig } from "../../types/vpn-configs"; +export type { VPNConfig } from "../../types/vpn-configs"; + import { defaultService, - defaultNetwork, - defaultVolume, - defaultVPNConfig, - defaultTailscaleConfig, - defaultNewtConfig, - defaultCloudflaredConfig, - defaultWireguardConfig, - defaultZerotierConfig, - defaultNetbirdConfig, } from "../../utils/default-configs"; -import { generateYaml } from "../../utils/yaml-generator"; -import { validateServices, redactSensitiveData } from "../../utils/validation"; +import { redactSensitiveData } from "../../utils/validation"; +import { + parseComposeService, + parseComposeTemplate, +} from "../../utils/template-import"; +import { copyToClipboard, downloadFile } from "../../utils/clipboard"; import { convertToDockerRun, convertToSystemd, @@ -68,11 +64,6 @@ import { DialogTitle, } from "../../components/ui/dialog"; import { Alert, AlertTitle, AlertDescription } from "../../components/ui/alert"; -import { - Tooltip, - TooltipTrigger, - TooltipContent, -} from "../../components/ui/tooltip"; import { Textarea } from "../../components/ui/textarea"; import { Download, @@ -80,90 +71,71 @@ import { AlertCircle, Copy, Settings, - Shield, - ChevronDown, - ChevronRight, } from "lucide-react"; -// Re-export types -export type { - ServiceConfig, - NetworkConfig, - VolumeConfig, -} from "../../types/compose"; -export type { VPNConfig } from "../../types/vpn-configs"; - function App() { const [services, setServices] = useState([defaultService()]); - const [selectedIdx, setSelectedIdx] = useState(0); - const [selectedType, setSelectedType] = useState< - "service" | "network" | "volume" - >("service"); - const [selectedNetworkIdx, setSelectedNetworkIdx] = useState( - null - ); - const [selectedVolumeIdx, setSelectedVolumeIdx] = useState( - null - ); - const [yaml, setYaml] = useState(""); - const [networks, setNetworks] = useState([]); - const [volumes, setVolumes] = useState([]); - const [vpnConfig, setVpnConfig] = useState(defaultVPNConfig()); - const [vpnConfigOpen, setVpnConfigOpen] = useState(false); - const codeFileRef = useRef(null); - const [editorSize, setEditorSize] = useState({ width: 0, height: 0 }); - const [conversionDialogOpen, setConversionDialogOpen] = useState(false); - const [conversionType, setConversionType] = useState(""); - const [conversionOutput, setConversionOutput] = useState(""); - const [validationError, setValidationError] = useState(null); - const [validationSuccess, setValidationSuccess] = useState(false); - const [clearEnvAfterDownload, setClearEnvAfterDownload] = useState(false); - // Template store state - const [templateStoreOpen, setTemplateStoreOpen] = useState(false); - const [templates, setTemplates] = useState([]); - const [templateLoading, setTemplateLoading] = useState(false); - const [templateError, setTemplateError] = useState(null); - const [templateSearch, setTemplateSearch] = useState(""); - const [selectedTemplate, setSelectedTemplate] = useState(null); - const [templateDetailOpen, setTemplateDetailOpen] = useState(false); - const [templateDetailTab, setTemplateDetailTab] = useState< - "overview" | "compose" - >("overview"); - const [templateCache, setTemplateCache] = useState(() => { - const cached = localStorage.getItem("templateStoreCache"); - return cached ? JSON.parse(cached) : []; + const [sidebarCollapsed, setSidebarCollapsed] = useState(false); + const { + selectedIdx, + selectedType, + selectedNetworkIdx, + selectedVolumeIdx, + selectService, + selectNetwork, + selectVolume, + } = useSelectionState(); + + // Network and Volume Management + const networkVolumeManager = useNetworkVolumeManager({ + initialNetworks: [], + initialVolumes: [], + setServices, + onSelectionChange: (type, idx) => { + if (type === "network") selectNetwork(idx); + else if (type === "volume") selectVolume(idx); + else selectService(idx); + }, }); - const [templateCacheTimestamp, setTemplateCacheTimestamp] = useState< - number | null - >(() => { - const cached = localStorage.getItem("templateStoreCacheTimestamp"); - return cached ? parseInt(cached) : null; + + const { networks, volumes, addNetwork, bulkAddNetworks, updateNetwork, removeNetwork, addVolume, bulkAddVolumes, updateVolume, removeVolume } = networkVolumeManager; + const { + vpnConfig, + vpnConfigOpen, + setVpnConfigOpen, + updateVpnType, + updateTailscaleConfig, + updateNewtConfig, + updateCloudflaredConfig, + updateWireguardConfig, + updateZerotierConfig, + updateNetbirdConfig, + updateServicesUsingVpn, + updateVpnNetworks, + } = useVpnConfig(); + const { + yaml, + validationError, + validationSuccess, + validateAndReformat, + } = useYamlValidation({ + services, + networks, + volumes, + vpnConfig, }); - useLayoutEffect(() => { - if (!codeFileRef.current) return; - const handleResize = () => { - const rect = codeFileRef.current?.getBoundingClientRect(); - if (rect) { - // Ensure minimum dimensions for small screens - setEditorSize({ - width: Math.max(rect.width, 300), - height: Math.max(rect.height, 200), - }); - } - }; - handleResize(); - const ro = new window.ResizeObserver(handleResize); - ro.observe(codeFileRef.current); + // Editor size management + const { codeFileRef, editorSize } = useEditorSize(); - // Also listen to window resize for better responsiveness - window.addEventListener("resize", handleResize); + // Conversion dialog state + const [conversionDialogOpen, setConversionDialogOpen] = useState(false); + const [conversionType, setConversionType] = useState(""); + const [conversionOutput, setConversionOutput] = useState(""); + const [clearEnvAfterDownload, setClearEnvAfterDownload] = useState(false); - return () => { - ro.disconnect(); - window.removeEventListener("resize", handleResize); - }; - }, [codeFileRef]); + // Template store + const templateStore = useTemplateStore(); function handleConversion(type: string) { setConversionType(type); @@ -205,58 +177,7 @@ function App() { } } - // Validation and reformatting - function validateAndReformat() { - try { - setValidationError(null); - setValidationSuccess(false); - - // Validate services - const errors = validateServices(services); - if (errors.length > 0) { - setValidationError(errors.join("; ")); - return; - } - - // Regenerate YAML using the imported generateYaml function - // This preserves VPN configs, JSON content, and proper formatting - const reformatted = generateYaml( - services, - networks, - volumes, - vpnConfig || defaultVPNConfig() - ); - setYaml(reformatted); - setValidationSuccess(true); - setTimeout(() => setValidationSuccess(false), 3000); - } catch (error: any) { - setValidationError(error.message || "Invalid YAML format"); - setValidationSuccess(false); - } - } - - function copyToClipboard(text: string) { - navigator.clipboard.writeText(text); - } - - function downloadFile(content: string, filename: string, mimeType: string) { - const blob = new Blob([content], { type: mimeType }); - const url = URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - a.download = filename; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - } - - useEffect(() => { - setYaml( - generateYaml(services, networks, volumes, vpnConfig || defaultVPNConfig()) - ); - }, [services, networks, volumes, vpnConfig]); // Helper to get new services array with selected service validation function getNewServices(): [ServiceConfig[], number] | null { @@ -350,10 +271,7 @@ function App() { function addService() { const newServices = [...services, defaultService()]; setServices(newServices); - setSelectedIdx(services.length); - setSelectedType("service"); - setSelectedNetworkIdx(null); - setSelectedVolumeIdx(null); + selectService(services.length); } function removeService(idx: number) { const newServices = services.filter((_, i) => i !== idx); @@ -361,7 +279,7 @@ function App() { const finalServices = newServices.length === 0 ? [defaultService()] : newServices; setServices(finalServices); - setSelectedIdx( + const newSelectedIdx = typeof selectedIdx === "number" ? Math.max( 0, @@ -370,8 +288,8 @@ function App() { selectedIdx - (idx <= selectedIdx ? 1 : 0) ) ) - : 0 - ); + : 0; + selectService(newSelectedIdx); } function updatePortField( @@ -599,455 +517,17 @@ function App() { setServices(newServices); } - function addNetwork() { - const newNetworks = [...networks, defaultNetwork()]; - setNetworks(newNetworks); - setSelectedType("network"); - setSelectedNetworkIdx(newNetworks.length - 1); - setSelectedIdx(null); - setSelectedVolumeIdx(null); - } - function updateNetwork(idx: number, field: keyof NetworkConfig, value: any) { - const newNetworks = [...networks]; - if (field === "name") { - const oldName = newNetworks[idx].name; - newNetworks[idx][field] = value; - setNetworks(newNetworks); - setServices((prev) => { - const newSvcs = prev.map((svc) => ({ - ...svc, - networks: svc.networks?.map((n) => (n === oldName ? value : n)) || [], - })); - return newSvcs; - }); - return; - } - (newNetworks[idx] as any)[field] = value; - setNetworks(newNetworks); - } - function removeNetwork(idx: number) { - const newNetworks = [...networks]; - const removedName = newNetworks[idx].name; - newNetworks.splice(idx, 1); - setNetworks(newNetworks); - const newServices = services.map((svc) => ({ - ...svc, - networks: svc.networks?.filter((n) => n !== removedName) || [], - })); - setServices(newServices); - if (newNetworks.length === 0) { - setSelectedType("service"); - setSelectedNetworkIdx(null); - } else { - setSelectedNetworkIdx(0); - } - } - function addVolume() { - const newVolumes = [...volumes, defaultVolume()]; - setVolumes(newVolumes); - setSelectedType("volume"); - setSelectedVolumeIdx(newVolumes.length - 1); - setSelectedIdx(null); - setSelectedNetworkIdx(null); - } - function updateVolume(idx: number, field: keyof VolumeConfig, value: any) { - const newVolumes = [...volumes]; - if (field === "name") { - const oldName = newVolumes[idx].name; - newVolumes[idx][field] = value; - setVolumes(newVolumes); - setServices((prev) => { - const newSvcs = prev.map((svc) => ({ - ...svc, - volumes: - svc.volumes?.map((v) => - v.host === oldName ? { ...v, host: value } : v - ) || [], - })); - return newSvcs; - }); - return; - } - (newVolumes[idx] as any)[field] = value; - setVolumes(newVolumes); - } - function removeVolume(idx: number) { - const newVolumes = [...volumes]; - const removedName = newVolumes[idx].name; - newVolumes.splice(idx, 1); - setVolumes(newVolumes); - const newServices = services.map((svc) => ({ - ...svc, - volumes: svc.volumes?.filter((v) => v.host !== removedName) || [], - })); - setServices(newServices); - if (newVolumes.length === 0) { - setSelectedType("service"); - setSelectedVolumeIdx(null); - } else { - setSelectedVolumeIdx(0); - } - } + // Network and volume functions are now provided by useNetworkVolumeManager hook // Template fetching functions - async function fetchTemplatesFromGitHub(backgroundUpdate: boolean = false) { - if (!backgroundUpdate) { - setTemplateLoading(true); - setTemplateError(null); - } - - const GITHUB_OWNER = "hhftechnology"; - const GITHUB_REPO = "Marketplace"; - const GITHUB_BRANCH = "main"; - const GITHUB_RAW_BASE = "https://raw.githubusercontent.com"; - - try { - // Fetch meta.json - const metaUrl = `${GITHUB_RAW_BASE}/${GITHUB_OWNER}/${GITHUB_REPO}/${GITHUB_BRANCH}/meta.json`; - const metaResponse = await fetch(metaUrl); - - if (!metaResponse.ok) { - throw new Error( - `Failed to fetch templates: ${metaResponse.statusText}` - ); - } - - const templatesMeta: any[] = await metaResponse.json(); - - // Store templates with metadata - setTemplates(templatesMeta); - setTemplateCache(templatesMeta); - setTemplateCacheTimestamp(Date.now()); - localStorage.setItem("templateStoreCache", JSON.stringify(templatesMeta)); - localStorage.setItem("templateStoreCacheTimestamp", String(Date.now())); - - if (!backgroundUpdate) { - setTemplateLoading(false); - } - } catch (error: any) { - console.error("Error fetching templates:", error); - if (!backgroundUpdate) { - setTemplateLoading(false); - setTemplateError(error.message || "Failed to load templates"); - } - } - } - - // Fetch template details (compose, logo) - async function fetchTemplateDetails(templateId: string): Promise { - const GITHUB_OWNER = "hhftechnology"; - const GITHUB_REPO = "Marketplace"; - const GITHUB_BRANCH = "main"; - const GITHUB_RAW_BASE = "https://raw.githubusercontent.com"; - - const template = templates.find((t) => t.id === templateId); - if (!template) { - throw new Error(`Template ${templateId} not found`); - } - - try { - const basePath = `compose-files/${templateId}`; - - // Fetch docker-compose.yml - const composeUrl = `${GITHUB_RAW_BASE}/${GITHUB_OWNER}/${GITHUB_REPO}/${GITHUB_BRANCH}/${basePath}/docker-compose.yml`; - const composeResponse = await fetch(composeUrl); - if (!composeResponse.ok) { - throw new Error( - `Failed to fetch docker-compose.yml: ${composeResponse.statusText}` - ); - } - const composeContent = await composeResponse.text(); - - // Build logo URL if logo exists - let logoUrl = null; - if (template.logo) { - logoUrl = `${GITHUB_RAW_BASE}/${GITHUB_OWNER}/${GITHUB_REPO}/${GITHUB_BRANCH}/${basePath}/${template.logo}`; - } - - return { - ...template, - composeContent, - logoUrl, - }; - } catch (error: any) { - console.error( - `Error fetching template details for ${templateId}:`, - error - ); - throw error; - } - } - - // Initialize templates when store opens - useEffect(() => { - if (!templateStoreOpen) return; - - const CACHE_DURATION = 60 * 60 * 1000; // 1 hour - const now = Date.now(); - - // Check if we have valid cached data - if ( - templateCache.length > 0 && - templateCacheTimestamp && - now - templateCacheTimestamp < CACHE_DURATION - ) { - setTemplates(templateCache); - setTemplateLoading(false); - setTemplateError(null); - - // Still check for updates in the background - fetchTemplatesFromGitHub(true); - return; - } - - fetchTemplatesFromGitHub(false); - }, [templateStoreOpen, templateCache, templateCacheTimestamp]); - - function refreshTemplateStore() { - setTemplateCache([]); - setTemplateCacheTimestamp(null); - localStorage.removeItem("templateStoreCache"); - localStorage.removeItem("templateStoreCacheTimestamp"); - fetchTemplatesFromGitHub(false); - } function handleAddComposeServiceFull( svc: any, allNetworks: any, allVolumes: any ) { - const serviceData = svc.rawService || {}; + const parsed = parseComposeService(svc, allNetworks, allVolumes); - const actualServiceData = serviceData.rawService || serviceData; - - const parseCommandArray = (cmd: any): string => { - if (Array.isArray(cmd)) { - return JSON.stringify(cmd); - } - return cmd || ""; - }; - - const newService: ServiceConfig = { - ...defaultService(), - name: svc.name, - image: svc.image, - container_name: actualServiceData.container_name || "", - command: parseCommandArray(actualServiceData.command), - restart: actualServiceData.restart || "", - ports: Array.isArray(actualServiceData.ports) - ? actualServiceData.ports.map((p: string) => { - // Handle format: "host:container/protocol" or "container/protocol" or just "container" - if (p.includes(":")) { - const parts = p.split(":"); - const host = parts[0]; - const containerWithProtocol = parts[1] || ""; - const [container, protocol] = containerWithProtocol.split("/"); - return { - host, - container, - protocol: protocol || "none", - }; - } else { - // No colon means it's just a container port, possibly with protocol - const [container, protocol] = p.split("/"); - return { - host: "", - container, - protocol: protocol || "none", - }; - } - }) - : [], - expose: Array.isArray(actualServiceData.expose) - ? actualServiceData.expose - : actualServiceData.expose - ? [String(actualServiceData.expose)] - : [], - volumes: Array.isArray(actualServiceData.volumes) - ? actualServiceData.volumes.map((v: any) => { - if (typeof v === "string") { - const parts = v.split(":"); - const host = parts[0]; - const container = parts[1] || ""; - const read_only = parts[2] === "ro"; - return { host, container, read_only }; - } else if (typeof v === "object" && v !== null) { - return { - host: v.source || "", - container: v.target || "", - read_only: v.read_only || false, - }; - } - return { host: "", container: "", read_only: false }; - }) - : [], - volumes_syntax: - Array.isArray(actualServiceData.volumes) && - actualServiceData.volumes.length > 0 && - typeof actualServiceData.volumes[0] === "object" - ? "dict" - : "array", - environment: Array.isArray(actualServiceData.environment) - ? actualServiceData.environment.map((e: string) => { - const [key, ...rest] = e.split("="); - return { key, value: rest.join("=") }; - }) - : actualServiceData.environment && - typeof actualServiceData.environment === "object" - ? Object.entries(actualServiceData.environment).map( - ([key, value]: [string, any]) => ({ key, value: String(value) }) - ) - : [], - environment_syntax: Array.isArray(actualServiceData.environment) - ? "array" - : "dict", - healthcheck: actualServiceData.healthcheck - ? { - test: parseCommandArray(actualServiceData.healthcheck.test), - interval: actualServiceData.healthcheck.interval || "", - timeout: actualServiceData.healthcheck.timeout || "", - retries: actualServiceData.healthcheck.retries - ? String(actualServiceData.healthcheck.retries) - : "", - start_period: actualServiceData.healthcheck.start_period || "", - start_interval: actualServiceData.healthcheck.start_interval || "", - } - : undefined, - depends_on: Array.isArray(actualServiceData.depends_on) - ? actualServiceData.depends_on - : actualServiceData.depends_on - ? Object.keys(actualServiceData.depends_on) - : [], - entrypoint: parseCommandArray(actualServiceData.entrypoint), - env_file: Array.isArray(actualServiceData.env_file) - ? actualServiceData.env_file.join(",") - : actualServiceData.env_file || "", - extra_hosts: Array.isArray(actualServiceData.extra_hosts) - ? actualServiceData.extra_hosts - : [], - dns: Array.isArray(actualServiceData.dns) ? actualServiceData.dns : [], - networks: Array.isArray(actualServiceData.networks) - ? actualServiceData.networks - : actualServiceData.networks - ? Object.keys(actualServiceData.networks) - : [], - user: actualServiceData.user || "", - working_dir: actualServiceData.working_dir || "", - labels: actualServiceData.labels - ? Array.isArray(actualServiceData.labels) - ? actualServiceData.labels.map((l: string) => { - const [key, ...rest] = l.split("="); - return { key, value: rest.join("=") }; - }) - : Object.entries(actualServiceData.labels).map( - ([key, value]: [string, any]) => ({ key, value: String(value) }) - ) - : [], - privileged: - actualServiceData.privileged !== undefined - ? !!actualServiceData.privileged - : undefined, - read_only: - actualServiceData.read_only !== undefined - ? !!actualServiceData.read_only - : undefined, - shm_size: actualServiceData.shm_size || "", - security_opt: Array.isArray(actualServiceData.security_opt) - ? actualServiceData.security_opt - : [], - network_mode: actualServiceData.network_mode || "", - cap_add: Array.isArray(actualServiceData.cap_add) - ? actualServiceData.cap_add - : [], - cap_drop: Array.isArray(actualServiceData.cap_drop) - ? actualServiceData.cap_drop - : [], - sysctls: - actualServiceData.sysctls && - typeof actualServiceData.sysctls === "object" - ? Array.isArray(actualServiceData.sysctls) - ? actualServiceData.sysctls.map((s: string) => { - const [key, value] = s.split("="); - return { key: key || "", value: value || "" }; - }) - : Object.entries(actualServiceData.sysctls).map( - ([key, value]: [string, any]) => ({ - key, - value: String(value), - }) - ) - : [], - devices: Array.isArray(actualServiceData.devices) - ? actualServiceData.devices - : [], - tmpfs: Array.isArray(actualServiceData.tmpfs) - ? actualServiceData.tmpfs - : actualServiceData.tmpfs - ? Object.keys(actualServiceData.tmpfs).map( - (key) => `${key}:${actualServiceData.tmpfs[key] || ""}` - ) - : [], - ulimits: - actualServiceData.ulimits && - typeof actualServiceData.ulimits === "object" - ? Object.entries(actualServiceData.ulimits).map( - ([name, limit]: [string, any]) => ({ - name, - soft: - limit && typeof limit === "object" && limit.soft - ? String(limit.soft) - : "", - hard: - limit && typeof limit === "object" && limit.hard - ? String(limit.hard) - : "", - }) - ) - : [], - init: - actualServiceData.init !== undefined - ? !!actualServiceData.init - : undefined, - stop_grace_period: actualServiceData.stop_grace_period || "", - stop_signal: actualServiceData.stop_signal || "", - tty: - actualServiceData.tty !== undefined - ? !!actualServiceData.tty - : undefined, - stdin_open: - actualServiceData.stdin_open !== undefined - ? !!actualServiceData.stdin_open - : undefined, - hostname: actualServiceData.hostname || "", - domainname: actualServiceData.domainname || "", - mac_address: actualServiceData.mac_address || "", - ipc_mode: actualServiceData.ipc || "", - pid: actualServiceData.pid || "", - uts: actualServiceData.uts || "", - cgroup_parent: actualServiceData.cgroup_parent || "", - isolation: actualServiceData.isolation || "", - deploy: actualServiceData.deploy?.resources - ? { - resources: { - limits: { - cpus: - actualServiceData.deploy.resources.limits?.cpus || undefined, - memory: - actualServiceData.deploy.resources.limits?.memory || - undefined, - }, - reservations: { - cpus: - actualServiceData.deploy.resources.reservations?.cpus || - undefined, - memory: - actualServiceData.deploy.resources.reservations?.memory || - undefined, - }, - }, - } - : undefined, - }; // Calculate the new service index after filtering out unnamed services const currentServices = services; const filteredServices = currentServices.filter( @@ -1058,163 +538,42 @@ function App() { setServices((prev) => { // Remove any unnamed services (empty name) when adding from marketplace const filtered = prev.filter((svc) => svc.name && svc.name.trim() !== ""); - const updated = [...filtered, newService]; + const updated = [...filtered, parsed.service]; return updated; }); - if (allNetworks && Object.keys(allNetworks).length > 0) { - const networkConfigs: NetworkConfig[] = Object.entries(allNetworks).map( - ([name, config]: [string, any]) => ({ - name, - driver: config.driver || "", - driver_opts: config.driver_opts - ? Object.entries(config.driver_opts).map( - ([key, value]: [string, any]) => ({ key, value: String(value) }) - ) - : [], - attachable: - config.attachable !== undefined ? !!config.attachable : false, - labels: config.labels - ? Array.isArray(config.labels) - ? config.labels.map((l: string) => { - const [key, ...rest] = l.split("="); - return { key, value: rest.join("=") }; - }) - : Object.entries(config.labels).map( - ([key, value]: [string, any]) => ({ - key, - value: String(value), - }) - ) - : [], - external: !!config.external, - name_external: - config.external && typeof config.external === "object" - ? config.external.name || "" - : "", - internal: config.internal !== undefined ? !!config.internal : false, - enable_ipv6: - config.enable_ipv6 !== undefined ? !!config.enable_ipv6 : false, - ipam: { - driver: config.ipam?.driver || "", - config: config.ipam?.config || [], - options: config.ipam?.options - ? Object.entries(config.ipam.options).map( - ([key, value]: [string, any]) => ({ - key, - value: String(value), - }) - ) - : [], - }, - }) - ); - setNetworks((prev) => { - const existingNames = new Set(prev.map((n) => n.name)); - const newNetworks = networkConfigs.filter( - (n) => !existingNames.has(n.name) - ); - return [...prev, ...newNetworks]; - }); - } - if (allVolumes && Object.keys(allVolumes).length > 0) { - const volumeConfigs: VolumeConfig[] = Object.entries(allVolumes).map( - ([name, config]: [string, any]) => { - let driverOptsType = ""; - let driverOptsDevice = ""; - let driverOptsO = ""; - if (config && config.driver_opts) { - driverOptsType = config.driver_opts.type || ""; - driverOptsDevice = config.driver_opts.device || ""; - driverOptsO = config.driver_opts.o || ""; - } + // Add parsed networks and volumes using bulk methods + if (parsed.networks.length > 0) { + bulkAddNetworks(parsed.networks); + } - return { - name, - driver: config && config.driver ? config.driver : "", - driver_opts: - config && config.driver_opts - ? Object.entries(config.driver_opts).map( - ([key, value]: [string, any]) => ({ - key, - value: String(value), - }) - ) - : [], - labels: - config && config.labels - ? Array.isArray(config.labels) - ? config.labels.map((l: string) => { - const [key, ...rest] = l.split("="); - return { key, value: rest.join("=") }; - }) - : Object.entries(config.labels).map( - ([key, value]: [string, any]) => ({ - key, - value: String(value), - }) - ) - : [], - external: !!config?.external, - name_external: - config?.external && typeof config.external === "object" - ? config.external.name || "" - : "", - driver_opts_type: driverOptsType, - driver_opts_device: driverOptsDevice, - driver_opts_o: driverOptsO, - }; - } - ); - setVolumes((prev) => { - const existingNames = new Set(prev.map((v) => v.name)); - const newVolumes = volumeConfigs.filter( - (v) => !existingNames.has(v.name) - ); - return [...prev, ...newVolumes]; - }); + if (parsed.volumes.length > 0) { + bulkAddVolumes(parsed.volumes); } - setSelectedType("service"); - setSelectedIdx(newServiceIndex); + + selectService(newServiceIndex); } async function importTemplate(template: any) { try { // Parse docker-compose.yml - const doc = jsyaml.load(template.composeContent) as any; - - if (!doc || !doc.services) { - throw new Error("Invalid docker-compose.yml in template"); - } - - // Add all services from compose file - const servicesArray = Object.entries(doc.services).map( - ([svcName, svcObj]: [string, any]) => ({ - name: svcName, - image: svcObj.image || "", - rawService: svcObj, - }) - ); + const { services: servicesArray, networks, volumes } = + parseComposeTemplate(template.composeContent); // Add services one by one for (const service of servicesArray) { - handleAddComposeServiceFull( - service, - doc.networks || {}, - doc.volumes || {} - ); + handleAddComposeServiceFull(service, networks, volumes); } // Close dialogs - setTemplateDetailOpen(false); - setTemplateStoreOpen(false); + templateStore.setTemplateDetailOpen(false); + templateStore.setTemplateStoreOpen(false); } catch (error: any) { console.error("Error importing template:", error); throw new Error(`Failed to import template: ${error.message}`); } } - if (!yaml) setYaml(generateYaml(services, networks, volumes)); const svc = selectedIdx !== null && @@ -1233,7 +592,8 @@ function App() { return ( <> - - + -
- - {/* Service List Sidebar */} - - {/* Configuration Panel */} -
+ + {/* Floating Expand Button (when sidebar is collapsed) */} + {sidebarCollapsed && ( +
+ +
+ )} + + {/* Main Content Area - Configuration Forms and YAML Preview */} +
+ {/* Configuration Form */} +
{selectedType === "service" && ( - <> -
- - Service Configuration - -
-
-
- - - updateServiceField("name", e.target.value) - } - placeholder="e.g. proxy" - /> -
-
- - - updateServiceField("container_name", e.target.value) - } - placeholder="e.g. my-traefik" - /> -
-
- - - updateServiceField("image", e.target.value) - } - placeholder="e.g. traefik:latest" - /> -
-
- - - updateServiceField("command", e.target.value) - } - placeholder="e.g. npm start" - /> -
-
- - - - - - - {restartOptions.map((opt) => ( - - updateServiceField("restart", opt.value) - } - > - {opt.label} - - ))} - - -
- {/* Ports */} -
- -
- {svc.ports.map((port, idx) => ( -
- - updatePortField(idx, "host", e.target.value) - } - placeholder="Host" - className="w-1/3" - /> - → - - updatePortField( - idx, - "container", - e.target.value - ) - } - placeholder="Container" - className="w-1/3" - /> - - - - - - - updatePortField(idx, "protocol", "none") - } - > - None - - - updatePortField(idx, "protocol", "tcp") - } - > - TCP - - - updatePortField(idx, "protocol", "udp") - } - > - UDP - - - - -
- ))} - -
-
- {/* Expose */} -
- -
- {svc.expose.map((port, idx) => ( -
- - updateListField( - "expose", - idx, - e.target.value.replace(/[^0-9]/g, "") - ) - } - placeholder="Port number" - className="flex-1" - /> - -
- ))} - -
-
- {/* Volumes */} -
-
- -
- - Syntax: - - - updateServiceField( - "volumes_syntax", - pressed ? "array" : "dict" - ) - } - aria-label="Array syntax" - className="border rounded px-2 py-1 text-xs" - > - Array - - - updateServiceField( - "volumes_syntax", - pressed ? "dict" : "array" - ) - } - aria-label="Dictionary syntax" - className="border rounded px-2 py-1 text-xs" - > - Dict - -
-
-
- {svc.volumes.map((vol, idx) => ( -
- - updateVolumeField(idx, "host", e.target.value) - } - placeholder="Host path/volume" - className="w-1/2" - /> - → - - updateVolumeField( - idx, - "container", - e.target.value - ) - } - placeholder="Container path" - className="w-1/2" - /> -
- - updateVolumeField(idx, "read_only", v) - } - aria-label="Read Only" - className="border rounded px-2 py-1" - > - RO - -
- -
- ))} - -
-
- {/* Environment Variables */} -
-
-
- - - - - - -

- Privacy Notice -

-

- All information you add here stays in your - browser and is never sent to any server. Click - the × button on each line to remove variables, - or use "Clear All" to remove them all at once. -

-
-
-
-
- - Syntax: - - - updateServiceField( - "environment_syntax", - pressed ? "array" : "dict" - ) - } - aria-label="Array syntax" - className="border rounded px-2 py-1 text-xs" - > - Array - - - updateServiceField( - "environment_syntax", - pressed ? "dict" : "array" - ) - } - aria-label="Dictionary syntax" - className="border rounded px-2 py-1 text-xs" - > - Dict - -
-
-
- {svc.environment.map((env, idx) => ( -
- - updateListField("environment", idx, { - ...env, - key: e.target.value, - }) - } - placeholder="KEY" - className="w-1/2" - /> - - updateListField("environment", idx, { - ...env, - value: e.target.value, - }) - } - placeholder="value" - className="w-1/2" - /> - -
- ))} -
- - {svc.environment.length > 0 && ( - - )} -
-
-
- {/* Advanced Section */} - - - - - - {/* Healthcheck */} -
- - - updateHealthcheckField("test", e.target.value) - } - placeholder="Test command (e.g. CMD curl -f http://localhost)" - /> -
- - updateHealthcheckField( - "interval", - e.target.value - ) - } - placeholder="Interval (e.g. 1m30s)" - className="w-1/2" - /> - - updateHealthcheckField( - "timeout", - e.target.value - ) - } - placeholder="Timeout (e.g. 10s)" - className="w-1/2" - /> -
-
- - updateHealthcheckField( - "retries", - e.target.value - ) - } - placeholder="Retries (e.g. 3)" - className="w-1/2" - /> - - updateHealthcheckField( - "start_period", - e.target.value - ) - } - placeholder="Start period (e.g. 40s)" - className="w-1/2" - /> -
- - updateHealthcheckField( - "start_interval", - e.target.value - ) - } - placeholder="Start interval (e.g. 5s)" - className="mt-2" - /> -
- {/* Depends On */} -
- -
- {svc.depends_on?.map((dep, idx) => ( -
- - updateDependsOn(idx, e.target.value) - } - placeholder="Service name" - /> - -
- ))} - -
-
- {/* Resource Allocation */} -
- -
-
- -
-
- - - updateResourceField( - "limits", - "cpus", - e.target.value - ) - } - placeholder="e.g. 0.5 or 2" - /> -
-
- - - updateResourceField( - "limits", - "memory", - e.target.value - ) - } - placeholder="e.g. 512m or 2g" - /> -
-
-
-
- -
-
- - - updateResourceField( - "reservations", - "cpus", - e.target.value - ) - } - placeholder="e.g. 0.25 or 1" - /> -
-
- - - updateResourceField( - "reservations", - "memory", - e.target.value - ) - } - placeholder="e.g. 256m or 1g" - /> -
-
-
-
-
- {/* Entrypoint */} -
- - - updateServiceField("entrypoint", e.target.value) - } - placeholder="Entrypoint" - /> -
- {/* Env File */} -
- - - updateServiceField("env_file", e.target.value) - } - placeholder=".env file path" - /> -
- {/* Extra Hosts */} -
- - - updateServiceField( - "extra_hosts", - e.target.value.split(",") - ) - } - placeholder="host1:ip1,host2:ip2" - /> -
- {/* DNS */} -
- - - updateServiceField( - "dns", - e.target.value.split(",") - ) - } - placeholder="8.8.8.8,8.8.4.4" - /> -
- {/* Networks */} -
- - - updateServiceField( - "networks", - e.target.value.split(",") - ) - } - placeholder="network1,network2" - /> -
- {/* User */} -
- - - updateServiceField("user", e.target.value) - } - placeholder="user" - /> -
- {/* Working Dir */} -
- - - updateServiceField("working_dir", e.target.value) - } - placeholder="/app" - /> -
- {/* Labels */} -
- -
- {svc.labels?.map((label, idx) => ( -
- { - const newLabels = [...(svc.labels || [])]; - newLabels[idx] = { - ...newLabels[idx], - key: e.target.value, - }; - updateServiceField("labels", newLabels); - }} - placeholder="Key" - className="w-1/2" - /> - { - const newLabels = [...(svc.labels || [])]; - newLabels[idx] = { - ...newLabels[idx], - value: e.target.value, - }; - updateServiceField("labels", newLabels); - }} - placeholder="Value" - className="w-1/2" - /> - -
- ))} - -
-
- {/* Privileged */} -
- - updateServiceField("privileged", v) - } - aria-label="Privileged" - className="border rounded px-2 py-1" - > - Privileged - -
- {/* Read Only */} -
- - updateServiceField("read_only", v) - } - aria-label="Read Only" - className="border rounded px-2 py-1" - > - Read Only - -
- {/* Shared Memory Size */} -
- - - updateServiceField("shm_size", e.target.value) - } - placeholder="e.g. 1gb, 512m" - /> -
- {/* Security Options */} -
- -
- {svc.security_opt?.map((opt, idx) => ( -
- - updateSecurityOpt(idx, e.target.value) - } - placeholder="e.g. seccomp:unconfined" - /> - -
- ))} - -
-
- {/* Network Mode */} -
- - - updateServiceField("network_mode", e.target.value) - } - placeholder="e.g. host, bridge, none, service:name" - /> -

- Options: host, bridge, none, service:service_name -

-
- {/* Cap Add */} -
- -
- {svc.cap_add?.map((cap, idx) => ( -
- - updateCapAdd(idx, e.target.value) - } - placeholder="e.g. NET_ADMIN, SYS_MODULE" - /> - -
- ))} - -
-
- {/* Cap Drop */} -
- -
- {svc.cap_drop?.map((cap, idx) => ( -
- - updateCapDrop(idx, e.target.value) - } - placeholder="e.g. ALL, CHOWN" - /> - -
- ))} - -
-
- {/* Sysctls */} -
- -
- {svc.sysctls?.map((sysctl, idx) => ( -
- - updateSysctl(idx, "key", e.target.value) - } - placeholder="Key (e.g. net.ipv4.ip_forward)" - className="w-1/2" - /> - - updateSysctl(idx, "value", e.target.value) - } - placeholder="Value (e.g. 1)" - className="w-1/2" - /> - -
- ))} - -
-
- {/* Devices */} -
- -
- {svc.devices?.map((device, idx) => ( -
- - updateDevice(idx, e.target.value) - } - placeholder="e.g. /dev/ttyUSB0:/dev/ttyUSB0" - /> - -
- ))} - -
-
- {/* Tmpfs */} -
- -
- {svc.tmpfs?.map((tmpfs, idx) => ( -
- - updateTmpfs(idx, e.target.value) - } - placeholder="e.g. /tmp:rw,noexec,nosuid,size=100m" - /> - -
- ))} - -
-
- {/* Ulimits */} -
- -
- {svc.ulimits?.map((ulimit, idx) => ( -
- - updateUlimit(idx, "name", e.target.value) - } - placeholder="Name (e.g. nofile)" - className="w-1/3" - /> - - updateUlimit(idx, "soft", e.target.value) - } - placeholder="Soft limit" - className="w-1/3" - /> - - updateUlimit(idx, "hard", e.target.value) - } - placeholder="Hard limit" - className="w-1/3" - /> - -
- ))} - -
-
- {/* Init */} -
- - updateServiceField("init", v) - } - aria-label="Init" - className="border rounded px-2 py-1" - > - Init (PID 1) - -
- {/* Stop Grace Period */} -
- - - updateServiceField( - "stop_grace_period", - e.target.value - ) - } - placeholder="e.g. 10s, 1m30s" - /> -
- {/* Stop Signal */} -
- - - updateServiceField("stop_signal", e.target.value) - } - placeholder="e.g. SIGTERM, SIGKILL" - /> -
- {/* TTY */} -
- - updateServiceField("tty", v) - } - aria-label="TTY" - className="border rounded px-2 py-1" - > - TTY - -
- {/* Stdin Open */} -
- - updateServiceField("stdin_open", v) - } - aria-label="Stdin Open" - className="border rounded px-2 py-1" - > - Stdin Open - -
- {/* Hostname */} -
- - - updateServiceField("hostname", e.target.value) - } - placeholder="Container hostname" - /> -
- {/* Domainname */} -
- - - updateServiceField("domainname", e.target.value) - } - placeholder="Container domainname" - /> -
- {/* MAC Address */} -
- - - updateServiceField("mac_address", e.target.value) - } - placeholder="e.g. 02:42:ac:11:65:43" - /> -
- {/* IPC Mode */} -
- - - updateServiceField("ipc_mode", e.target.value) - } - placeholder="e.g. host, container:name, shareable" - /> -
- {/* PID */} -
- - - updateServiceField("pid", e.target.value) - } - placeholder="e.g. host, container:name" - /> -
- {/* UTS */} -
- - - updateServiceField("uts", e.target.value) - } - placeholder="e.g. host, container:name" - /> -
- {/* Cgroup Parent */} -
- - - updateServiceField( - "cgroup_parent", - e.target.value - ) - } - placeholder="e.g. /system.slice" - /> -
- {/* Isolation */} -
- - - updateServiceField("isolation", e.target.value) - } - placeholder="e.g. default, process, hyperv" - /> -
-
-
-
- + )} {selectedType === "network" && selectedNetworkIdx !== null && ( - <> -
- - Network Configuration - -
-
-
- - - updateNetwork( - selectedNetworkIdx, - "name", - e.target.value - ) - } - placeholder="e.g. frontend" - /> -
-
- - - updateNetwork( - selectedNetworkIdx, - "driver", - e.target.value - ) - } - placeholder="e.g. bridge" - /> -
-
- - updateNetwork(selectedNetworkIdx, "attachable", v) - } - aria-label="Attachable" - className="border rounded px-2 py-1" - > - Attachable - -
-
- - updateNetwork(selectedNetworkIdx, "external", v) - } - aria-label="External" - className="border rounded px-2 py-1" - > - External - -
- {networks[selectedNetworkIdx]?.external && ( -
- - - updateNetwork( - selectedNetworkIdx, - "name_external", - e.target.value - ) - } - placeholder="External network name" - /> -
- )} -
- - updateNetwork(selectedNetworkIdx, "internal", v) - } - aria-label="Internal" - className="border rounded px-2 py-1" - > - Internal - -
-
- - updateNetwork(selectedNetworkIdx, "enable_ipv6", v) - } - aria-label="Enable IPv6" - className="border rounded px-2 py-1" - > - Enable IPv6 - -
-
- + + updateNetwork(selectedNetworkIdx, field, value) + } + /> )} {selectedType === "volume" && selectedVolumeIdx !== null && ( - <> -
- - Volume Configuration - -
-
-
- - - updateVolume( - selectedVolumeIdx, - "name", - e.target.value - ) - } - placeholder="e.g. webdata" - /> -
-
- - - updateVolume( - selectedVolumeIdx, - "driver", - e.target.value - ) - } - placeholder="e.g. local" - /> -
- {/* Driver Options */} -
- -
- - updateVolume( - selectedVolumeIdx, - "driver_opts_type", - e.target.value - ) - } - placeholder="Type (e.g. none)" - /> - - updateVolume( - selectedVolumeIdx, - "driver_opts_device", - e.target.value - ) - } - placeholder="Device (e.g. /path/to/device)" - /> - - updateVolume( - selectedVolumeIdx, - "driver_opts_o", - e.target.value - ) - } - placeholder="Options (e.g. bind)" - /> -
-
- {/* Labels */} -
- -
- {volumes[selectedVolumeIdx]?.labels?.map( - (label, idx) => ( -
- { - const newLabels = [ - ...(volumes[selectedVolumeIdx]?.labels || - []), - ]; - newLabels[idx] = { - ...newLabels[idx], - key: e.target.value, - }; - updateVolume( - selectedVolumeIdx, - "labels", - newLabels - ); - }} - placeholder="Key" - className="w-1/2" - /> - { - const newLabels = [ - ...(volumes[selectedVolumeIdx]?.labels || - []), - ]; - newLabels[idx] = { - ...newLabels[idx], - value: e.target.value, - }; - updateVolume( - selectedVolumeIdx, - "labels", - newLabels - ); - }} - placeholder="Value" - className="w-1/2" - /> - -
- ) - )} - -
-
-
- - updateVolume(selectedVolumeIdx, "external", v) - } - aria-label="External" - className="border rounded px-2 py-1" - > - External - -
- {volumes[selectedVolumeIdx]?.external && ( -
- - - updateVolume( - selectedVolumeIdx, - "name_external", - e.target.value - ) - } - placeholder="External volume name" - /> -
- )} -
- + + updateVolume(selectedVolumeIdx, field, value) + } + /> )} -
- {/* Docker Compose File Panel */} -
-
- - Docker Compose File - -
- {validationError && ( - - - +
+ + {/* Docker Compose Preview */} +
+
+
+
+

+ Docker Compose +

+
+
{validationError && ( + + + {validationError} )} {validationSuccess && ( - - - - Valid YAML + + + + Valid )} @@ -4227,13 +916,14 @@ function App() { size="sm" variant="outline" onClick={validateAndReformat} + className="shadow-sm hover:shadow-md transition-all" > Validate & Reformat - @@ -4268,7 +958,7 @@ function App() {
{editorSize.width > 0 && editorSize.height > 0 && ( )}
-
+
+
@@ -4394,42 +1085,42 @@ function App() { {/* Template Detail Dialog */} { - setTemplateDetailOpen(open); + templateStore.setTemplateDetailOpen(open); if (!open) { - setSelectedTemplate(null); - setTemplateDetailTab("overview"); + templateStore.setSelectedTemplate(null); + templateStore.setTemplateDetailTab("overview"); } }} > - {selectedTemplate?.name || "Template Details"} + {templateStore.selectedTemplate?.name || "Template Details"} - {selectedTemplate && ( + {templateStore.selectedTemplate && (
{/* Tab Buttons */}
@@ -4437,13 +1128,13 @@ function App() { {/* Tab Content */}
- {templateDetailTab === "overview" && ( + {templateStore.templateDetailTab === "overview" && (
- {selectedTemplate.logoUrl && ( + {templateStore.selectedTemplate.logoUrl && ( {selectedTemplate.name} { (e.target as HTMLImageElement).style.display = @@ -4453,20 +1144,20 @@ function App() { )}

- {selectedTemplate.name} + {templateStore.selectedTemplate.name}

- {selectedTemplate.version && ( + {templateStore.selectedTemplate.version && (

- Version {selectedTemplate.version} + Version {templateStore.selectedTemplate.version}

)}

- {selectedTemplate.description} + {templateStore.selectedTemplate.description}

- {selectedTemplate.tags && - selectedTemplate.tags.length > 0 && ( + {templateStore.selectedTemplate.tags && + templateStore.selectedTemplate.tags.length > 0 && (
- {selectedTemplate.tags.map((tag: string) => ( + {templateStore.selectedTemplate.tags.map((tag: string) => ( )} - {selectedTemplate.links && ( + {templateStore.selectedTemplate.links && (
- {selectedTemplate.links.github && ( + {templateStore.selectedTemplate.links.github && ( )} - {selectedTemplate.links.website && ( + {templateStore.selectedTemplate.links.website && ( )} - {selectedTemplate.links.docs && ( + {templateStore.selectedTemplate.links.docs && ( )} - {templateDetailTab === "compose" && ( + {templateStore.templateDetailTab === "compose" && (
- {selectedTemplate.composeContent ? ( + {templateStore.selectedTemplate.composeContent ? ( <>
{}} width="100%" height="100%" @@ -4533,7 +1224,7 @@ function App() { size="sm" className="w-full sm:w-auto" onClick={() => { - copyToClipboard(selectedTemplate.composeContent); + copyToClipboard(templateStore.selectedTemplate.composeContent); }} > @@ -4545,7 +1236,7 @@ function App() { className="w-full sm:w-auto" onClick={() => { downloadFile( - selectedTemplate.composeContent, + templateStore.selectedTemplate.composeContent, "docker-compose.yml", "text/yaml" ); @@ -4572,8 +1263,8 @@ function App() { size="sm" className="w-full sm:w-auto" onClick={() => { - setTemplateDetailOpen(false); - setTemplateStoreOpen(false); + templateStore.setTemplateDetailOpen(false); + templateStore.setTemplateStoreOpen(false); }} > Cancel @@ -4583,11 +1274,11 @@ function App() { className="w-full sm:w-auto" onClick={async () => { try { - await importTemplate(selectedTemplate); - setTemplateDetailOpen(false); - setTemplateStoreOpen(false); + await importTemplate(templateStore.selectedTemplate); + templateStore.setTemplateDetailOpen(false); + templateStore.setTemplateStoreOpen(false); } catch (error: any) { - setTemplateError( + templateStore.setTemplateError( `Failed to import template: ${error.message}` ); } diff --git a/src/routes/scheduler-builder.tsx b/src/routes/scheduler-builder.tsx index da48fde..599bc0c 100644 --- a/src/routes/scheduler-builder.tsx +++ b/src/routes/scheduler-builder.tsx @@ -193,8 +193,8 @@ ${serviceUnit} }, [scheduleType, config.name]); return ( - - + + diff --git a/src/types/vpn-configs.ts b/src/types/vpn-configs.ts index 22070ea..07f9de0 100644 --- a/src/types/vpn-configs.ts +++ b/src/types/vpn-configs.ts @@ -16,6 +16,7 @@ export interface TailscaleConfig { serveInternalPort: string; servePath: string; serveProtocol: "HTTPS" | "HTTP"; + serveInsideProtocol: "http" | "https" | "https+insecure"; containerName: string; enableHealthCheck: boolean; healthCheckEndpoint: string; @@ -73,4 +74,5 @@ export interface VPNConfig { zerotier?: ZerotierConfig; netbird?: NetbirdConfig; servicesUsingVpn: string[]; // Service names that should use VPN + networks?: string[]; // Networks the VPN service should attach to } diff --git a/src/utils/default-configs.ts b/src/utils/default-configs.ts index 9fc605e..c3ec22d 100644 --- a/src/utils/default-configs.ts +++ b/src/utils/default-configs.ts @@ -32,6 +32,7 @@ export function defaultTailscaleConfig(): TailscaleConfig { serveInternalPort: "8080", servePath: "/", serveProtocol: "HTTPS", + serveInsideProtocol: "http", // ScaleTail patterns - defaults containerName: "", enableHealthCheck: true, diff --git a/src/utils/template-import.ts b/src/utils/template-import.ts new file mode 100644 index 0000000..df2a190 --- /dev/null +++ b/src/utils/template-import.ts @@ -0,0 +1,388 @@ +import jsyaml from "js-yaml"; +import type { ServiceConfig, NetworkConfig, VolumeConfig } from "../types/compose"; +import { defaultService } from "./default-configs"; + +export interface ParsedComposeData { + service: ServiceConfig; + networks: NetworkConfig[]; + volumes: VolumeConfig[]; +} + +export interface ComposeServiceInput { + name: string; + image: string; + rawService: any; +} + +/** + * Parses a Docker Compose service into our ServiceConfig format + */ +export function parseComposeService( + svc: ComposeServiceInput, + allNetworks?: Record, + allVolumes?: Record +): ParsedComposeData { + const serviceData = svc.rawService || {}; + const actualServiceData = serviceData.rawService || serviceData; + + const parseCommandArray = (cmd: any): string => { + if (Array.isArray(cmd)) { + return JSON.stringify(cmd); + } + return cmd || ""; + }; + + const newService: ServiceConfig = { + ...defaultService(), + name: svc.name, + image: svc.image, + container_name: actualServiceData.container_name || "", + command: parseCommandArray(actualServiceData.command), + restart: actualServiceData.restart || "", + ports: Array.isArray(actualServiceData.ports) + ? actualServiceData.ports.map((p: string) => { + // Handle format: "host:container/protocol" or "container/protocol" or just "container" + if (p.includes(":")) { + const parts = p.split(":"); + const host = parts[0]; + const containerWithProtocol = parts[1] || ""; + const [container, protocol] = containerWithProtocol.split("/"); + return { + host, + container, + protocol: protocol || "none", + }; + } else { + // No colon means it's just a container port, possibly with protocol + const [container, protocol] = p.split("/"); + return { + host: "", + container, + protocol: protocol || "none", + }; + } + }) + : [], + expose: Array.isArray(actualServiceData.expose) + ? actualServiceData.expose + : actualServiceData.expose + ? [String(actualServiceData.expose)] + : [], + volumes: Array.isArray(actualServiceData.volumes) + ? actualServiceData.volumes.map((v: any) => { + if (typeof v === "string") { + const parts = v.split(":"); + const host = parts[0]; + const container = parts[1] || ""; + const read_only = parts[2] === "ro"; + return { host, container, read_only }; + } else if (typeof v === "object" && v !== null) { + return { + host: v.source || "", + container: v.target || "", + read_only: v.read_only || false, + }; + } + return { host: "", container: "", read_only: false }; + }) + : [], + volumes_syntax: + Array.isArray(actualServiceData.volumes) && + actualServiceData.volumes.length > 0 && + typeof actualServiceData.volumes[0] === "object" + ? "dict" + : "array", + environment: Array.isArray(actualServiceData.environment) + ? actualServiceData.environment.map((e: string) => { + const [key, ...rest] = e.split("="); + return { key, value: rest.join("=") }; + }) + : actualServiceData.environment && + typeof actualServiceData.environment === "object" + ? Object.entries(actualServiceData.environment).map( + ([key, value]: [string, any]) => ({ key, value: String(value) }) + ) + : [], + environment_syntax: Array.isArray(actualServiceData.environment) + ? "array" + : "dict", + healthcheck: actualServiceData.healthcheck + ? { + test: parseCommandArray(actualServiceData.healthcheck.test), + interval: actualServiceData.healthcheck.interval || "", + timeout: actualServiceData.healthcheck.timeout || "", + retries: actualServiceData.healthcheck.retries + ? String(actualServiceData.healthcheck.retries) + : "", + start_period: actualServiceData.healthcheck.start_period || "", + start_interval: actualServiceData.healthcheck.start_interval || "", + } + : undefined, + depends_on: Array.isArray(actualServiceData.depends_on) + ? actualServiceData.depends_on + : actualServiceData.depends_on + ? Object.keys(actualServiceData.depends_on) + : [], + entrypoint: parseCommandArray(actualServiceData.entrypoint), + env_file: Array.isArray(actualServiceData.env_file) + ? actualServiceData.env_file.join(",") + : actualServiceData.env_file || "", + extra_hosts: Array.isArray(actualServiceData.extra_hosts) + ? actualServiceData.extra_hosts + : [], + dns: Array.isArray(actualServiceData.dns) ? actualServiceData.dns : [], + networks: Array.isArray(actualServiceData.networks) + ? actualServiceData.networks + : actualServiceData.networks + ? Object.keys(actualServiceData.networks) + : [], + user: actualServiceData.user || "", + working_dir: actualServiceData.working_dir || "", + labels: actualServiceData.labels + ? Array.isArray(actualServiceData.labels) + ? actualServiceData.labels.map((l: string) => { + const [key, ...rest] = l.split("="); + return { key, value: rest.join("=") }; + }) + : Object.entries(actualServiceData.labels).map( + ([key, value]: [string, any]) => ({ key, value: String(value) }) + ) + : [], + privileged: + actualServiceData.privileged !== undefined + ? !!actualServiceData.privileged + : undefined, + read_only: + actualServiceData.read_only !== undefined + ? !!actualServiceData.read_only + : undefined, + shm_size: actualServiceData.shm_size || "", + security_opt: Array.isArray(actualServiceData.security_opt) + ? actualServiceData.security_opt + : [], + network_mode: actualServiceData.network_mode || "", + cap_add: Array.isArray(actualServiceData.cap_add) + ? actualServiceData.cap_add + : [], + cap_drop: Array.isArray(actualServiceData.cap_drop) + ? actualServiceData.cap_drop + : [], + sysctls: + actualServiceData.sysctls && typeof actualServiceData.sysctls === "object" + ? Array.isArray(actualServiceData.sysctls) + ? actualServiceData.sysctls.map((s: string) => { + const [key, value] = s.split("="); + return { key: key || "", value: value || "" }; + }) + : Object.entries(actualServiceData.sysctls).map( + ([key, value]: [string, any]) => ({ + key, + value: String(value), + }) + ) + : [], + devices: Array.isArray(actualServiceData.devices) + ? actualServiceData.devices + : [], + tmpfs: Array.isArray(actualServiceData.tmpfs) + ? actualServiceData.tmpfs + : actualServiceData.tmpfs + ? Object.keys(actualServiceData.tmpfs).map( + (key) => `${key}:${actualServiceData.tmpfs[key] || ""}` + ) + : [], + ulimits: + actualServiceData.ulimits && + typeof actualServiceData.ulimits === "object" + ? Object.entries(actualServiceData.ulimits).map( + ([name, limit]: [string, any]) => ({ + name, + soft: + limit && typeof limit === "object" && limit.soft + ? String(limit.soft) + : "", + hard: + limit && typeof limit === "object" && limit.hard + ? String(limit.hard) + : "", + }) + ) + : [], + init: + actualServiceData.init !== undefined ? !!actualServiceData.init : undefined, + stop_grace_period: actualServiceData.stop_grace_period || "", + stop_signal: actualServiceData.stop_signal || "", + tty: + actualServiceData.tty !== undefined ? !!actualServiceData.tty : undefined, + stdin_open: + actualServiceData.stdin_open !== undefined + ? !!actualServiceData.stdin_open + : undefined, + hostname: actualServiceData.hostname || "", + domainname: actualServiceData.domainname || "", + mac_address: actualServiceData.mac_address || "", + ipc_mode: actualServiceData.ipc || "", + pid: actualServiceData.pid || "", + uts: actualServiceData.uts || "", + cgroup_parent: actualServiceData.cgroup_parent || "", + isolation: actualServiceData.isolation || "", + deploy: actualServiceData.deploy?.resources + ? { + resources: { + limits: { + cpus: + actualServiceData.deploy.resources.limits?.cpus || undefined, + memory: + actualServiceData.deploy.resources.limits?.memory || undefined, + }, + reservations: { + cpus: + actualServiceData.deploy.resources.reservations?.cpus || + undefined, + memory: + actualServiceData.deploy.resources.reservations?.memory || + undefined, + }, + }, + } + : undefined, + }; + + // Parse networks + const networkConfigs: NetworkConfig[] = + allNetworks && Object.keys(allNetworks).length > 0 + ? Object.entries(allNetworks).map(([name, config]: [string, any]) => ({ + name, + driver: config.driver || "", + driver_opts: config.driver_opts + ? Object.entries(config.driver_opts).map( + ([key, value]: [string, any]) => ({ key, value: String(value) }) + ) + : [], + attachable: + config.attachable !== undefined ? !!config.attachable : false, + labels: config.labels + ? Array.isArray(config.labels) + ? config.labels.map((l: string) => { + const [key, ...rest] = l.split("="); + return { key, value: rest.join("=") }; + }) + : Object.entries(config.labels).map( + ([key, value]: [string, any]) => ({ + key, + value: String(value), + }) + ) + : [], + external: !!config.external, + name_external: + config.external && typeof config.external === "object" + ? config.external.name || "" + : "", + internal: config.internal !== undefined ? !!config.internal : false, + enable_ipv6: + config.enable_ipv6 !== undefined ? !!config.enable_ipv6 : false, + ipam: { + driver: config.ipam?.driver || "", + config: config.ipam?.config || [], + options: config.ipam?.options + ? Object.entries(config.ipam.options).map( + ([key, value]: [string, any]) => ({ + key, + value: String(value), + }) + ) + : [], + }, + })) + : []; + + // Parse volumes + const volumeConfigs: VolumeConfig[] = + allVolumes && Object.keys(allVolumes).length > 0 + ? Object.entries(allVolumes).map(([name, config]: [string, any]) => { + let driverOptsType = ""; + let driverOptsDevice = ""; + let driverOptsO = ""; + + if (config && config.driver_opts) { + driverOptsType = config.driver_opts.type || ""; + driverOptsDevice = config.driver_opts.device || ""; + driverOptsO = config.driver_opts.o || ""; + } + + return { + name, + driver: config && config.driver ? config.driver : "", + driver_opts: + config && config.driver_opts + ? Object.entries(config.driver_opts).map( + ([key, value]: [string, any]) => ({ + key, + value: String(value), + }) + ) + : [], + labels: + config && config.labels + ? Array.isArray(config.labels) + ? config.labels.map((l: string) => { + const [key, ...rest] = l.split("="); + return { key, value: rest.join("=") }; + }) + : Object.entries(config.labels).map( + ([key, value]: [string, any]) => ({ + key, + value: String(value), + }) + ) + : [], + external: !!config?.external, + name_external: + config?.external && typeof config.external === "object" + ? config.external.name || "" + : "", + driver_opts_type: driverOptsType, + driver_opts_device: driverOptsDevice, + driver_opts_o: driverOptsO, + }; + }) + : []; + + return { + service: newService, + networks: networkConfigs, + volumes: volumeConfigs, + }; +} + +/** + * Parses a Docker Compose YAML template and returns all services, networks, and volumes + */ +export function parseComposeTemplate(composeContent: string): { + services: ComposeServiceInput[]; + networks: Record; + volumes: Record; +} { + const doc = jsyaml.load(composeContent) as any; + + if (!doc || !doc.services) { + throw new Error("Invalid docker-compose.yml in template"); + } + + // Extract all services from compose file + const servicesArray: ComposeServiceInput[] = Object.entries( + doc.services + ).map(([svcName, svcObj]: [string, any]) => ({ + name: svcName, + image: svcObj.image || "", + rawService: svcObj, + })); + + return { + services: servicesArray, + networks: doc.networks || {}, + volumes: doc.volumes || {}, + }; +} + diff --git a/src/utils/vpn-generator.ts b/src/utils/vpn-generator.ts index 0c6f9ef..034bbb2 100644 --- a/src/utils/vpn-generator.ts +++ b/src/utils/vpn-generator.ts @@ -9,16 +9,21 @@ export function generateTailscaleServeConfig( internalPort: string, path: string, protocol: "HTTPS" | "HTTP", - certDomain: string + certDomain: string, + insideProtocol: "http" | "https" | "https+insecure" = "http" ): string { const config: any = { TCP: { [externalPort]: { HTTPS: protocol === "HTTPS", + HTTP: protocol === "HTTP", }, }, }; + // Build proxy URL based on inside protocol + const proxyUrl = `${insideProtocol}://127.0.0.1:${internalPort}`; + if (protocol === "HTTPS") { const certDomainKey = certDomain ? certDomain @@ -27,17 +32,19 @@ export function generateTailscaleServeConfig( [`${certDomainKey}:${externalPort}`]: { Handlers: { [path]: { - Proxy: `http://127.0.0.1:${internalPort}`, + Proxy: proxyUrl, }, }, }, }; } else { - config.TCP[externalPort] = { - HTTP: true, - Handlers: { - [path]: { - Proxy: `http://127.0.0.1:${internalPort}`, + // HTTP mode: use Web section with localhost + config.Web = { + [`localhost:${externalPort}`]: { + Handlers: { + [path]: { + Proxy: proxyUrl, + }, }, }, }; @@ -58,6 +65,11 @@ export function generateVpnService(vpnConfig: VPNConfig | undefined): any { restart: "always", }; + // Add selected networks + if (vpnConfig.networks && vpnConfig.networks.length > 0) { + service.networks = vpnConfig.networks; + } + switch (vpnConfig.type) { case "tailscale": { const ts = vpnConfig.tailscale!; @@ -107,7 +119,10 @@ export function generateVpnService(vpnConfig: VPNConfig | undefined): any { NEWT_ID: newt.newtId ? "${NEWT_ID}" : undefined, NEWT_SECRET: newt.newtSecret ? "${NEWT_SECRET}" : undefined, }; - service.networks = [newt.networkName]; + service.networks = [ + ...(service.networks || []), + newt.networkName + ]; Object.keys(service.environment).forEach( (key) => service.environment[key] === undefined && @@ -148,7 +163,13 @@ export function generateVpnService(vpnConfig: VPNConfig | undefined): any { const zt = vpnConfig.zerotier!; service.image = "zerotier/zerotier:latest"; service.privileged = true; - service.networks = ["host"]; + service.networks = ["host"]; // ZeroTier needs host networking + // If host networking is used, we can't attach to other networks + if (vpnConfig.networks && vpnConfig.networks.length > 0) { + // Warning: ZeroTier uses host networking, ignoring selected networks + delete service.networks; + service.network_mode = "host"; + } service.volumes = [zt.identityPath + ":/var/lib/zerotier-one"]; service.environment = { ZT_NC_NETWORK: zt.networkId ? "${ZT_NETWORK_ID}" : undefined, diff --git a/src/utils/yaml-generator.ts b/src/utils/yaml-generator.ts index 9e480f9..c81bf35 100644 --- a/src/utils/yaml-generator.ts +++ b/src/utils/yaml-generator.ts @@ -182,13 +182,13 @@ export function generateYaml( : svc.networks && svc.networks.filter(Boolean).length ? svc.networks.filter(Boolean) : undefined, - user: svc.user ? `"${svc.user}"` : undefined, + user: svc.user || undefined, working_dir: svc.working_dir || undefined, labels: svc.labels && svc.labels.filter((l) => l.key).length ? svc.labels .filter((l) => l.key) - .map(({ key, value }) => `"${key}=${value}"`) + .map(({ key, value }) => `${key}=${value}`) : undefined, privileged: svc.privileged !== undefined ? svc.privileged : undefined, read_only: svc.read_only !== undefined ? svc.read_only : undefined, @@ -331,7 +331,8 @@ export function generateYaml( ts.serveInternalPort, ts.servePath, ts.serveProtocol, - ts.certDomain + ts.certDomain, + ts.serveInsideProtocol || "http" ); if (!compose.configs) { @@ -373,7 +374,7 @@ export function generateYaml( n.labels && n.labels.length ? n.labels .filter((l) => l.key) - .map(({ key, value }) => `"${key}=${value}"`) + .map(({ key, value }) => `${key}=${value}`) : undefined, ipam: n.ipam.driver || n.ipam.config.length || n.ipam.options.length @@ -436,7 +437,7 @@ export function generateYaml( if (v.labels && v.labels.length) { externalVolume.labels = v.labels .filter((l) => l.key) - .map(({ key, value }) => `"${key}=${value}"`); + .map(({ key, value }) => `${key}=${value}`); } compose.volumes[v.name] = externalVolume; @@ -463,7 +464,7 @@ export function generateYaml( v.labels && v.labels.length ? v.labels .filter((l) => l.key) - .map(({ key, value }) => `"${key}=${value}"`) + .map(({ key, value }) => `${key}=${value}`) : undefined, }; }