diff --git a/frontend/README.md b/frontend/README.md
deleted file mode 100644
index d2e7761..0000000
--- a/frontend/README.md
+++ /dev/null
@@ -1,73 +0,0 @@
-# React + TypeScript + Vite
-
-This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
-
-Currently, two official plugins are available:
-
-- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
-- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
-
-## React Compiler
-
-The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
-
-## Expanding the ESLint configuration
-
-If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
-
-```js
-export default defineConfig([
- globalIgnores(['dist']),
- {
- files: ['**/*.{ts,tsx}'],
- extends: [
- // Other configs...
-
- // Remove tseslint.configs.recommended and replace with this
- tseslint.configs.recommendedTypeChecked,
- // Alternatively, use this for stricter rules
- tseslint.configs.strictTypeChecked,
- // Optionally, add this for stylistic rules
- tseslint.configs.stylisticTypeChecked,
-
- // Other configs...
- ],
- languageOptions: {
- parserOptions: {
- project: ['./tsconfig.node.json', './tsconfig.app.json'],
- tsconfigRootDir: import.meta.dirname,
- },
- // other options...
- },
- },
-])
-```
-
-You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
-
-```js
-// eslint.config.js
-import reactX from 'eslint-plugin-react-x'
-import reactDom from 'eslint-plugin-react-dom'
-
-export default defineConfig([
- globalIgnores(['dist']),
- {
- files: ['**/*.{ts,tsx}'],
- extends: [
- // Other configs...
- // Enable lint rules for React
- reactX.configs['recommended-typescript'],
- // Enable lint rules for React DOM
- reactDom.configs.recommended,
- ],
- languageOptions: {
- parserOptions: {
- project: ['./tsconfig.node.json', './tsconfig.app.json'],
- tsconfigRootDir: import.meta.dirname,
- },
- // other options...
- },
- },
-])
-```
diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js
deleted file mode 100644
index 5e6b472..0000000
--- a/frontend/eslint.config.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import js from '@eslint/js'
-import globals from 'globals'
-import reactHooks from 'eslint-plugin-react-hooks'
-import reactRefresh from 'eslint-plugin-react-refresh'
-import tseslint from 'typescript-eslint'
-import { defineConfig, globalIgnores } from 'eslint/config'
-
-export default defineConfig([
- globalIgnores(['dist']),
- {
- files: ['**/*.{ts,tsx}'],
- extends: [
- js.configs.recommended,
- tseslint.configs.recommended,
- reactHooks.configs.flat.recommended,
- reactRefresh.configs.vite,
- ],
- languageOptions: {
- ecmaVersion: 2020,
- globals: globals.browser,
- },
- },
-])
diff --git a/frontend/index.html b/frontend/index.html
deleted file mode 100644
index 072a57e..0000000
--- a/frontend/index.html
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
- frontend
-
-
-
-
-
-
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
deleted file mode 100644
index 0f88400..0000000
--- a/frontend/package-lock.json
+++ /dev/null
@@ -1,3315 +0,0 @@
-{
- "name": "frontend",
- "version": "0.0.0",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "frontend",
- "version": "0.0.0",
- "dependencies": {
- "react": "^19.2.0",
- "react-dom": "^19.2.0",
- "react-router-dom": "^7.12.0"
- },
- "devDependencies": {
- "@eslint/js": "^9.39.1",
- "@types/node": "^24.10.1",
- "@types/react": "^19.2.5",
- "@types/react-dom": "^19.2.3",
- "@vitejs/plugin-react": "^5.1.1",
- "eslint": "^9.39.1",
- "eslint-plugin-react-hooks": "^7.0.1",
- "eslint-plugin-react-refresh": "^0.4.24",
- "globals": "^16.5.0",
- "typescript": "~5.9.3",
- "typescript-eslint": "^8.46.4",
- "vite": "^7.2.4"
- }
- },
- "node_modules/@babel/code-frame": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
- "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-validator-identifier": "^7.27.1",
- "js-tokens": "^4.0.0",
- "picocolors": "^1.1.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/compat-data": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz",
- "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/core": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
- "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
- "dev": true,
- "license": "MIT",
- "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/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.1.0",
- "gensync": "^1.0.0-beta.2",
- "json5": "^2.2.3",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/babel"
- }
- },
- "node_modules/@babel/generator": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
- "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.28.5",
- "@babel/types": "^7.28.5",
- "@jridgewell/gen-mapping": "^0.3.12",
- "@jridgewell/trace-mapping": "^0.3.28",
- "jsesc": "^3.0.2"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-compilation-targets": {
- "version": "7.27.2",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
- "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/compat-data": "^7.27.2",
- "@babel/helper-validator-option": "^7.27.1",
- "browserslist": "^4.24.0",
- "lru-cache": "^5.1.1",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-globals": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
- "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-module-imports": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
- "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/traverse": "^7.27.1",
- "@babel/types": "^7.27.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-module-transforms": {
- "version": "7.28.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
- "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-imports": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.27.1",
- "@babel/traverse": "^7.28.3"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/helper-plugin-utils": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
- "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-string-parser": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
- "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-identifier": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
- "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-option": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
- "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helpers": {
- "version": "7.28.4",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
- "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/template": "^7.27.2",
- "@babel/types": "^7.28.4"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/parser": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
- "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.28.5"
- },
- "bin": {
- "parser": "bin/babel-parser.js"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-react-jsx-self": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
- "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-react-jsx-source": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
- "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/template": {
- "version": "7.27.2",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
- "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.27.1",
- "@babel/parser": "^7.27.2",
- "@babel/types": "^7.27.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/traverse": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
- "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
- "dev": true,
- "license": "MIT",
- "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.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/types": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
- "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-string-parser": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.28.5"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@esbuild/aix-ppc64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
- "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-arm": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
- "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
- "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
- "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/darwin-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
- "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/darwin-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
- "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/freebsd-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
- "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/freebsd-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
- "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-arm": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
- "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
- "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-ia32": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
- "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-loong64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
- "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-mips64el": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
- "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-ppc64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
- "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-riscv64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
- "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-s390x": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
- "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
- "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/netbsd-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
- "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/netbsd-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
- "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
- "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
- "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openharmony-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
- "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/sunos-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
- "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
- "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-ia32": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
- "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
- "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@eslint-community/eslint-utils": {
- "version": "4.9.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
- "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eslint-visitor-keys": "^3.4.3"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- },
- "peerDependencies": {
- "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
- }
- },
- "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
- "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/@eslint-community/regexpp": {
- "version": "4.12.2",
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
- "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
- }
- },
- "node_modules/@eslint/config-array": {
- "version": "0.21.1",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
- "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/object-schema": "^2.1.7",
- "debug": "^4.3.1",
- "minimatch": "^3.1.2"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/config-helpers": {
- "version": "0.4.2",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
- "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/core": "^0.17.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/core": {
- "version": "0.17.0",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
- "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@types/json-schema": "^7.0.15"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/eslintrc": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
- "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "^6.12.4",
- "debug": "^4.3.2",
- "espree": "^10.0.1",
- "globals": "^14.0.0",
- "ignore": "^5.2.0",
- "import-fresh": "^3.2.1",
- "js-yaml": "^4.1.1",
- "minimatch": "^3.1.2",
- "strip-json-comments": "^3.1.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/@eslint/eslintrc/node_modules/globals": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
- "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@eslint/js": {
- "version": "9.39.2",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
- "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://eslint.org/donate"
- }
- },
- "node_modules/@eslint/object-schema": {
- "version": "2.1.7",
- "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
- "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/plugin-kit": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
- "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/core": "^0.17.0",
- "levn": "^0.4.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@humanfs/core": {
- "version": "0.19.1",
- "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
- "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.18.0"
- }
- },
- "node_modules/@humanfs/node": {
- "version": "0.16.7",
- "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
- "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@humanfs/core": "^0.19.1",
- "@humanwhocodes/retry": "^0.4.0"
- },
- "engines": {
- "node": ">=18.18.0"
- }
- },
- "node_modules/@humanwhocodes/module-importer": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
- "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=12.22"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
- "node_modules/@humanwhocodes/retry": {
- "version": "0.4.3",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
- "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.18"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
- "node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.13",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
- "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.0",
- "@jridgewell/trace-mapping": "^0.3.24"
- }
- },
- "node_modules/@jridgewell/remapping": {
- "version": "2.3.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
- "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
- }
- },
- "node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
- "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.31",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
- "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
- },
- "node_modules/@rolldown/pluginutils": {
- "version": "1.0.0-beta.53",
- "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz",
- "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
- "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
- "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
- "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
- "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
- "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
- "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
- "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
- "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
- "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
- "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-loong64-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
- "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-loong64-musl": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
- "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-ppc64-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
- "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-ppc64-musl": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
- "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
- "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
- "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
- "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
- "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
- "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-openbsd-x64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
- "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ]
- },
- "node_modules/@rollup/rollup-openharmony-arm64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
- "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ]
- },
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
- "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
- "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
- "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
- "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@types/babel__core": {
- "version": "7.20.5",
- "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
- "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.20.7",
- "@babel/types": "^7.20.7",
- "@types/babel__generator": "*",
- "@types/babel__template": "*",
- "@types/babel__traverse": "*"
- }
- },
- "node_modules/@types/babel__generator": {
- "version": "7.27.0",
- "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
- "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.0.0"
- }
- },
- "node_modules/@types/babel__template": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
- "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.1.0",
- "@babel/types": "^7.0.0"
- }
- },
- "node_modules/@types/babel__traverse": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
- "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.28.2"
- }
- },
- "node_modules/@types/estree": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
- "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/json-schema": {
- "version": "7.0.15",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
- "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/node": {
- "version": "24.10.7",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.7.tgz",
- "integrity": "sha512-+054pVMzVTmRQV8BhpGv3UyfZ2Llgl8rdpDTon+cUH9+na0ncBVXj3wTUKh14+Kiz18ziM3b4ikpP5/Pc0rQEQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "undici-types": "~7.16.0"
- }
- },
- "node_modules/@types/react": {
- "version": "19.2.8",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.8.tgz",
- "integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "csstype": "^3.2.2"
- }
- },
- "node_modules/@types/react-dom": {
- "version": "19.2.3",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
- "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "^19.2.0"
- }
- },
- "node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.52.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.52.0.tgz",
- "integrity": "sha512-okqtOgqu2qmZJ5iN4TWlgfF171dZmx2FzdOv2K/ixL2LZWDStL8+JgQerI2sa8eAEfoydG9+0V96m7V+P8yE1Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/regexpp": "^4.12.2",
- "@typescript-eslint/scope-manager": "8.52.0",
- "@typescript-eslint/type-utils": "8.52.0",
- "@typescript-eslint/utils": "8.52.0",
- "@typescript-eslint/visitor-keys": "8.52.0",
- "ignore": "^7.0.5",
- "natural-compare": "^1.4.0",
- "ts-api-utils": "^2.4.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "@typescript-eslint/parser": "^8.52.0",
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
- "version": "7.0.5",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
- "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/@typescript-eslint/parser": {
- "version": "8.52.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.52.0.tgz",
- "integrity": "sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/scope-manager": "8.52.0",
- "@typescript-eslint/types": "8.52.0",
- "@typescript-eslint/typescript-estree": "8.52.0",
- "@typescript-eslint/visitor-keys": "8.52.0",
- "debug": "^4.4.3"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/project-service": {
- "version": "8.52.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.52.0.tgz",
- "integrity": "sha512-xD0MfdSdEmeFa3OmVqonHi+Cciab96ls1UhIF/qX/O/gPu5KXD0bY9lu33jj04fjzrXHcuvjBcBC+D3SNSadaw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.52.0",
- "@typescript-eslint/types": "^8.52.0",
- "debug": "^4.4.3"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/scope-manager": {
- "version": "8.52.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.52.0.tgz",
- "integrity": "sha512-ixxqmmCcc1Nf8S0mS0TkJ/3LKcC8mruYJPOU6Ia2F/zUUR4pApW7LzrpU3JmtePbRUTes9bEqRc1Gg4iyRnDzA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.52.0",
- "@typescript-eslint/visitor-keys": "8.52.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.52.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.52.0.tgz",
- "integrity": "sha512-jl+8fzr/SdzdxWJznq5nvoI7qn2tNYV/ZBAEcaFMVXf+K6jmXvAFrgo/+5rxgnL152f//pDEAYAhhBAZGrVfwg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/type-utils": {
- "version": "8.52.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.52.0.tgz",
- "integrity": "sha512-JD3wKBRWglYRQkAtsyGz1AewDu3mTc7NtRjR/ceTyGoPqmdS5oCdx/oZMWD5Zuqmo6/MpsYs0wp6axNt88/2EQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.52.0",
- "@typescript-eslint/typescript-estree": "8.52.0",
- "@typescript-eslint/utils": "8.52.0",
- "debug": "^4.4.3",
- "ts-api-utils": "^2.4.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/types": {
- "version": "8.52.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.52.0.tgz",
- "integrity": "sha512-LWQV1V4q9V4cT4H5JCIx3481iIFxH1UkVk+ZkGGAV1ZGcjGI9IoFOfg3O6ywz8QqCDEp7Inlg6kovMofsNRaGg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.52.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.52.0.tgz",
- "integrity": "sha512-XP3LClsCc0FsTK5/frGjolyADTh3QmsLp6nKd476xNI9CsSsLnmn4f0jrzNoAulmxlmNIpeXuHYeEQv61Q6qeQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/project-service": "8.52.0",
- "@typescript-eslint/tsconfig-utils": "8.52.0",
- "@typescript-eslint/types": "8.52.0",
- "@typescript-eslint/visitor-keys": "8.52.0",
- "debug": "^4.4.3",
- "minimatch": "^9.0.5",
- "semver": "^7.7.3",
- "tinyglobby": "^0.2.15",
- "ts-api-utils": "^2.4.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
- "version": "9.0.9",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
- "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.2"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
- "version": "7.7.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
- "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@typescript-eslint/utils": {
- "version": "8.52.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.52.0.tgz",
- "integrity": "sha512-wYndVMWkweqHpEpwPhwqE2lnD2DxC6WVLupU/DOt/0/v+/+iQbbzO3jOHjmBMnhu0DgLULvOaU4h4pwHYi2oRQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.9.1",
- "@typescript-eslint/scope-manager": "8.52.0",
- "@typescript-eslint/types": "8.52.0",
- "@typescript-eslint/typescript-estree": "8.52.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.52.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.52.0.tgz",
- "integrity": "sha512-ink3/Zofus34nmBsPjow63FP5M7IGff0RKAgqR6+CFpdk22M7aLwC9gOcLGYqr7MczLPzZVERW9hRog3O4n1sQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.52.0",
- "eslint-visitor-keys": "^4.2.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@vitejs/plugin-react": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz",
- "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/core": "^7.28.5",
- "@babel/plugin-transform-react-jsx-self": "^7.27.1",
- "@babel/plugin-transform-react-jsx-source": "^7.27.1",
- "@rolldown/pluginutils": "1.0.0-beta.53",
- "@types/babel__core": "^7.20.5",
- "react-refresh": "^0.18.0"
- },
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- },
- "peerDependencies": {
- "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
- }
- },
- "node_modules/acorn": {
- "version": "8.15.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
- "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/acorn-jsx": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
- "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
- }
- },
- "node_modules/ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true,
- "license": "Python-2.0"
- },
- "node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/baseline-browser-mapping": {
- "version": "2.9.14",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz",
- "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==",
- "dev": true,
- "license": "Apache-2.0",
- "bin": {
- "baseline-browser-mapping": "dist/cli.js"
- }
- },
- "node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/browserslist": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
- "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "baseline-browser-mapping": "^2.9.0",
- "caniuse-lite": "^1.0.30001759",
- "electron-to-chromium": "^1.5.263",
- "node-releases": "^2.0.27",
- "update-browserslist-db": "^1.2.0"
- },
- "bin": {
- "browserslist": "cli.js"
- },
- "engines": {
- "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
- }
- },
- "node_modules/callsites": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
- "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/caniuse-lite": {
- "version": "1.0.30001763",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001763.tgz",
- "integrity": "sha512-mh/dGtq56uN98LlNX9qdbKnzINhX0QzhiWBFEkFfsFO4QyCvL8YegrJAazCwXIeqkIob8BlZPGM3xdnY+sgmvQ==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "CC-BY-4.0"
- },
- "node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/cookie": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
- "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/cross-spawn": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
- "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/csstype": {
- "version": "3.2.3",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
- "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/deep-is": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
- "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/electron-to-chromium": {
- "version": "1.5.267",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
- "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/esbuild": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
- "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=18"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.27.2",
- "@esbuild/android-arm": "0.27.2",
- "@esbuild/android-arm64": "0.27.2",
- "@esbuild/android-x64": "0.27.2",
- "@esbuild/darwin-arm64": "0.27.2",
- "@esbuild/darwin-x64": "0.27.2",
- "@esbuild/freebsd-arm64": "0.27.2",
- "@esbuild/freebsd-x64": "0.27.2",
- "@esbuild/linux-arm": "0.27.2",
- "@esbuild/linux-arm64": "0.27.2",
- "@esbuild/linux-ia32": "0.27.2",
- "@esbuild/linux-loong64": "0.27.2",
- "@esbuild/linux-mips64el": "0.27.2",
- "@esbuild/linux-ppc64": "0.27.2",
- "@esbuild/linux-riscv64": "0.27.2",
- "@esbuild/linux-s390x": "0.27.2",
- "@esbuild/linux-x64": "0.27.2",
- "@esbuild/netbsd-arm64": "0.27.2",
- "@esbuild/netbsd-x64": "0.27.2",
- "@esbuild/openbsd-arm64": "0.27.2",
- "@esbuild/openbsd-x64": "0.27.2",
- "@esbuild/openharmony-arm64": "0.27.2",
- "@esbuild/sunos-x64": "0.27.2",
- "@esbuild/win32-arm64": "0.27.2",
- "@esbuild/win32-ia32": "0.27.2",
- "@esbuild/win32-x64": "0.27.2"
- }
- },
- "node_modules/escalade": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
- "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/escape-string-regexp": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
- "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/eslint": {
- "version": "9.39.2",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
- "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.8.0",
- "@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.21.1",
- "@eslint/config-helpers": "^0.4.2",
- "@eslint/core": "^0.17.0",
- "@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.39.2",
- "@eslint/plugin-kit": "^0.4.1",
- "@humanfs/node": "^0.16.6",
- "@humanwhocodes/module-importer": "^1.0.1",
- "@humanwhocodes/retry": "^0.4.2",
- "@types/estree": "^1.0.6",
- "ajv": "^6.12.4",
- "chalk": "^4.0.0",
- "cross-spawn": "^7.0.6",
- "debug": "^4.3.2",
- "escape-string-regexp": "^4.0.0",
- "eslint-scope": "^8.4.0",
- "eslint-visitor-keys": "^4.2.1",
- "espree": "^10.4.0",
- "esquery": "^1.5.0",
- "esutils": "^2.0.2",
- "fast-deep-equal": "^3.1.3",
- "file-entry-cache": "^8.0.0",
- "find-up": "^5.0.0",
- "glob-parent": "^6.0.2",
- "ignore": "^5.2.0",
- "imurmurhash": "^0.1.4",
- "is-glob": "^4.0.0",
- "json-stable-stringify-without-jsonify": "^1.0.1",
- "lodash.merge": "^4.6.2",
- "minimatch": "^3.1.2",
- "natural-compare": "^1.4.0",
- "optionator": "^0.9.3"
- },
- "bin": {
- "eslint": "bin/eslint.js"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://eslint.org/donate"
- },
- "peerDependencies": {
- "jiti": "*"
- },
- "peerDependenciesMeta": {
- "jiti": {
- "optional": true
- }
- }
- },
- "node_modules/eslint-plugin-react-hooks": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz",
- "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/core": "^7.24.4",
- "@babel/parser": "^7.24.4",
- "hermes-parser": "^0.25.1",
- "zod": "^3.25.0 || ^4.0.0",
- "zod-validation-error": "^3.5.0 || ^4.0.0"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
- }
- },
- "node_modules/eslint-plugin-react-refresh": {
- "version": "0.4.26",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz",
- "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "eslint": ">=8.40"
- }
- },
- "node_modules/eslint-scope": {
- "version": "8.4.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
- "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "esrecurse": "^4.3.0",
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/eslint-visitor-keys": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
- "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/espree": {
- "version": "10.4.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
- "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "acorn": "^8.15.0",
- "acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^4.2.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/esquery": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
- "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "estraverse": "^5.1.0"
- },
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/esrecurse": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
- "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/estraverse": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
- "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/esutils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-json-stable-stringify": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-levenshtein": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fdir": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
- "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12.0.0"
- },
- "peerDependencies": {
- "picomatch": "^3 || ^4"
- },
- "peerDependenciesMeta": {
- "picomatch": {
- "optional": true
- }
- }
- },
- "node_modules/file-entry-cache": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
- "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "flat-cache": "^4.0.0"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/find-up": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
- "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "locate-path": "^6.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/flat-cache": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
- "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "flatted": "^3.2.9",
- "keyv": "^4.5.4"
- },
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/flatted": {
- "version": "3.4.2",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
- "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/gensync": {
- "version": "1.0.0-beta.2",
- "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
- "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/glob-parent": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.3"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/globals": {
- "version": "16.5.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz",
- "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/hermes-estree": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
- "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/hermes-parser": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
- "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "hermes-estree": "0.25.1"
- }
- },
- "node_modules/ignore": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
- "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/import-fresh": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
- "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "parent-module": "^1.0.0",
- "resolve-from": "^4.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/imurmurhash": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.8.19"
- }
- },
- "node_modules/is-extglob": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-glob": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
- "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-extglob": "^2.1.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/js-yaml": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
- "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "argparse": "^2.0.1"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
- "node_modules/jsesc": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
- "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "jsesc": "bin/jsesc"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/json-buffer": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
- "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/json-schema-traverse": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/json-stable-stringify-without-jsonify": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
- "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/json5": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
- "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "json5": "lib/cli.js"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/keyv": {
- "version": "4.5.4",
- "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
- "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "json-buffer": "3.0.1"
- }
- },
- "node_modules/levn": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
- "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "prelude-ls": "^1.2.1",
- "type-check": "~0.4.0"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/locate-path": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
- "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-locate": "^5.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/lodash.merge": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
- "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/lru-cache": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
- "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "yallist": "^3.0.2"
- }
- },
- "node_modules/minimatch": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
- "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/nanoid": {
- "version": "3.3.11",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
- "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "bin": {
- "nanoid": "bin/nanoid.cjs"
- },
- "engines": {
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
- }
- },
- "node_modules/natural-compare": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
- "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/node-releases": {
- "version": "2.0.27",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
- "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/optionator": {
- "version": "0.9.4",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
- "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "deep-is": "^0.1.3",
- "fast-levenshtein": "^2.0.6",
- "levn": "^0.4.1",
- "prelude-ls": "^1.2.1",
- "type-check": "^0.4.0",
- "word-wrap": "^1.2.5"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/p-limit": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "yocto-queue": "^0.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-locate": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
- "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^3.0.2"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/parent-module": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
- "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "callsites": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/path-exists": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
- "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-key": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
- "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/picocolors": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/picomatch": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/postcss": {
- "version": "8.5.6",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
- "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "nanoid": "^3.3.11",
- "picocolors": "^1.1.1",
- "source-map-js": "^1.2.1"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
- "node_modules/prelude-ls": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
- "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/punycode": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
- "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/react": {
- "version": "19.2.3",
- "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
- "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/react-dom": {
- "version": "19.2.3",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
- "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
- "license": "MIT",
- "dependencies": {
- "scheduler": "^0.27.0"
- },
- "peerDependencies": {
- "react": "^19.2.3"
- }
- },
- "node_modules/react-refresh": {
- "version": "0.18.0",
- "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
- "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/react-router": {
- "version": "7.12.0",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.12.0.tgz",
- "integrity": "sha512-kTPDYPFzDVGIIGNLS5VJykK0HfHLY5MF3b+xj0/tTyNYL1gF1qs7u67Z9jEhQk2sQ98SUaHxlG31g1JtF7IfVw==",
- "license": "MIT",
- "dependencies": {
- "cookie": "^1.0.1",
- "set-cookie-parser": "^2.6.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "react": ">=18",
- "react-dom": ">=18"
- },
- "peerDependenciesMeta": {
- "react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/react-router-dom": {
- "version": "7.12.0",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.12.0.tgz",
- "integrity": "sha512-pfO9fiBcpEfX4Tx+iTYKDtPbrSLLCbwJ5EqP+SPYQu1VYCXdy79GSj0wttR0U4cikVdlImZuEZ/9ZNCgoaxwBA==",
- "license": "MIT",
- "dependencies": {
- "react-router": "7.12.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "react": ">=18",
- "react-dom": ">=18"
- }
- },
- "node_modules/resolve-from": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
- "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/rollup": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
- "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/estree": "1.0.8"
- },
- "bin": {
- "rollup": "dist/bin/rollup"
- },
- "engines": {
- "node": ">=18.0.0",
- "npm": ">=8.0.0"
- },
- "optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.59.0",
- "@rollup/rollup-android-arm64": "4.59.0",
- "@rollup/rollup-darwin-arm64": "4.59.0",
- "@rollup/rollup-darwin-x64": "4.59.0",
- "@rollup/rollup-freebsd-arm64": "4.59.0",
- "@rollup/rollup-freebsd-x64": "4.59.0",
- "@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
- "@rollup/rollup-linux-arm-musleabihf": "4.59.0",
- "@rollup/rollup-linux-arm64-gnu": "4.59.0",
- "@rollup/rollup-linux-arm64-musl": "4.59.0",
- "@rollup/rollup-linux-loong64-gnu": "4.59.0",
- "@rollup/rollup-linux-loong64-musl": "4.59.0",
- "@rollup/rollup-linux-ppc64-gnu": "4.59.0",
- "@rollup/rollup-linux-ppc64-musl": "4.59.0",
- "@rollup/rollup-linux-riscv64-gnu": "4.59.0",
- "@rollup/rollup-linux-riscv64-musl": "4.59.0",
- "@rollup/rollup-linux-s390x-gnu": "4.59.0",
- "@rollup/rollup-linux-x64-gnu": "4.59.0",
- "@rollup/rollup-linux-x64-musl": "4.59.0",
- "@rollup/rollup-openbsd-x64": "4.59.0",
- "@rollup/rollup-openharmony-arm64": "4.59.0",
- "@rollup/rollup-win32-arm64-msvc": "4.59.0",
- "@rollup/rollup-win32-ia32-msvc": "4.59.0",
- "@rollup/rollup-win32-x64-gnu": "4.59.0",
- "@rollup/rollup-win32-x64-msvc": "4.59.0",
- "fsevents": "~2.3.2"
- }
- },
- "node_modules/scheduler": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
- "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
- "license": "MIT"
- },
- "node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/set-cookie-parser": {
- "version": "2.7.2",
- "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
- "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
- "license": "MIT"
- },
- "node_modules/shebang-command": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
- "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "shebang-regex": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/shebang-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
- "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/strip-json-comments": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
- "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/tinyglobby": {
- "version": "0.2.15",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
- "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fdir": "^6.5.0",
- "picomatch": "^4.0.3"
- },
- "engines": {
- "node": ">=12.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/SuperchupuDev"
- }
- },
- "node_modules/ts-api-utils": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
- "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18.12"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4"
- }
- },
- "node_modules/type-check": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
- "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "prelude-ls": "^1.2.1"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/typescript": {
- "version": "5.9.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
- "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
- "dev": true,
- "license": "Apache-2.0",
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=14.17"
- }
- },
- "node_modules/typescript-eslint": {
- "version": "8.52.0",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.52.0.tgz",
- "integrity": "sha512-atlQQJ2YkO4pfTVQmQ+wvYQwexPDOIgo+RaVcD7gHgzy/IQA+XTyuxNM9M9TVXvttkF7koBHmcwisKdOAf2EcA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/eslint-plugin": "8.52.0",
- "@typescript-eslint/parser": "8.52.0",
- "@typescript-eslint/typescript-estree": "8.52.0",
- "@typescript-eslint/utils": "8.52.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/undici-types": {
- "version": "7.16.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
- "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/update-browserslist-db": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
- "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "escalade": "^3.2.0",
- "picocolors": "^1.1.1"
- },
- "bin": {
- "update-browserslist-db": "cli.js"
- },
- "peerDependencies": {
- "browserslist": ">= 4.21.0"
- }
- },
- "node_modules/uri-js": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
- "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "punycode": "^2.1.0"
- }
- },
- "node_modules/vite": {
- "version": "7.3.1",
- "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
- "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "esbuild": "^0.27.0",
- "fdir": "^6.5.0",
- "picomatch": "^4.0.3",
- "postcss": "^8.5.6",
- "rollup": "^4.43.0",
- "tinyglobby": "^0.2.15"
- },
- "bin": {
- "vite": "bin/vite.js"
- },
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- },
- "funding": {
- "url": "https://github.com/vitejs/vite?sponsor=1"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.3"
- },
- "peerDependencies": {
- "@types/node": "^20.19.0 || >=22.12.0",
- "jiti": ">=1.21.0",
- "less": "^4.0.0",
- "lightningcss": "^1.21.0",
- "sass": "^1.70.0",
- "sass-embedded": "^1.70.0",
- "stylus": ">=0.54.8",
- "sugarss": "^5.0.0",
- "terser": "^5.16.0",
- "tsx": "^4.8.1",
- "yaml": "^2.4.2"
- },
- "peerDependenciesMeta": {
- "@types/node": {
- "optional": true
- },
- "jiti": {
- "optional": true
- },
- "less": {
- "optional": true
- },
- "lightningcss": {
- "optional": true
- },
- "sass": {
- "optional": true
- },
- "sass-embedded": {
- "optional": true
- },
- "stylus": {
- "optional": true
- },
- "sugarss": {
- "optional": true
- },
- "terser": {
- "optional": true
- },
- "tsx": {
- "optional": true
- },
- "yaml": {
- "optional": true
- }
- }
- },
- "node_modules/which": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
- "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "isexe": "^2.0.0"
- },
- "bin": {
- "node-which": "bin/node-which"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/word-wrap": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
- "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/yallist": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
- "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/yocto-queue": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
- "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/zod": {
- "version": "4.3.5",
- "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz",
- "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/colinhacks"
- }
- },
- "node_modules/zod-validation-error": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
- "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18.0.0"
- },
- "peerDependencies": {
- "zod": "^3.25.0 || ^4.0.0"
- }
- }
- }
-}
diff --git a/frontend/package.json b/frontend/package.json
deleted file mode 100644
index 1593359..0000000
--- a/frontend/package.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- "name": "frontend",
- "private": true,
- "version": "0.0.0",
- "type": "module",
- "scripts": {
- "dev": "vite",
- "build": "tsc -b && vite build",
- "lint": "eslint .",
- "preview": "vite preview"
- },
- "dependencies": {
- "react": "^19.2.0",
- "react-dom": "^19.2.0",
- "react-router-dom": "^7.12.0"
- },
- "devDependencies": {
- "@eslint/js": "^9.39.1",
- "@types/node": "^24.10.1",
- "@types/react": "^19.2.5",
- "@types/react-dom": "^19.2.3",
- "@vitejs/plugin-react": "^5.1.1",
- "eslint": "^9.39.1",
- "eslint-plugin-react-hooks": "^7.0.1",
- "eslint-plugin-react-refresh": "^0.4.24",
- "globals": "^16.5.0",
- "typescript": "~5.9.3",
- "typescript-eslint": "^8.46.4",
- "vite": "^7.2.4"
- }
-}
diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg
deleted file mode 100644
index e7b8dfb..0000000
--- a/frontend/public/vite.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/frontend/src/App.css b/frontend/src/App.css
deleted file mode 100644
index 6cb3d18..0000000
--- a/frontend/src/App.css
+++ /dev/null
@@ -1,654 +0,0 @@
-* {
- box-sizing: border-box;
-}
-
-body {
- margin: 0;
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
- background: var(--color-bg);
- color: var(--color-text);
-}
-
-.app {
- max-width: 1000px;
- margin: 0 auto;
- padding: 20px;
-}
-
-nav {
- display: flex;
- align-items: center;
- gap: 20px;
- padding: 15px 0;
- border-bottom: 1px solid var(--color-border);
- margin-bottom: 20px;
-}
-
-.theme-toggle {
- margin-left: auto;
-}
-
-nav a {
- color: var(--color-primary);
- text-decoration: none;
- font-weight: 500;
-}
-
-nav a:hover {
- text-decoration: underline;
-}
-
-main {
- background: var(--color-bg-card);
- padding: 20px;
- border-radius: 8px;
- box-shadow: var(--shadow);
-}
-
-.header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 20px;
-}
-
-.header h1 {
- margin: 0;
-}
-
-.btn {
- display: inline-block;
- padding: 8px 16px;
- border: 1px solid var(--color-border);
- border-radius: 4px;
- background: var(--color-bg-card);
- color: var(--color-text);
- text-decoration: none;
- cursor: pointer;
- font-size: 14px;
- margin-left: 8px;
-}
-
-.btn:hover {
- background: var(--color-bg-hover);
-}
-
-.btn-primary {
- background: var(--color-primary);
- border-color: var(--color-primary);
- color: white;
-}
-
-.btn-primary:hover {
- background: var(--color-primary-hover);
-}
-
-.btn-danger {
- background: var(--color-danger);
- border-color: var(--color-danger);
- color: white;
-}
-
-.btn-danger:hover {
- background: var(--color-danger-hover);
-}
-
-.btn-small {
- padding: 4px 8px;
- font-size: 12px;
-}
-
-.btn:disabled {
- opacity: 0.6;
- cursor: not-allowed;
-}
-
-table {
- width: 100%;
- border-collapse: collapse;
-}
-
-th,
-td {
- padding: 12px;
- text-align: left;
- border-bottom: 1px solid var(--color-border-light);
-}
-
-th {
- font-weight: 600;
- background: var(--color-bg-muted);
-}
-
-tr:hover {
- background: var(--color-bg-muted);
-}
-
-.error {
- background: var(--color-bg-error);
- color: var(--color-text-error);
- padding: 10px;
- border-radius: 4px;
- margin-bottom: 20px;
-}
-
-.info-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
- gap: 10px;
- margin-bottom: 20px;
-}
-
-.section {
- margin-top: 30px;
- padding-top: 20px;
- border-top: 1px solid var(--color-border-light);
-}
-
-.section h3 {
- margin-top: 0;
- margin-bottom: 15px;
-}
-
-.section h4 {
- margin: 15px 0 10px;
- font-size: 14px;
- color: var(--color-text-muted);
-}
-
-.section ul {
- list-style: none;
- padding: 0;
- margin: 0;
-}
-
-.section li {
- display: flex;
- align-items: center;
- gap: 10px;
- padding: 8px 0;
- border-bottom: 1px solid var(--color-border-lighter);
-}
-
-.tag {
- display: inline-block;
- background: var(--color-tag-bg);
- padding: 2px 8px;
- border-radius: 12px;
- font-size: 12px;
- color: var(--color-text-muted);
-}
-
-.add-form {
- display: flex;
- gap: 10px;
- margin-top: 15px;
- flex-wrap: wrap;
-}
-
-.add-form select,
-.add-form input {
- padding: 8px;
- border: 1px solid var(--color-border);
- border-radius: 4px;
- min-width: 200px;
- background: var(--color-bg-card);
- color: var(--color-text);
-}
-
-.form-group {
- margin-bottom: 20px;
-}
-
-.form-group label {
- display: block;
- font-weight: 500;
- margin-bottom: 5px;
-}
-
-.form-group input,
-.form-group textarea,
-.form-group select {
- width: 100%;
- padding: 10px;
- border: 1px solid var(--color-border);
- border-radius: 4px;
- font-size: 14px;
- background: var(--color-bg-card);
- color: var(--color-text);
-}
-
-.form-group textarea {
- resize: vertical;
-}
-
-.form-row {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 20px;
-}
-
-.checkbox-group {
- display: flex;
- flex-wrap: wrap;
- gap: 15px;
-}
-
-.checkbox-label {
- display: flex;
- align-items: center;
- gap: 5px;
- cursor: pointer;
-}
-
-.form-actions {
- display: flex;
- gap: 10px;
- margin-top: 30px;
- padding-top: 20px;
- border-top: 1px solid var(--color-border-light);
-}
-
-.need-list .header {
- margin-bottom: 20px;
-}
-
-.need-form {
- background: var(--color-bg-muted);
- padding: 20px;
- border-radius: 4px;
- margin-bottom: 20px;
-}
-
-.need-items {
- list-style: none;
- padding: 0;
-}
-
-.need-items li {
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- padding: 15px;
- border: 1px solid var(--color-border-light);
- border-radius: 4px;
- margin-bottom: 10px;
-}
-
-.need-info p {
- margin: 5px 0 0;
- color: var(--color-text-muted);
- font-size: 14px;
-}
-
-a {
- color: var(--color-primary);
-}
-
-a:hover {
- text-decoration: underline;
-}
-
-/* Graph styles */
-.graph-container {
- width: 100%;
-}
-
-.graph-hint {
- color: var(--color-text-muted);
- font-size: 14px;
- margin-bottom: 15px;
-}
-
-.selected-info {
- margin-top: 15px;
- padding: 15px;
- background: var(--color-bg-muted);
- border-radius: 8px;
-}
-
-.selected-info h3 {
- margin: 0 0 10px;
-}
-
-.selected-info p {
- margin: 5px 0;
- color: var(--color-text-muted);
-}
-
-.legend {
- margin-top: 20px;
- padding: 15px;
- background: var(--color-bg-muted);
- border-radius: 8px;
-}
-
-.legend h4 {
- margin: 0 0 10px;
- font-size: 14px;
-}
-
-.legend-items {
- display: flex;
- flex-wrap: wrap;
- gap: 15px;
-}
-
-.legend-item {
- display: flex;
- align-items: center;
- gap: 8px;
- font-size: 12px;
- color: var(--color-text-muted);
-}
-
-.legend-line {
- width: 30px;
- border-radius: 2px;
-}
-
-/* Weight control styles */
-.weight-control {
- display: flex;
- align-items: center;
- gap: 8px;
- font-size: 12px;
- color: var(--color-text-muted);
-}
-
-.weight-control input[type="range"] {
- width: 80px;
- cursor: pointer;
-}
-
-.weight-value {
- min-width: 20px;
- text-align: center;
- font-weight: 600;
-}
-
-.weight-display {
- font-size: 12px;
- color: var(--color-text-muted);
- margin-left: auto;
-}
-
-/* ID Card Styles */
-.id-card {
- width: 100%;
-}
-
-.id-card-inner {
- background: linear-gradient(135deg, #0a0a0f 0%, #1a1a2e 50%, #0a0a0f 100%);
- background-image:
- radial-gradient(white 1px, transparent 1px),
- linear-gradient(135deg, #0a0a0f 0%, #1a1a2e 50%, #0a0a0f 100%);
- background-size: 50px 50px, 100% 100%;
- background-position: 0 0, 0 0;
- color: #fff;
- border-radius: 12px;
- padding: 25px;
- min-height: 500px;
- position: relative;
- overflow: hidden;
-}
-
-.id-card-header {
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- margin-bottom: 15px;
-}
-
-.id-card-header-left {
- flex: 1;
-}
-
-.id-card-header-right {
- display: flex;
- flex-direction: column;
- align-items: flex-end;
- gap: 10px;
-}
-
-.id-card-title {
- font-size: 2.5rem;
- font-weight: 700;
- margin: 0;
- color: #fff;
- text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
-}
-
-.id-profile-pic {
- width: 80px;
- height: 80px;
- border-radius: 8px;
- object-fit: cover;
- border: 2px solid rgba(255,255,255,0.3);
-}
-
-.id-profile-placeholder {
- width: 80px;
- height: 80px;
- border-radius: 8px;
- background: linear-gradient(135deg, #4ecdc4 0%, #44a8a0 100%);
- display: flex;
- align-items: center;
- justify-content: center;
- border: 2px solid rgba(255,255,255,0.3);
-}
-
-.id-profile-placeholder span {
- font-size: 2rem;
- font-weight: 700;
- color: #fff;
- text-shadow: 1px 1px 2px rgba(0,0,0,0.3);
-}
-
-.id-card-actions {
- display: flex;
- gap: 8px;
-}
-
-.id-card-actions .btn {
- background: rgba(255,255,255,0.1);
- border-color: rgba(255,255,255,0.3);
- color: #fff;
-}
-
-.id-card-actions .btn:hover {
- background: rgba(255,255,255,0.2);
-}
-
-.id-card-body {
- display: grid;
- grid-template-columns: 1fr 1.5fr;
- gap: 30px;
-}
-
-.id-card-left {
- display: flex;
- flex-direction: column;
- gap: 8px;
-}
-
-.id-field {
- font-size: 1rem;
- line-height: 1.4;
-}
-
-.id-field-block {
- margin-top: 15px;
- font-size: 0.95rem;
- line-height: 1.5;
-}
-
-.id-label {
- color: #4ecdc4;
- font-weight: 500;
-}
-
-.id-card-right {
- display: flex;
- flex-direction: column;
- gap: 20px;
-}
-
-.id-bio {
- font-size: 0.9rem;
- line-height: 1.6;
- color: #e0e0e0;
-}
-
-.id-relationships {
- margin-top: 10px;
-}
-
-.id-section-title {
- font-size: 1.5rem;
- margin: 0 0 15px;
- color: #fff;
- border-bottom: 1px solid rgba(255,255,255,0.2);
- padding-bottom: 8px;
-}
-
-.id-rel-group {
- margin-bottom: 12px;
- font-size: 0.9rem;
- line-height: 1.6;
-}
-
-.id-rel-label {
- color: #a0a0a0;
-}
-
-.id-rel-group a {
- color: #4ecdc4;
- text-decoration: none;
-}
-
-.id-rel-group a:hover {
- text-decoration: underline;
-}
-
-.id-rel-type {
- color: #888;
- font-size: 0.85em;
-}
-
-.id-card-warnings {
- margin-top: 30px;
- padding-top: 20px;
- border-top: 1px solid rgba(255,255,255,0.2);
- display: flex;
- flex-wrap: wrap;
- gap: 20px;
-}
-
-.id-warning {
- display: flex;
- align-items: center;
- gap: 8px;
- font-size: 0.9rem;
- color: #ff6b6b;
-}
-
-.warning-dot {
- width: 8px;
- height: 8px;
- background: #ff6b6b;
- border-radius: 50%;
- flex-shrink: 0;
-}
-
-.warning-desc {
- color: #ccc;
-}
-
-/* Management section */
-.id-card-manage {
- margin-top: 20px;
- background: var(--color-bg-muted);
- border-radius: 8px;
- padding: 15px;
-}
-
-.id-card-manage summary {
- cursor: pointer;
- font-weight: 600;
- font-size: 1.1rem;
- padding: 5px 0;
-}
-
-.id-card-manage[open] summary {
- margin-bottom: 15px;
- border-bottom: 1px solid var(--color-border-light);
- padding-bottom: 10px;
-}
-
-.manage-section {
- margin-bottom: 25px;
-}
-
-.manage-section h3 {
- margin: 0 0 15px;
- font-size: 1rem;
-}
-
-.manage-relationships {
- display: flex;
- flex-direction: column;
- gap: 10px;
- margin-bottom: 15px;
-}
-
-.manage-rel-item {
- display: flex;
- align-items: center;
- gap: 12px;
- padding: 10px;
- background: var(--color-bg-card);
- border-radius: 6px;
- flex-wrap: wrap;
-}
-
-.manage-rel-item a {
- font-weight: 500;
- min-width: 120px;
-}
-
-.manage-needs-list {
- list-style: none;
- padding: 0;
- margin: 0 0 15px;
-}
-
-.manage-needs-list li {
- display: flex;
- align-items: center;
- gap: 12px;
- padding: 10px;
- background: var(--color-bg-card);
- border-radius: 6px;
- margin-bottom: 8px;
-}
-
-.manage-needs-list li .btn {
- margin-left: auto;
-}
-
-/* Responsive adjustments */
-@media (max-width: 768px) {
- .id-card-body {
- grid-template-columns: 1fr;
- }
-
- .id-card-title {
- font-size: 1.8rem;
- }
-
- .id-card-header {
- flex-direction: column;
- gap: 15px;
- }
-}
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
deleted file mode 100644
index 7255efb..0000000
--- a/frontend/src/App.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import { useEffect, useState } from "react";
-import { Link, Route, Routes } from "react-router-dom";
-import { ContactDetail } from "./components/ContactDetail";
-import { ContactForm } from "./components/ContactForm";
-import { ContactList } from "./components/ContactList";
-import { NeedList } from "./components/NeedList";
-import { RelationshipGraph } from "./components/RelationshipGraph";
-import "./App.css";
-
-function App() {
- const [theme, setTheme] = useState<"light" | "dark">(() => {
- return (localStorage.getItem("theme") as "light" | "dark") || "light";
- });
-
- useEffect(() => {
- document.documentElement.setAttribute("data-theme", theme);
- localStorage.setItem("theme", theme);
- }, [theme]);
-
- const toggleTheme = () => {
- setTheme((prev) => (prev === "light" ? "dark" : "light"));
- };
-
- return (
-
-
-
-
-
- } />
- } />
- } />
- } />
- } />
- } />
- } />
-
-
-
- );
-}
-
-export default App;
diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts
deleted file mode 100644
index 7d77bdc..0000000
--- a/frontend/src/api/client.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-import type {
- Contact,
- ContactCreate,
- ContactListItem,
- ContactRelationship,
- ContactRelationshipCreate,
- ContactRelationshipUpdate,
- ContactUpdate,
- GraphData,
- Need,
- NeedCreate,
-} from "../types";
-
-const API_BASE = "";
-
-async function request(
- endpoint: string,
- options?: RequestInit
-): Promise {
- const response = await fetch(`${API_BASE}${endpoint}`, {
- ...options,
- headers: {
- "Content-Type": "application/json",
- ...options?.headers,
- },
- });
-
- if (!response.ok) {
- const error = await response.json().catch(() => ({}));
- throw new Error(error.detail || `HTTP ${response.status}`);
- }
-
- return response.json();
-}
-
-export const api = {
- // Needs
- needs: {
- list: () => request("/api/needs"),
- get: (id: number) => request(`/api/needs/${id}`),
- create: (data: NeedCreate) =>
- request("/api/needs", {
- method: "POST",
- body: JSON.stringify(data),
- }),
- delete: (id: number) =>
- request<{ deleted: boolean }>(`/api/needs/${id}`, { method: "DELETE" }),
- },
-
- // Contacts
- contacts: {
- list: (skip = 0, limit = 100) =>
- request(`/api/contacts?skip=${skip}&limit=${limit}`),
- get: (id: number) => request(`/api/contacts/${id}`),
- create: (data: ContactCreate) =>
- request("/api/contacts", {
- method: "POST",
- body: JSON.stringify(data),
- }),
- update: (id: number, data: ContactUpdate) =>
- request(`/api/contacts/${id}`, {
- method: "PATCH",
- body: JSON.stringify(data),
- }),
- delete: (id: number) =>
- request<{ deleted: boolean }>(`/api/contacts/${id}`, { method: "DELETE" }),
-
- // Contact-Need relationships
- addNeed: (contactId: number, needId: number) =>
- request<{ added: boolean }>(`/api/contacts/${contactId}/needs/${needId}`, {
- method: "POST",
- }),
- removeNeed: (contactId: number, needId: number) =>
- request<{ removed: boolean }>(`/api/contacts/${contactId}/needs/${needId}`, {
- method: "DELETE",
- }),
-
- // Contact-Contact relationships
- getRelationships: (contactId: number) =>
- request(`/api/contacts/${contactId}/relationships`),
- addRelationship: (contactId: number, data: ContactRelationshipCreate) =>
- request(`/api/contacts/${contactId}/relationships`, {
- method: "POST",
- body: JSON.stringify(data),
- }),
- updateRelationship: (contactId: number, relatedContactId: number, data: ContactRelationshipUpdate) =>
- request(
- `/api/contacts/${contactId}/relationships/${relatedContactId}`,
- {
- method: "PATCH",
- body: JSON.stringify(data),
- }
- ),
- removeRelationship: (contactId: number, relatedContactId: number) =>
- request<{ deleted: boolean }>(
- `/api/contacts/${contactId}/relationships/${relatedContactId}`,
- { method: "DELETE" }
- ),
- },
-
- // Graph
- graph: {
- get: () => request("/api/graph"),
- },
-};
diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg
deleted file mode 100644
index 6c87de9..0000000
--- a/frontend/src/assets/react.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/frontend/src/components/ContactDetail.tsx b/frontend/src/components/ContactDetail.tsx
deleted file mode 100644
index 66b0df5..0000000
--- a/frontend/src/components/ContactDetail.tsx
+++ /dev/null
@@ -1,456 +0,0 @@
-import { useEffect, useState } from "react";
-import { Link, useParams } from "react-router-dom";
-import { api } from "../api/client";
-import type { Contact, ContactListItem, Need, RelationshipTypeValue } from "../types";
-import { RELATIONSHIP_TYPES } from "../types";
-
-export function ContactDetail() {
- const { id } = useParams<{ id: string }>();
- const [contact, setContact] = useState(null);
- const [allNeeds, setAllNeeds] = useState([]);
- const [allContacts, setAllContacts] = useState([]);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState(null);
-
- const [newNeedId, setNewNeedId] = useState("");
- const [newRelContactId, setNewRelContactId] = useState("");
- const [newRelType, setNewRelType] = useState("");
-
- useEffect(() => {
- if (!id) return;
- Promise.all([
- api.contacts.get(Number(id)),
- api.needs.list(),
- api.contacts.list(),
- ])
- .then(([c, n, contacts]) => {
- setContact(c);
- setAllNeeds(n);
- setAllContacts(contacts.filter((ct) => ct.id !== Number(id)));
- })
- .catch((err) => setError(err.message))
- .finally(() => setLoading(false));
- }, [id]);
-
- const handleAddNeed = async () => {
- if (!contact || newNeedId === "") return;
- try {
- await api.contacts.addNeed(contact.id, Number(newNeedId));
- const updated = await api.contacts.get(contact.id);
- setContact(updated);
- setNewNeedId("");
- } catch (err) {
- setError(err instanceof Error ? err.message : "Failed to add need");
- }
- };
-
- const handleRemoveNeed = async (needId: number) => {
- if (!contact) return;
- try {
- await api.contacts.removeNeed(contact.id, needId);
- const updated = await api.contacts.get(contact.id);
- setContact(updated);
- } catch (err) {
- setError(err instanceof Error ? err.message : "Failed to remove need");
- }
- };
-
- const handleAddRelationship = async () => {
- if (!contact || newRelContactId === "" || newRelType === "") return;
- try {
- await api.contacts.addRelationship(contact.id, {
- related_contact_id: Number(newRelContactId),
- relationship_type: newRelType,
- });
- const updated = await api.contacts.get(contact.id);
- setContact(updated);
- setNewRelContactId("");
- setNewRelType("");
- } catch (err) {
- setError(
- err instanceof Error ? err.message : "Failed to add relationship"
- );
- }
- };
-
- const handleRemoveRelationship = async (relatedContactId: number) => {
- if (!contact) return;
- try {
- await api.contacts.removeRelationship(contact.id, relatedContactId);
- const updated = await api.contacts.get(contact.id);
- setContact(updated);
- } catch (err) {
- setError(
- err instanceof Error ? err.message : "Failed to remove relationship"
- );
- }
- };
-
- const handleUpdateWeight = async (relatedContactId: number, newWeight: number) => {
- if (!contact) return;
- try {
- await api.contacts.updateRelationship(contact.id, relatedContactId, {
- closeness_weight: newWeight,
- });
- const updated = await api.contacts.get(contact.id);
- setContact(updated);
- } catch (err) {
- setError(
- err instanceof Error ? err.message : "Failed to update weight"
- );
- }
- };
-
- if (loading) return Loading...
;
- if (error) return Error: {error}
;
- if (!contact) return Contact not found
;
-
- const availableNeeds = allNeeds.filter(
- (n) => !contact.needs.some((cn) => cn.id === n.id)
- );
-
- const getContactName = (contactId: number) => {
- const c = allContacts.find((ct) => ct.id === contactId);
- return c?.name || `Contact #${contactId}`;
- };
-
- const getRelationshipDisplayName = (type: string) => {
- const rt = RELATIONSHIP_TYPES.find((r) => r.value === type);
- return rt?.displayName || type;
- };
-
- // Group relationships by category for display
- const groupRelationships = () => {
- const familial: typeof contact.related_to = [];
- const friends: typeof contact.related_to = [];
- const partners: typeof contact.related_to = [];
- const professional: typeof contact.related_to = [];
- const other: typeof contact.related_to = [];
-
- const familialTypes = ['parent', 'child', 'sibling', 'grandparent', 'grandchild', 'aunt_uncle', 'niece_nephew', 'cousin', 'in_law'];
- const friendTypes = ['best_friend', 'close_friend', 'friend', 'acquaintance', 'neighbor'];
- const partnerTypes = ['spouse', 'partner'];
- const professionalTypes = ['mentor', 'mentee', 'business_partner', 'colleague', 'manager', 'direct_report', 'client'];
-
- for (const rel of contact.related_to) {
- if (familialTypes.includes(rel.relationship_type)) {
- familial.push(rel);
- } else if (friendTypes.includes(rel.relationship_type)) {
- friends.push(rel);
- } else if (partnerTypes.includes(rel.relationship_type)) {
- partners.push(rel);
- } else if (professionalTypes.includes(rel.relationship_type)) {
- professional.push(rel);
- } else {
- other.push(rel);
- }
- }
-
- return { familial, friends, partners, professional, other };
- };
-
- const relationshipGroups = groupRelationships();
-
- return (
-
-
- {/* Header with name and profile pic */}
-
-
-
I.D.: {contact.name}
-
-
- {contact.profile_pic ? (
-

- ) : (
-
- {contact.name.charAt(0).toUpperCase()}
-
- )}
-
-
- Edit
-
-
- Back
-
-
-
-
-
-
- {/* Left column - Basic info */}
-
- {contact.legal_name && (
-
Legal name: {contact.legal_name}
- )}
- {contact.suffix && (
-
Suffix: {contact.suffix}
- )}
- {contact.gender && (
-
Gender: {contact.gender}
- )}
- {contact.age && (
-
Age: {contact.age}
- )}
- {contact.current_job && (
-
Job: {contact.current_job}
- )}
- {contact.social_structure_style && (
-
Social style: {contact.social_structure_style}
- )}
- {contact.self_sufficiency_score !== null && (
-
Self-Sufficiency: {contact.self_sufficiency_score}
- )}
- {contact.timezone && (
-
Timezone: {contact.timezone}
- )}
-
- {contact.safe_conversation_starters && (
-
- Safe con starters: {contact.safe_conversation_starters}
-
- )}
-
- {contact.topics_to_avoid && (
-
- Topics to avoid: {contact.topics_to_avoid}
-
- )}
-
- {contact.goals && (
-
- Goals: {contact.goals}
-
- )}
-
-
- {/* Right column - Bio and Relationships */}
-
- {contact.bio && (
-
- Bio: {contact.bio}
-
- )}
-
-
-
Relationships
-
- {relationshipGroups.familial.length > 0 && (
-
- Familial:{" "}
- {relationshipGroups.familial.map((rel, i) => (
-
-
- {getContactName(rel.related_contact_id)}
-
- ({getRelationshipDisplayName(rel.relationship_type)})
- {i < relationshipGroups.familial.length - 1 && ", "}
-
- ))}
-
- )}
-
- {relationshipGroups.partners.length > 0 && (
-
- Partners:{" "}
- {relationshipGroups.partners.map((rel, i) => (
-
-
- {getContactName(rel.related_contact_id)}
-
- {i < relationshipGroups.partners.length - 1 && ", "}
-
- ))}
-
- )}
-
- {relationshipGroups.friends.length > 0 && (
-
- Friends:{" "}
- {relationshipGroups.friends.map((rel, i) => (
-
-
- {getContactName(rel.related_contact_id)}
-
- {i < relationshipGroups.friends.length - 1 && ", "}
-
- ))}
-
- )}
-
- {relationshipGroups.professional.length > 0 && (
-
- Professional:{" "}
- {relationshipGroups.professional.map((rel, i) => (
-
-
- {getContactName(rel.related_contact_id)}
-
- ({getRelationshipDisplayName(rel.relationship_type)})
- {i < relationshipGroups.professional.length - 1 && ", "}
-
- ))}
-
- )}
-
- {relationshipGroups.other.length > 0 && (
-
- Other:{" "}
- {relationshipGroups.other.map((rel, i) => (
-
-
- {getContactName(rel.related_contact_id)}
-
- ({getRelationshipDisplayName(rel.relationship_type)})
- {i < relationshipGroups.other.length - 1 && ", "}
-
- ))}
-
- )}
-
- {contact.related_from.length > 0 && (
-
- Known by:{" "}
- {contact.related_from.map((rel, i) => (
-
-
- {getContactName(rel.contact_id)}
-
- {i < contact.related_from.length - 1 && ", "}
-
- ))}
-
- )}
-
-
-
-
- {/* Needs/Warnings at bottom */}
- {contact.needs.length > 0 && (
-
- {contact.needs.map((need) => (
-
-
- Warning: {need.name}
- {need.description && - {need.description}}
-
- ))}
-
- )}
-
-
- {/* Management section (expandable) */}
-
- Manage Contact
-
-
-
Manage Relationships
-
- {contact.related_to.map((rel) => (
-
-
- {getContactName(rel.related_contact_id)}
-
- {getRelationshipDisplayName(rel.relationship_type)}
-
-
-
- ))}
-
-
- {allContacts.length > 0 && (
-
-
-
-
-
- )}
-
-
-
-
Manage Needs/Warnings
-
- {contact.needs.map((need) => (
- -
- {need.name}
- {need.description && - {need.description}}
-
-
- ))}
-
- {availableNeeds.length > 0 && (
-
-
-
-
- )}
-
-
-
- );
-}
diff --git a/frontend/src/components/ContactForm.tsx b/frontend/src/components/ContactForm.tsx
deleted file mode 100644
index 14e6bd3..0000000
--- a/frontend/src/components/ContactForm.tsx
+++ /dev/null
@@ -1,325 +0,0 @@
-import { useEffect, useState } from "react";
-import { useNavigate, useParams } from "react-router-dom";
-import { api } from "../api/client";
-import type { ContactCreate, Need } from "../types";
-
-export function ContactForm() {
- const { id } = useParams<{ id: string }>();
- const navigate = useNavigate();
- const isEdit = Boolean(id);
-
- const [allNeeds, setAllNeeds] = useState([]);
- const [loading, setLoading] = useState(isEdit);
- const [error, setError] = useState(null);
- const [submitting, setSubmitting] = useState(false);
-
- const [form, setForm] = useState({
- name: "",
- age: null,
- bio: null,
- current_job: null,
- gender: null,
- goals: null,
- legal_name: null,
- profile_pic: null,
- safe_conversation_starters: null,
- self_sufficiency_score: null,
- social_structure_style: null,
- ssn: null,
- suffix: null,
- timezone: null,
- topics_to_avoid: null,
- need_ids: [],
- });
-
- useEffect(() => {
- const loadData = async () => {
- try {
- const needs = await api.needs.list();
- setAllNeeds(needs);
-
- if (id) {
- const contact = await api.contacts.get(Number(id));
- setForm({
- name: contact.name,
- age: contact.age,
- bio: contact.bio,
- current_job: contact.current_job,
- gender: contact.gender,
- goals: contact.goals,
- legal_name: contact.legal_name,
- profile_pic: contact.profile_pic,
- safe_conversation_starters: contact.safe_conversation_starters,
- self_sufficiency_score: contact.self_sufficiency_score,
- social_structure_style: contact.social_structure_style,
- ssn: contact.ssn,
- suffix: contact.suffix,
- timezone: contact.timezone,
- topics_to_avoid: contact.topics_to_avoid,
- need_ids: contact.needs.map((n) => n.id),
- });
- }
- } catch (err) {
- setError(err instanceof Error ? err.message : "Failed to load data");
- } finally {
- setLoading(false);
- }
- };
- loadData();
- }, [id]);
-
- const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
- setSubmitting(true);
- setError(null);
-
- try {
- if (isEdit) {
- await api.contacts.update(Number(id), form);
- navigate(`/contacts/${id}`);
- } else {
- const created = await api.contacts.create(form);
- navigate(`/contacts/${created.id}`);
- }
- } catch (err) {
- setError(err instanceof Error ? err.message : "Save failed");
- setSubmitting(false);
- }
- };
-
- const updateField = (
- field: K,
- value: ContactCreate[K]
- ) => {
- setForm((prev) => ({ ...prev, [field]: value }));
- };
-
- const toggleNeed = (needId: number) => {
- setForm((prev) => ({
- ...prev,
- need_ids: prev.need_ids?.includes(needId)
- ? prev.need_ids.filter((id) => id !== needId)
- : [...(prev.need_ids || []), needId],
- }));
- };
-
- if (loading) return Loading...
;
-
- return (
-
-
{isEdit ? "Edit Contact" : "New Contact"}
-
- {error &&
{error}
}
-
-
-
- );
-}
diff --git a/frontend/src/components/ContactList.tsx b/frontend/src/components/ContactList.tsx
deleted file mode 100644
index 186f0f3..0000000
--- a/frontend/src/components/ContactList.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import { useEffect, useState } from "react";
-import { Link } from "react-router-dom";
-import { api } from "../api/client";
-import type { ContactListItem } from "../types";
-
-export function ContactList() {
- const [contacts, setContacts] = useState([]);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState(null);
-
- useEffect(() => {
- api.contacts
- .list()
- .then(setContacts)
- .catch((err) => setError(err.message))
- .finally(() => setLoading(false));
- }, []);
-
- const handleDelete = async (id: number) => {
- if (!confirm("Delete this contact?")) return;
- try {
- await api.contacts.delete(id);
- setContacts((prev) => prev.filter((c) => c.id !== id));
- } catch (err) {
- setError(err instanceof Error ? err.message : "Delete failed");
- }
- };
-
- if (loading) return Loading...
;
- if (error) return Error: {error}
;
-
- return (
-
-
-
Contacts
-
- Add Contact
-
-
-
- {contacts.length === 0 ? (
-
No contacts yet.
- ) : (
-
-
-
- | Name |
- Job |
- Timezone |
- Actions |
-
-
-
- {contacts.map((contact) => (
-
- |
- {contact.name}
- |
- {contact.current_job || "-"} |
- {contact.timezone || "-"} |
-
-
- Edit
-
-
- |
-
- ))}
-
-
- )}
-
- );
-}
diff --git a/frontend/src/components/NeedList.tsx b/frontend/src/components/NeedList.tsx
deleted file mode 100644
index 9088682..0000000
--- a/frontend/src/components/NeedList.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-import { useEffect, useState } from "react";
-import { api } from "../api/client";
-import type { Need, NeedCreate } from "../types";
-
-export function NeedList() {
- const [needs, setNeeds] = useState([]);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState(null);
- const [showForm, setShowForm] = useState(false);
- const [form, setForm] = useState({ name: "", description: null });
- const [submitting, setSubmitting] = useState(false);
-
- useEffect(() => {
- api.needs
- .list()
- .then(setNeeds)
- .catch((err) => setError(err.message))
- .finally(() => setLoading(false));
- }, []);
-
- const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
- if (!form.name.trim()) return;
-
- setSubmitting(true);
- try {
- const created = await api.needs.create(form);
- setNeeds((prev) => [...prev, created]);
- setForm({ name: "", description: null });
- setShowForm(false);
- } catch (err) {
- setError(err instanceof Error ? err.message : "Create failed");
- } finally {
- setSubmitting(false);
- }
- };
-
- const handleDelete = async (id: number) => {
- if (!confirm("Delete this need?")) return;
- try {
- await api.needs.delete(id);
- setNeeds((prev) => prev.filter((n) => n.id !== id));
- } catch (err) {
- setError(err instanceof Error ? err.message : "Delete failed");
- }
- };
-
- if (loading) return Loading...
;
-
- return (
-
-
-
Needs / Accommodations
-
-
-
- {error &&
{error}
}
-
- {showForm && (
-
- )}
-
- {needs.length === 0 ? (
-
No needs defined yet.
- ) : (
-
- {needs.map((need) => (
- -
-
-
{need.name}
- {need.description &&
{need.description}
}
-
-
-
- ))}
-
- )}
-
- );
-}
diff --git a/frontend/src/components/RelationshipGraph.tsx b/frontend/src/components/RelationshipGraph.tsx
deleted file mode 100644
index 80c7736..0000000
--- a/frontend/src/components/RelationshipGraph.tsx
+++ /dev/null
@@ -1,330 +0,0 @@
-import { useEffect, useRef, useState } from "react";
-import { api } from "../api/client";
-import type { GraphData, GraphEdge, GraphNode } from "../types";
-import { RELATIONSHIP_TYPES } from "../types";
-
-interface SimNode extends GraphNode {
- x: number;
- y: number;
- vx: number;
- vy: number;
-}
-
-interface SimEdge extends GraphEdge {
- sourceNode: SimNode;
- targetNode: SimNode;
-}
-
-export function RelationshipGraph() {
- const canvasRef = useRef(null);
- const [data, setData] = useState(null);
- const [error, setError] = useState(null);
- const [loading, setLoading] = useState(true);
- const [selectedNode, setSelectedNode] = useState(null);
- const nodesRef = useRef([]);
- const edgesRef = useRef([]);
- const dragNodeRef = useRef(null);
- const animationRef = useRef(0);
-
- useEffect(() => {
- api.graph.get()
- .then(setData)
- .catch((err) => setError(err.message))
- .finally(() => setLoading(false));
- }, []);
-
- useEffect(() => {
- if (!data || !canvasRef.current) return;
-
- const canvas = canvasRef.current;
- const maybeCtx = canvas.getContext("2d");
- if (!maybeCtx) return;
- const ctx: CanvasRenderingContext2D = maybeCtx;
-
- const width = canvas.width;
- const height = canvas.height;
- const centerX = width / 2;
- const centerY = height / 2;
-
- // Initialize nodes with random positions
- const nodes: SimNode[] = data.nodes.map((node) => ({
- ...node,
- x: centerX + (Math.random() - 0.5) * 300,
- y: centerY + (Math.random() - 0.5) * 300,
- vx: 0,
- vy: 0,
- }));
- nodesRef.current = nodes;
-
- const nodeMap = new Map(nodes.map((n) => [n.id, n]));
-
- // Create edges with node references
- const edges: SimEdge[] = data.edges
- .map((edge) => {
- const sourceNode = nodeMap.get(edge.source);
- const targetNode = nodeMap.get(edge.target);
- if (!sourceNode || !targetNode) return null;
- return { ...edge, sourceNode, targetNode };
- })
- .filter((e): e is SimEdge => e !== null);
- edgesRef.current = edges;
-
- // Force simulation parameters
- const repulsion = 5000;
- const springStrength = 0.05;
- const baseSpringLength = 150;
- const damping = 0.9;
- const centerPull = 0.01;
-
- function simulate() {
- const nodes = nodesRef.current;
- const edges = edgesRef.current;
-
- // Reset forces
- for (const node of nodes) {
- node.vx = 0;
- node.vy = 0;
- }
-
- // Repulsion between all nodes
- for (let i = 0; i < nodes.length; i++) {
- for (let j = i + 1; j < nodes.length; j++) {
- const dx = nodes[j].x - nodes[i].x;
- const dy = nodes[j].y - nodes[i].y;
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
- const force = repulsion / (dist * dist);
- const fx = (dx / dist) * force;
- const fy = (dy / dist) * force;
- nodes[i].vx -= fx;
- nodes[i].vy -= fy;
- nodes[j].vx += fx;
- nodes[j].vy += fy;
- }
- }
-
- // Spring forces for edges - closer relationships = shorter springs
- // Weight is 1-10, normalize to 0-1 for calculations
- for (const edge of edges) {
- const dx = edge.targetNode.x - edge.sourceNode.x;
- const dy = edge.targetNode.y - edge.sourceNode.y;
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
- // Higher weight (1-10) = shorter ideal length
- // Normalize: weight 10 -> 0.5x length, weight 1 -> 1.4x length
- const normalizedWeight = edge.closeness_weight / 10;
- const idealLength = baseSpringLength * (1.5 - normalizedWeight);
- const displacement = dist - idealLength;
- const force = springStrength * displacement;
- const fx = (dx / dist) * force;
- const fy = (dy / dist) * force;
- edge.sourceNode.vx += fx;
- edge.sourceNode.vy += fy;
- edge.targetNode.vx -= fx;
- edge.targetNode.vy -= fy;
- }
-
- // Pull toward center
- for (const node of nodes) {
- node.vx += (centerX - node.x) * centerPull;
- node.vy += (centerY - node.y) * centerPull;
- }
-
- // Apply velocities with damping (skip dragged node)
- for (const node of nodes) {
- if (node === dragNodeRef.current) continue;
- node.x += node.vx * damping;
- node.y += node.vy * damping;
- // Keep within bounds
- node.x = Math.max(30, Math.min(width - 30, node.x));
- node.y = Math.max(30, Math.min(height - 30, node.y));
- }
- }
-
- function getEdgeColor(weight: number): string {
- // Interpolate from light gray (distant) to dark blue (close)
- // weight is 1-10, normalize to 0-1
- const normalized = weight / 10;
- const hue = 220;
- const saturation = 70;
- const lightness = 80 - normalized * 40;
- return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
- }
-
- function draw(context: CanvasRenderingContext2D) {
- const nodes = nodesRef.current;
- const edges = edgesRef.current;
-
- context.clearRect(0, 0, width, height);
-
- // Draw edges
- for (const edge of edges) {
- // Weight is 1-10, scale line width accordingly
- const lineWidth = 1 + (edge.closeness_weight / 10) * 3;
- context.strokeStyle = getEdgeColor(edge.closeness_weight);
- context.lineWidth = lineWidth;
- context.beginPath();
- context.moveTo(edge.sourceNode.x, edge.sourceNode.y);
- context.lineTo(edge.targetNode.x, edge.targetNode.y);
- context.stroke();
-
- // Draw relationship type label at midpoint
- const midX = (edge.sourceNode.x + edge.targetNode.x) / 2;
- const midY = (edge.sourceNode.y + edge.targetNode.y) / 2;
- context.fillStyle = "#666";
- context.font = "10px sans-serif";
- context.textAlign = "center";
- const typeInfo = RELATIONSHIP_TYPES.find(t => t.value === edge.relationship_type);
- const label = typeInfo?.displayName || edge.relationship_type;
- context.fillText(label, midX, midY - 5);
- }
-
- // Draw nodes
- for (const node of nodes) {
- const isSelected = node === selectedNode;
- const radius = isSelected ? 25 : 20;
-
- // Node circle
- context.beginPath();
- context.arc(node.x, node.y, radius, 0, Math.PI * 2);
- context.fillStyle = isSelected ? "#0066cc" : "#fff";
- context.fill();
- context.strokeStyle = "#0066cc";
- context.lineWidth = 2;
- context.stroke();
-
- // Node label
- context.fillStyle = isSelected ? "#fff" : "#333";
- context.font = "12px sans-serif";
- context.textAlign = "center";
- context.textBaseline = "middle";
- const name = node.name.length > 10 ? node.name.slice(0, 9) + "…" : node.name;
- context.fillText(name, node.x, node.y);
- }
- }
-
- function animate() {
- simulate();
- draw(ctx);
- animationRef.current = requestAnimationFrame(animate);
- }
-
- animate();
-
- return () => {
- cancelAnimationFrame(animationRef.current);
- };
- }, [data, selectedNode]);
-
- // Mouse interaction handlers
- useEffect(() => {
- const canvas = canvasRef.current;
- if (!canvas) return;
-
- function getNodeAtPosition(x: number, y: number): SimNode | null {
- for (const node of nodesRef.current) {
- const dx = x - node.x;
- const dy = y - node.y;
- if (dx * dx + dy * dy < 400) {
- return node;
- }
- }
- return null;
- }
-
- function handleMouseDown(e: MouseEvent) {
- const rect = canvas!.getBoundingClientRect();
- const x = e.clientX - rect.left;
- const y = e.clientY - rect.top;
- const node = getNodeAtPosition(x, y);
- if (node) {
- dragNodeRef.current = node;
- setSelectedNode(node);
- }
- }
-
- function handleMouseMove(e: MouseEvent) {
- if (!dragNodeRef.current) return;
- const rect = canvas!.getBoundingClientRect();
- dragNodeRef.current.x = e.clientX - rect.left;
- dragNodeRef.current.y = e.clientY - rect.top;
- }
-
- function handleMouseUp() {
- dragNodeRef.current = null;
- }
-
- canvas.addEventListener("mousedown", handleMouseDown);
- canvas.addEventListener("mousemove", handleMouseMove);
- canvas.addEventListener("mouseup", handleMouseUp);
- canvas.addEventListener("mouseleave", handleMouseUp);
-
- return () => {
- canvas.removeEventListener("mousedown", handleMouseDown);
- canvas.removeEventListener("mousemove", handleMouseMove);
- canvas.removeEventListener("mouseup", handleMouseUp);
- canvas.removeEventListener("mouseleave", handleMouseUp);
- };
- }, []);
-
- if (loading) return Loading graph...
;
- if (error) return {error}
;
- if (!data) return No data available
;
-
- return (
-
-
-
Relationship Graph
-
-
- Drag nodes to reposition. Closer relationships have shorter, darker edges.
-
-
- {selectedNode && (
-
-
{selectedNode.name}
- {selectedNode.current_job &&
Job: {selectedNode.current_job}
}
-
View details
-
- )}
-
-
Relationship Closeness (1-10)
-
-
-
- 10 - Very Close (Spouse, Partner)
-
-
-
- 7 - Close (Family, Best Friend)
-
-
-
- 4 - Moderate (Friend, Colleague)
-
-
-
- 2 - Distant (Acquaintance)
-
-
-
-
- );
-}
-
-function getEdgeColorCSS(weight: number): string {
- // weight is 1-10, normalize to 0-1
- const normalized = weight / 10;
- const hue = 220;
- const saturation = 70;
- const lightness = 80 - normalized * 40;
- return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
-}
diff --git a/frontend/src/index.css b/frontend/src/index.css
deleted file mode 100644
index a9ba010..0000000
--- a/frontend/src/index.css
+++ /dev/null
@@ -1,62 +0,0 @@
-:root {
- /* Light theme (default) */
- --color-bg: #f5f5f5;
- --color-bg-card: #ffffff;
- --color-bg-hover: #f0f0f0;
- --color-bg-muted: #f9f9f9;
- --color-bg-error: #ffe0e0;
-
- --color-text: #333333;
- --color-text-muted: #666666;
- --color-text-error: #cc0000;
-
- --color-border: #dddddd;
- --color-border-light: #eeeeee;
- --color-border-lighter: #f0f0f0;
-
- --color-primary: #0066cc;
- --color-primary-hover: #0055aa;
-
- --color-danger: #cc3333;
- --color-danger-hover: #aa2222;
-
- --color-tag-bg: #e0e0e0;
-
- --shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
-
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
- line-height: 1.5;
- font-weight: 400;
- color: var(--color-text);
- background-color: var(--color-bg);
- font-synthesis: none;
- text-rendering: optimizeLegibility;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-[data-theme="dark"] {
- --color-bg: #1a1a1a;
- --color-bg-card: #2d2d2d;
- --color-bg-hover: #3d3d3d;
- --color-bg-muted: #252525;
- --color-bg-error: #4a2020;
-
- --color-text: #e0e0e0;
- --color-text-muted: #a0a0a0;
- --color-text-error: #ff6b6b;
-
- --color-border: #404040;
- --color-border-light: #353535;
- --color-border-lighter: #303030;
-
- --color-primary: #4da6ff;
- --color-primary-hover: #7dbfff;
-
- --color-danger: #ff6b6b;
- --color-danger-hover: #ff8a8a;
-
- --color-tag-bg: #404040;
-
- --shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
-}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
deleted file mode 100644
index 9230150..0000000
--- a/frontend/src/main.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { StrictMode } from "react";
-import { createRoot } from "react-dom/client";
-import { BrowserRouter } from "react-router-dom";
-import App from "./App.tsx";
-import "./index.css";
-
-createRoot(document.getElementById("root")!).render(
-
-
-
-
-
-);
diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts
deleted file mode 100644
index d925f78..0000000
--- a/frontend/src/types/index.ts
+++ /dev/null
@@ -1,155 +0,0 @@
-export interface Need {
- id: number;
- name: string;
- description: string | null;
-}
-
-export interface NeedCreate {
- name: string;
- description?: string | null;
-}
-
-export const RELATIONSHIP_TYPES = [
- { value: 'spouse', displayName: 'Spouse', defaultWeight: 10 },
- { value: 'partner', displayName: 'Partner', defaultWeight: 10 },
- { value: 'parent', displayName: 'Parent', defaultWeight: 9 },
- { value: 'child', displayName: 'Child', defaultWeight: 9 },
- { value: 'sibling', displayName: 'Sibling', defaultWeight: 9 },
- { value: 'best_friend', displayName: 'Best Friend', defaultWeight: 8 },
- { value: 'grandparent', displayName: 'Grandparent', defaultWeight: 7 },
- { value: 'grandchild', displayName: 'Grandchild', defaultWeight: 7 },
- { value: 'aunt_uncle', displayName: 'Aunt/Uncle', defaultWeight: 7 },
- { value: 'niece_nephew', displayName: 'Niece/Nephew', defaultWeight: 7 },
- { value: 'cousin', displayName: 'Cousin', defaultWeight: 7 },
- { value: 'in_law', displayName: 'In-Law', defaultWeight: 7 },
- { value: 'close_friend', displayName: 'Close Friend', defaultWeight: 6 },
- { value: 'friend', displayName: 'Friend', defaultWeight: 6 },
- { value: 'mentor', displayName: 'Mentor', defaultWeight: 5 },
- { value: 'mentee', displayName: 'Mentee', defaultWeight: 5 },
- { value: 'business_partner', displayName: 'Business Partner', defaultWeight: 5 },
- { value: 'colleague', displayName: 'Colleague', defaultWeight: 4 },
- { value: 'manager', displayName: 'Manager', defaultWeight: 4 },
- { value: 'direct_report', displayName: 'Direct Report', defaultWeight: 4 },
- { value: 'client', displayName: 'Client', defaultWeight: 4 },
- { value: 'acquaintance', displayName: 'Acquaintance', defaultWeight: 3 },
- { value: 'neighbor', displayName: 'Neighbor', defaultWeight: 3 },
- { value: 'ex', displayName: 'Ex', defaultWeight: 2 },
- { value: 'other', displayName: 'Other', defaultWeight: 2 },
-] as const;
-
-export type RelationshipTypeValue = typeof RELATIONSHIP_TYPES[number]['value'];
-
-export interface ContactRelationship {
- contact_id: number;
- related_contact_id: number;
- relationship_type: string;
- closeness_weight: number;
-}
-
-export interface ContactRelationshipCreate {
- related_contact_id: number;
- relationship_type: RelationshipTypeValue;
- closeness_weight?: number;
-}
-
-export interface ContactRelationshipUpdate {
- relationship_type?: RelationshipTypeValue;
- closeness_weight?: number;
-}
-
-export interface GraphNode {
- id: number;
- name: string;
- current_job: string | null;
-}
-
-export interface GraphEdge {
- source: number;
- target: number;
- relationship_type: string;
- closeness_weight: number;
-}
-
-export interface GraphData {
- nodes: GraphNode[];
- edges: GraphEdge[];
-}
-
-export interface Contact {
- id: number;
- name: string;
- age: number | null;
- bio: string | null;
- current_job: string | null;
- gender: string | null;
- goals: string | null;
- legal_name: string | null;
- profile_pic: string | null;
- safe_conversation_starters: string | null;
- self_sufficiency_score: number | null;
- social_structure_style: string | null;
- ssn: string | null;
- suffix: string | null;
- timezone: string | null;
- topics_to_avoid: string | null;
- needs: Need[];
- related_to: ContactRelationship[];
- related_from: ContactRelationship[];
-}
-
-export interface ContactListItem {
- id: number;
- name: string;
- age: number | null;
- bio: string | null;
- current_job: string | null;
- gender: string | null;
- goals: string | null;
- legal_name: string | null;
- profile_pic: string | null;
- safe_conversation_starters: string | null;
- self_sufficiency_score: number | null;
- social_structure_style: string | null;
- ssn: string | null;
- suffix: string | null;
- timezone: string | null;
- topics_to_avoid: string | null;
-}
-
-export interface ContactCreate {
- name: string;
- age?: number | null;
- bio?: string | null;
- current_job?: string | null;
- gender?: string | null;
- goals?: string | null;
- legal_name?: string | null;
- profile_pic?: string | null;
- safe_conversation_starters?: string | null;
- self_sufficiency_score?: number | null;
- social_structure_style?: string | null;
- ssn?: string | null;
- suffix?: string | null;
- timezone?: string | null;
- topics_to_avoid?: string | null;
- need_ids?: number[];
-}
-
-export interface ContactUpdate {
- name?: string | null;
- age?: number | null;
- bio?: string | null;
- current_job?: string | null;
- gender?: string | null;
- goals?: string | null;
- legal_name?: string | null;
- profile_pic?: string | null;
- safe_conversation_starters?: string | null;
- self_sufficiency_score?: number | null;
- social_structure_style?: string | null;
- ssn?: string | null;
- suffix?: string | null;
- timezone?: string | null;
- topics_to_avoid?: string | null;
- need_ids?: number[] | null;
-}
diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json
deleted file mode 100644
index a9b5a59..0000000
--- a/frontend/tsconfig.app.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "compilerOptions": {
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
- "target": "ES2022",
- "useDefineForClassFields": true,
- "lib": ["ES2022", "DOM", "DOM.Iterable"],
- "module": "ESNext",
- "types": ["vite/client"],
- "skipLibCheck": true,
-
- /* Bundler mode */
- "moduleResolution": "bundler",
- "allowImportingTsExtensions": true,
- "verbatimModuleSyntax": true,
- "moduleDetection": "force",
- "noEmit": true,
- "jsx": "react-jsx",
-
- /* Linting */
- "strict": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "erasableSyntaxOnly": true,
- "noFallthroughCasesInSwitch": true,
- "noUncheckedSideEffectImports": true
- },
- "include": ["src"]
-}
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
deleted file mode 100644
index 1ffef60..0000000
--- a/frontend/tsconfig.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "files": [],
- "references": [
- { "path": "./tsconfig.app.json" },
- { "path": "./tsconfig.node.json" }
- ]
-}
diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json
deleted file mode 100644
index 8a67f62..0000000
--- a/frontend/tsconfig.node.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "compilerOptions": {
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
- "target": "ES2023",
- "lib": ["ES2023"],
- "module": "ESNext",
- "types": ["node"],
- "skipLibCheck": true,
-
- /* Bundler mode */
- "moduleResolution": "bundler",
- "allowImportingTsExtensions": true,
- "verbatimModuleSyntax": true,
- "moduleDetection": "force",
- "noEmit": true,
-
- /* Linting */
- "strict": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "erasableSyntaxOnly": true,
- "noFallthroughCasesInSwitch": true,
- "noUncheckedSideEffectImports": true
- },
- "include": ["vite.config.ts"]
-}
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
deleted file mode 100644
index 88026fc..0000000
--- a/frontend/vite.config.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { defineConfig } from "vite";
-import react from "@vitejs/plugin-react";
-
-export default defineConfig({
- plugins: [react()],
- server: {
- proxy: {
- "/api": "http://localhost:8000",
- },
- },
-});
diff --git a/python/api/main.py b/python/api/main.py
index a20391b..584ad78 100644
--- a/python/api/main.py
+++ b/python/api/main.py
@@ -1,27 +1,22 @@
"""FastAPI interface for Contact database."""
import logging
-import shutil
-import subprocess
-import tempfile
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
-from os import environ
-from pathlib import Path
from typing import Annotated
import typer
import uvicorn
from fastapi import FastAPI
-from python.api.routers import contact_router, create_frontend_router
+from python.api.routers import contact_router, views_router
from python.common import configure_logger
from python.orm.common import get_postgres_engine
logger = logging.getLogger(__name__)
-def create_app(frontend_dir: Path | None = None) -> FastAPI:
+def create_app() -> FastAPI:
"""Create and configure the FastAPI application."""
@asynccontextmanager
@@ -34,82 +29,20 @@ def create_app(frontend_dir: Path | None = None) -> FastAPI:
app = FastAPI(title="Contact Database API", lifespan=lifespan)
app.include_router(contact_router)
-
- if frontend_dir:
- logger.info(f"Serving frontend from {frontend_dir}")
- frontend_router = create_frontend_router(frontend_dir)
- app.include_router(frontend_router)
+ app.include_router(views_router)
return app
-def build_frontend(source_dir: Path | None, cache_dir: Path | None = None) -> Path | None:
- """Run npm build and copy output to a temp directory.
-
- Works even if source_dir is read-only by copying to a temp directory first.
-
- Args:
- source_dir: Frontend source directory.
- cache_dir: Optional npm cache directory for faster repeated builds.
-
- Returns:
- Path to frontend build directory, or None if no source_dir provided.
- """
- if not source_dir:
- return None
-
- if not source_dir.exists():
- error = f"Frontend directory {source_dir} does not exist"
- raise FileExistsError(error)
-
- logger.info("Building frontend from %s...", source_dir)
-
- # Copy source to a writable temp directory
- build_dir = Path(tempfile.mkdtemp(prefix="contact_frontend_build_"))
- shutil.copytree(source_dir, build_dir, dirs_exist_ok=True)
-
- env = dict(environ)
- if cache_dir:
- cache_dir.mkdir(parents=True, exist_ok=True)
- env["npm_config_cache"] = str(cache_dir)
-
- subprocess.run(["npm", "install"], cwd=build_dir, env=env, check=True) # noqa: S607
- subprocess.run(["npm", "run", "build"], cwd=build_dir, env=env, check=True) # noqa: S607
-
- dist_dir = build_dir / "dist"
- if not dist_dir.exists():
- error = f"Build output not found at {dist_dir}"
- raise FileNotFoundError(error)
-
- output_dir = Path(tempfile.mkdtemp(prefix="contact_frontend_"))
- shutil.copytree(dist_dir, output_dir, dirs_exist_ok=True)
- logger.info(f"Frontend built and copied to {output_dir}")
-
- shutil.rmtree(build_dir)
-
- return output_dir
-
-
def serve(
host: Annotated[str, typer.Option("--host", "-h", help="Host to bind to")],
- frontend_dir: Annotated[
- Path | None,
- typer.Option(
- "--frontend-dir",
- "-f",
- help="Frontend source directory. If provided, runs npm build and serves from temp dir.",
- ),
- ] = None,
port: Annotated[int, typer.Option("--port", "-p", help="Port to bind to")] = 8000,
log_level: Annotated[str, typer.Option("--log-level", "-l", help="Log level")] = "INFO",
) -> None:
"""Start the Contact API server."""
configure_logger(log_level)
- cache_dir = Path(environ["HOME"]) / ".npm"
- serve_dir = build_frontend(frontend_dir, cache_dir=cache_dir)
-
- app = create_app(frontend_dir=serve_dir)
+ app = create_app()
uvicorn.run(app, host=host, port=port)
diff --git a/python/api/routers/__init__.py b/python/api/routers/__init__.py
index 2e59a7e..87d808b 100644
--- a/python/api/routers/__init__.py
+++ b/python/api/routers/__init__.py
@@ -1,6 +1,6 @@
"""API routers."""
from python.api.routers.contact import router as contact_router
-from python.api.routers.frontend import create_frontend_router
+from python.api.routers.views import router as views_router
-__all__ = ["contact_router", "create_frontend_router"]
+__all__ = ["contact_router", "views_router"]
diff --git a/python/api/routers/contact.py b/python/api/routers/contact.py
index 00528c7..9aa398d 100644
--- a/python/api/routers/contact.py
+++ b/python/api/routers/contact.py
@@ -1,6 +1,10 @@
"""Contact API router."""
-from fastapi import APIRouter, HTTPException
+from pathlib import Path
+
+from fastapi import APIRouter, HTTPException, Request
+from fastapi.responses import HTMLResponse
+from fastapi.templating import Jinja2Templates
from pydantic import BaseModel
from sqlalchemy import select
from sqlalchemy.orm import selectinload
@@ -8,6 +12,14 @@ from sqlalchemy.orm import selectinload
from python.api.dependencies import DbSession
from python.orm.richie.contact import Contact, ContactRelationship, Need, RelationshipType
+TEMPLATES_DIR = Path(__file__).parent.parent / "templates"
+templates = Jinja2Templates(directory=TEMPLATES_DIR)
+
+
+def _is_htmx(request: Request) -> bool:
+ """Check if the request is from HTMX."""
+ return request.headers.get("HX-Request") == "true"
+
class NeedBase(BaseModel):
"""Base schema for Need."""
@@ -180,14 +192,16 @@ def get_need(need_id: int, db: DbSession) -> Need:
return need
-@router.delete("/needs/{need_id}")
-def delete_need(need_id: int, db: DbSession) -> dict[str, bool]:
+@router.delete("/needs/{need_id}", response_model=None)
+def delete_need(need_id: int, request: Request, db: DbSession) -> dict[str, bool] | HTMLResponse:
"""Delete a need by ID."""
need = db.get(Need, need_id)
if not need:
raise HTTPException(status_code=404, detail="Need not found")
db.delete(need)
db.commit()
+ if _is_htmx(request):
+ return HTMLResponse("")
return {"deleted": True}
@@ -261,14 +275,16 @@ def update_contact(
return db_contact
-@router.delete("/contacts/{contact_id}")
-def delete_contact(contact_id: int, db: DbSession) -> dict[str, bool]:
+@router.delete("/contacts/{contact_id}", response_model=None)
+def delete_contact(contact_id: int, request: Request, db: DbSession) -> dict[str, bool] | HTMLResponse:
"""Delete a contact by ID."""
contact = db.get(Contact, contact_id)
if not contact:
raise HTTPException(status_code=404, detail="Contact not found")
db.delete(contact)
db.commit()
+ if _is_htmx(request):
+ return HTMLResponse("")
return {"deleted": True}
@@ -294,12 +310,13 @@ def add_need_to_contact(
return {"added": True}
-@router.delete("/contacts/{contact_id}/needs/{need_id}")
+@router.delete("/contacts/{contact_id}/needs/{need_id}", response_model=None)
def remove_need_from_contact(
contact_id: int,
need_id: int,
+ request: Request,
db: DbSession,
-) -> dict[str, bool]:
+) -> dict[str, bool] | HTMLResponse:
"""Remove a need from a contact."""
contact = db.get(Contact, contact_id)
if not contact:
@@ -313,6 +330,8 @@ def remove_need_from_contact(
contact.needs.remove(need)
db.commit()
+ if _is_htmx(request):
+ return HTMLResponse("")
return {"removed": True}
@@ -404,12 +423,13 @@ def update_contact_relationship(
return relationship
-@router.delete("/contacts/{contact_id}/relationships/{related_contact_id}")
+@router.delete("/contacts/{contact_id}/relationships/{related_contact_id}", response_model=None)
def remove_contact_relationship(
contact_id: int,
related_contact_id: int,
+ request: Request,
db: DbSession,
-) -> dict[str, bool]:
+) -> dict[str, bool] | HTMLResponse:
"""Remove a relationship between two contacts."""
relationship = db.scalar(
select(ContactRelationship).where(
@@ -422,6 +442,8 @@ def remove_contact_relationship(
db.delete(relationship)
db.commit()
+ if _is_htmx(request):
+ return HTMLResponse("")
return {"deleted": True}
diff --git a/python/api/routers/frontend.py b/python/api/routers/frontend.py
deleted file mode 100644
index 150bca0..0000000
--- a/python/api/routers/frontend.py
+++ /dev/null
@@ -1,24 +0,0 @@
-"""Frontend SPA router."""
-
-from pathlib import Path
-
-from fastapi import APIRouter
-from fastapi.responses import FileResponse
-from fastapi.staticfiles import StaticFiles
-
-
-def create_frontend_router(frontend_dir: Path) -> APIRouter:
- """Create a router for serving the frontend SPA."""
- router = APIRouter(tags=["frontend"])
-
- router.mount("/assets", StaticFiles(directory=frontend_dir / "assets"), name="assets")
-
- @router.get("/{full_path:path}")
- async def serve_spa(full_path: str) -> FileResponse:
- """Serve React SPA for all non-API routes."""
- file_path = frontend_dir / full_path
- if file_path.is_file():
- return FileResponse(file_path)
- return FileResponse(frontend_dir / "index.html")
-
- return router
diff --git a/python/api/routers/views.py b/python/api/routers/views.py
new file mode 100644
index 0000000..2c058b3
--- /dev/null
+++ b/python/api/routers/views.py
@@ -0,0 +1,397 @@
+"""HTMX server-rendered view router."""
+
+from pathlib import Path
+
+from fastapi import APIRouter, Form, HTTPException, Request
+from fastapi.responses import HTMLResponse, RedirectResponse
+from fastapi.templating import Jinja2Templates
+from sqlalchemy import select
+from sqlalchemy.orm import selectinload
+
+from python.api.dependencies import DbSession
+from python.orm.richie.contact import Contact, ContactRelationship, Need, RelationshipType
+
+TEMPLATES_DIR = Path(__file__).parent.parent / "templates"
+templates = Jinja2Templates(directory=TEMPLATES_DIR)
+
+router = APIRouter(tags=["views"])
+
+FAMILIAL_TYPES = {"parent", "child", "sibling", "grandparent", "grandchild", "aunt_uncle", "niece_nephew", "cousin", "in_law"}
+FRIEND_TYPES = {"best_friend", "close_friend", "friend", "acquaintance", "neighbor"}
+PARTNER_TYPES = {"spouse", "partner"}
+PROFESSIONAL_TYPES = {"mentor", "mentee", "business_partner", "colleague", "manager", "direct_report", "client"}
+
+
+def _group_relationships(relationships: list[ContactRelationship]) -> dict[str, list[ContactRelationship]]:
+ """Group relationships by category."""
+ groups: dict[str, list[ContactRelationship]] = {
+ "familial": [],
+ "partners": [],
+ "friends": [],
+ "professional": [],
+ "other": [],
+ }
+ for rel in relationships:
+ if rel.relationship_type in FAMILIAL_TYPES:
+ groups["familial"].append(rel)
+ elif rel.relationship_type in PARTNER_TYPES:
+ groups["partners"].append(rel)
+ elif rel.relationship_type in FRIEND_TYPES:
+ groups["friends"].append(rel)
+ elif rel.relationship_type in PROFESSIONAL_TYPES:
+ groups["professional"].append(rel)
+ else:
+ groups["other"].append(rel)
+ return groups
+
+
+def _build_contact_name_map(database: DbSession, contact: Contact) -> dict[int, str]:
+ """Build a mapping of contact IDs to names for relationship display."""
+ related_ids = {rel.related_contact_id for rel in contact.related_to}
+ related_ids |= {rel.contact_id for rel in contact.related_from}
+ related_ids.discard(contact.id)
+
+ if not related_ids:
+ return {}
+
+ related_contacts = list(
+ database.scalars(select(Contact).where(Contact.id.in_(related_ids))).all()
+ )
+ return {related.id: related.name for related in related_contacts}
+
+
+def _get_relationship_type_display() -> dict[str, str]:
+ """Build a mapping of relationship type values to display names."""
+ return {rel_type.value: rel_type.display_name for rel_type in RelationshipType}
+
+
+def _parse_contact_form(
+ name: str,
+ legal_name: str,
+ suffix: str,
+ age: str,
+ gender: str,
+ current_job: str,
+ timezone: str,
+ profile_pic: str,
+ bio: str,
+ goals: str,
+ social_structure_style: str,
+ self_sufficiency_score: str,
+ safe_conversation_starters: str,
+ topics_to_avoid: str,
+ ssn: str,
+) -> dict:
+ """Parse form fields into a dict for contact creation/update."""
+ return {
+ "name": name,
+ "legal_name": legal_name or None,
+ "suffix": suffix or None,
+ "age": int(age) if age else None,
+ "gender": gender or None,
+ "current_job": current_job or None,
+ "timezone": timezone or None,
+ "profile_pic": profile_pic or None,
+ "bio": bio or None,
+ "goals": goals or None,
+ "social_structure_style": social_structure_style or None,
+ "self_sufficiency_score": int(self_sufficiency_score) if self_sufficiency_score else None,
+ "safe_conversation_starters": safe_conversation_starters or None,
+ "topics_to_avoid": topics_to_avoid or None,
+ "ssn": ssn or None,
+ }
+
+
+@router.get("/", response_class=HTMLResponse)
+@router.get("/contacts", response_class=HTMLResponse)
+def contact_list_page(request: Request, database: DbSession) -> HTMLResponse:
+ """Render the contacts list page."""
+ contacts = list(database.scalars(select(Contact)).all())
+ return templates.TemplateResponse(
+ request, "contact_list.html", {"contacts": contacts}
+ )
+
+
+@router.get("/contacts/new", response_class=HTMLResponse)
+def new_contact_page(request: Request, database: DbSession) -> HTMLResponse:
+ """Render the new contact form page."""
+ all_needs = list(database.scalars(select(Need)).all())
+ return templates.TemplateResponse(
+ request, "contact_form.html", {"contact": None, "all_needs": all_needs}
+ )
+
+
+@router.post("/htmx/contacts/new")
+def create_contact_form(
+ database: DbSession,
+ name: str = Form(...),
+ legal_name: str = Form(""),
+ suffix: str = Form(""),
+ age: str = Form(""),
+ gender: str = Form(""),
+ current_job: str = Form(""),
+ timezone: str = Form(""),
+ profile_pic: str = Form(""),
+ bio: str = Form(""),
+ goals: str = Form(""),
+ social_structure_style: str = Form(""),
+ self_sufficiency_score: str = Form(""),
+ safe_conversation_starters: str = Form(""),
+ topics_to_avoid: str = Form(""),
+ ssn: str = Form(""),
+ need_ids: list[int] = Form([]),
+) -> RedirectResponse:
+ """Handle the create contact form submission."""
+ contact_data = _parse_contact_form(
+ name, legal_name, suffix, age, gender, current_job, timezone,
+ profile_pic, bio, goals, social_structure_style, self_sufficiency_score,
+ safe_conversation_starters, topics_to_avoid, ssn,
+ )
+ contact = Contact(**contact_data)
+
+ if need_ids:
+ needs = list(database.scalars(select(Need).where(Need.id.in_(need_ids))).all())
+ contact.needs = needs
+
+ database.add(contact)
+ database.commit()
+ database.refresh(contact)
+ return RedirectResponse(url=f"/contacts/{contact.id}", status_code=303)
+
+
+@router.get("/contacts/{contact_id}", response_class=HTMLResponse)
+def contact_detail_page(contact_id: int, request: Request, database: DbSession) -> HTMLResponse:
+ """Render the contact detail page."""
+ contact = database.scalar(
+ select(Contact)
+ .where(Contact.id == contact_id)
+ .options(
+ selectinload(Contact.needs),
+ selectinload(Contact.related_to),
+ selectinload(Contact.related_from),
+ )
+ )
+ if not contact:
+ raise HTTPException(status_code=404, detail="Contact not found")
+
+ contact_names = _build_contact_name_map(database, contact)
+ grouped_relationships = _group_relationships(contact.related_to)
+ all_contacts = list(database.scalars(select(Contact)).all())
+ all_needs = list(database.scalars(select(Need)).all())
+ available_needs = [need for need in all_needs if need not in contact.needs]
+
+ return templates.TemplateResponse(
+ request,
+ "contact_detail.html",
+ {
+ "contact": contact,
+ "contact_names": contact_names,
+ "grouped_relationships": grouped_relationships,
+ "all_contacts": all_contacts,
+ "available_needs": available_needs,
+ "relationship_types": list(RelationshipType),
+ },
+ )
+
+
+@router.get("/contacts/{contact_id}/edit", response_class=HTMLResponse)
+def edit_contact_page(contact_id: int, request: Request, database: DbSession) -> HTMLResponse:
+ """Render the edit contact form page."""
+ contact = database.scalar(
+ select(Contact)
+ .where(Contact.id == contact_id)
+ .options(selectinload(Contact.needs))
+ )
+ if not contact:
+ raise HTTPException(status_code=404, detail="Contact not found")
+
+ all_needs = list(database.scalars(select(Need)).all())
+ return templates.TemplateResponse(
+ request, "contact_form.html", {"contact": contact, "all_needs": all_needs}
+ )
+
+
+@router.post("/htmx/contacts/{contact_id}/edit")
+def update_contact_form(
+ contact_id: int,
+ database: DbSession,
+ name: str = Form(...),
+ legal_name: str = Form(""),
+ suffix: str = Form(""),
+ age: str = Form(""),
+ gender: str = Form(""),
+ current_job: str = Form(""),
+ timezone: str = Form(""),
+ profile_pic: str = Form(""),
+ bio: str = Form(""),
+ goals: str = Form(""),
+ social_structure_style: str = Form(""),
+ self_sufficiency_score: str = Form(""),
+ safe_conversation_starters: str = Form(""),
+ topics_to_avoid: str = Form(""),
+ ssn: str = Form(""),
+ need_ids: list[int] = Form([]),
+) -> RedirectResponse:
+ """Handle the edit contact form submission."""
+ contact = database.get(Contact, contact_id)
+ if not contact:
+ raise HTTPException(status_code=404, detail="Contact not found")
+
+ contact_data = _parse_contact_form(
+ name, legal_name, suffix, age, gender, current_job, timezone,
+ profile_pic, bio, goals, social_structure_style, self_sufficiency_score,
+ safe_conversation_starters, topics_to_avoid, ssn,
+ )
+
+ for key, value in contact_data.items():
+ setattr(contact, key, value)
+
+ needs = list(database.scalars(select(Need).where(Need.id.in_(need_ids))).all()) if need_ids else []
+ contact.needs = needs
+
+ database.commit()
+ return RedirectResponse(url=f"/contacts/{contact_id}", status_code=303)
+
+
+@router.post("/htmx/contacts/{contact_id}/add-need", response_class=HTMLResponse)
+def add_need_to_contact_htmx(
+ contact_id: int,
+ request: Request,
+ database: DbSession,
+ need_id: int = Form(...),
+) -> HTMLResponse:
+ """Add a need to a contact and return updated manage-needs partial."""
+ contact = database.scalar(
+ select(Contact)
+ .where(Contact.id == contact_id)
+ .options(selectinload(Contact.needs))
+ )
+ if not contact:
+ raise HTTPException(status_code=404, detail="Contact not found")
+
+ need = database.get(Need, need_id)
+ if not need:
+ raise HTTPException(status_code=404, detail="Need not found")
+
+ if need not in contact.needs:
+ contact.needs.append(need)
+ database.commit()
+ database.refresh(contact)
+
+ return templates.TemplateResponse(
+ request, "partials/manage_needs.html", {"contact": contact}
+ )
+
+
+@router.post("/htmx/contacts/{contact_id}/add-relationship", response_class=HTMLResponse)
+def add_relationship_htmx(
+ contact_id: int,
+ request: Request,
+ database: DbSession,
+ related_contact_id: int = Form(...),
+ relationship_type: str = Form(...),
+) -> HTMLResponse:
+ """Add a relationship and return updated manage-relationships partial."""
+ contact = database.scalar(
+ select(Contact)
+ .where(Contact.id == contact_id)
+ .options(selectinload(Contact.related_to))
+ )
+ if not contact:
+ raise HTTPException(status_code=404, detail="Contact not found")
+
+ related_contact = database.get(Contact, related_contact_id)
+ if not related_contact:
+ raise HTTPException(status_code=404, detail="Related contact not found")
+
+ rel_type = RelationshipType(relationship_type)
+ weight = rel_type.default_weight
+
+ relationship = ContactRelationship(
+ contact_id=contact_id,
+ related_contact_id=related_contact_id,
+ relationship_type=relationship_type,
+ closeness_weight=weight,
+ )
+ database.add(relationship)
+ database.commit()
+ database.refresh(contact)
+
+ contact_names = _build_contact_name_map(database, contact)
+ return templates.TemplateResponse(
+ request, "partials/manage_relationships.html",
+ {"contact": contact, "contact_names": contact_names},
+ )
+
+
+@router.post("/htmx/contacts/{contact_id}/relationships/{related_contact_id}/weight")
+def update_relationship_weight_htmx(
+ contact_id: int,
+ related_contact_id: int,
+ database: DbSession,
+ closeness_weight: int = Form(...),
+) -> HTMLResponse:
+ """Update a relationship's closeness weight from HTMX range input."""
+ relationship = database.scalar(
+ select(ContactRelationship).where(
+ ContactRelationship.contact_id == contact_id,
+ ContactRelationship.related_contact_id == related_contact_id,
+ )
+ )
+ if not relationship:
+ raise HTTPException(status_code=404, detail="Relationship not found")
+
+ relationship.closeness_weight = closeness_weight
+ database.commit()
+ return HTMLResponse("")
+
+
+@router.post("/htmx/needs", response_class=HTMLResponse)
+def create_need_htmx(
+ request: Request,
+ database: DbSession,
+ name: str = Form(...),
+ description: str = Form(""),
+) -> HTMLResponse:
+ """Create a need via form data and return updated needs list."""
+ need = Need(name=name, description=description or None)
+ database.add(need)
+ database.commit()
+ needs = list(database.scalars(select(Need)).all())
+ return templates.TemplateResponse(request, "partials/need_items.html", {"needs": needs})
+
+
+@router.get("/needs", response_class=HTMLResponse)
+def needs_page(request: Request, database: DbSession) -> HTMLResponse:
+ """Render the needs list page."""
+ needs = list(database.scalars(select(Need)).all())
+ return templates.TemplateResponse(request, "need_list.html", {"needs": needs})
+
+
+@router.get("/graph", response_class=HTMLResponse)
+def graph_page(request: Request, database: DbSession) -> HTMLResponse:
+ """Render the relationship graph page."""
+ contacts = list(database.scalars(select(Contact)).all())
+ relationships = list(database.scalars(select(ContactRelationship)).all())
+
+ graph_data = {
+ "nodes": [{"id": contact.id, "name": contact.name, "current_job": contact.current_job} for contact in contacts],
+ "edges": [
+ {
+ "source": rel.contact_id,
+ "target": rel.related_contact_id,
+ "relationship_type": rel.relationship_type,
+ "closeness_weight": rel.closeness_weight,
+ }
+ for rel in relationships
+ ],
+ }
+
+ return templates.TemplateResponse(
+ request,
+ "graph.html",
+ {
+ "graph_data": graph_data,
+ "relationship_type_display": _get_relationship_type_display(),
+ },
+ )
diff --git a/python/api/templates/base.html b/python/api/templates/base.html
new file mode 100644
index 0000000..649e1e9
--- /dev/null
+++ b/python/api/templates/base.html
@@ -0,0 +1,198 @@
+
+
+
+
+
+ {% block title %}Contact Database{% endblock %}
+
+
+
+
+
+
+
+
+
diff --git a/python/api/templates/contact_detail.html b/python/api/templates/contact_detail.html
new file mode 100644
index 0000000..3911825
--- /dev/null
+++ b/python/api/templates/contact_detail.html
@@ -0,0 +1,204 @@
+{% extends "base.html" %}
+{% block title %}{{ contact.name }}{% endblock %}
+{% block content %}
+
+
+
+
+
+
+ {% if contact.legal_name %}
+
Legal name: {{ contact.legal_name }}
+ {% endif %}
+ {% if contact.suffix %}
+
Suffix: {{ contact.suffix }}
+ {% endif %}
+ {% if contact.gender %}
+
Gender: {{ contact.gender }}
+ {% endif %}
+ {% if contact.age %}
+
Age: {{ contact.age }}
+ {% endif %}
+ {% if contact.current_job %}
+
Job: {{ contact.current_job }}
+ {% endif %}
+ {% if contact.social_structure_style %}
+
Social style: {{ contact.social_structure_style }}
+ {% endif %}
+ {% if contact.self_sufficiency_score is not none %}
+
Self-Sufficiency: {{ contact.self_sufficiency_score }}
+ {% endif %}
+ {% if contact.timezone %}
+
Timezone: {{ contact.timezone }}
+ {% endif %}
+ {% if contact.safe_conversation_starters %}
+
+ Safe con starters: {{ contact.safe_conversation_starters }}
+
+ {% endif %}
+ {% if contact.topics_to_avoid %}
+
+ Topics to avoid: {{ contact.topics_to_avoid }}
+
+ {% endif %}
+ {% if contact.goals %}
+
+ Goals: {{ contact.goals }}
+
+ {% endif %}
+
+
+
+ {% if contact.bio %}
+
+ Bio: {{ contact.bio }}
+
+ {% endif %}
+
+
+
Relationships
+
+ {% if grouped_relationships.familial %}
+
+ {% endif %}
+
+ {% if grouped_relationships.partners %}
+
+ {% endif %}
+
+ {% if grouped_relationships.friends %}
+
+ {% endif %}
+
+ {% if grouped_relationships.professional %}
+
+
Professional:
+ {% for rel in grouped_relationships.professional %}
+
{{ contact_names[rel.related_contact_id] }}({{ rel.relationship_type|replace("_", " ")|title }}){% if not loop.last %}, {% endif %}
+ {% endfor %}
+
+ {% endif %}
+
+ {% if grouped_relationships.other %}
+
+ {% endif %}
+
+ {% if contact.related_from %}
+
+ {% endif %}
+
+
+
+
+ {% if contact.needs %}
+
+ {% for need in contact.needs %}
+
+
+ Warning: {{ need.name }}
+ {% if need.description %} - {{ need.description }}{% endif %}
+
+ {% endfor %}
+
+ {% endif %}
+
+
+
+ Manage Contact
+
+
+
Manage Relationships
+
+ {% include "partials/manage_relationships.html" %}
+
+
+ {% if all_contacts %}
+
+ {% endif %}
+
+
+
+
Manage Needs/Warnings
+
+ {% include "partials/manage_needs.html" %}
+
+
+ {% if available_needs %}
+
+ {% endif %}
+
+
+
+{% endblock %}
diff --git a/python/api/templates/contact_form.html b/python/api/templates/contact_form.html
new file mode 100644
index 0000000..3b8e86c
--- /dev/null
+++ b/python/api/templates/contact_form.html
@@ -0,0 +1,115 @@
+{% extends "base.html" %}
+{% block title %}{{ "Edit " + contact.name if contact else "New Contact" }}{% endblock %}
+{% block content %}
+
+{% endblock %}
diff --git a/python/api/templates/contact_list.html b/python/api/templates/contact_list.html
new file mode 100644
index 0000000..3934844
--- /dev/null
+++ b/python/api/templates/contact_list.html
@@ -0,0 +1,14 @@
+{% extends "base.html" %}
+{% block title %}Contacts{% endblock %}
+{% block content %}
+
+{% endblock %}
diff --git a/python/api/templates/graph.html b/python/api/templates/graph.html
new file mode 100644
index 0000000..4deb8b5
--- /dev/null
+++ b/python/api/templates/graph.html
@@ -0,0 +1,198 @@
+{% extends "base.html" %}
+{% block title %}Relationship Graph{% endblock %}
+{% block content %}
+
+
+
Drag nodes to reposition. Closer relationships have shorter, darker edges.
+
+
+
+
Relationship Closeness (1-10)
+
+
+
+ 10 - Very Close (Spouse, Partner)
+
+
+
+ 7 - Close (Family, Best Friend)
+
+
+
+ 4 - Moderate (Friend, Colleague)
+
+
+
+ 2 - Distant (Acquaintance)
+
+
+
+
+
+
+{% endblock %}
diff --git a/python/api/templates/need_list.html b/python/api/templates/need_list.html
new file mode 100644
index 0000000..b7978ea
--- /dev/null
+++ b/python/api/templates/need_list.html
@@ -0,0 +1,31 @@
+{% extends "base.html" %}
+{% block title %}Needs{% endblock %}
+{% block content %}
+
+
+
+
+
+
+ {% include "partials/need_items.html" %}
+
+
+{% endblock %}
diff --git a/python/api/templates/partials/contact_table.html b/python/api/templates/partials/contact_table.html
new file mode 100644
index 0000000..0f570f8
--- /dev/null
+++ b/python/api/templates/partials/contact_table.html
@@ -0,0 +1,33 @@
+{% if contacts %}
+
+
+
+ | Name |
+ Job |
+ Timezone |
+ Actions |
+
+
+
+ {% for contact in contacts %}
+
+ | {{ contact.name }} |
+ {{ contact.current_job or "-" }} |
+ {{ contact.timezone or "-" }} |
+
+ Edit
+
+ |
+
+ {% endfor %}
+
+
+{% else %}
+No contacts yet.
+{% endif %}
diff --git a/python/api/templates/partials/manage_needs.html b/python/api/templates/partials/manage_needs.html
new file mode 100644
index 0000000..f1a4b69
--- /dev/null
+++ b/python/api/templates/partials/manage_needs.html
@@ -0,0 +1,14 @@
+
+ {% for need in contact.needs %}
+ -
+ {{ need.name }}
+ {% if need.description %} - {{ need.description }}{% endif %}
+
+
+ {% endfor %}
+
diff --git a/python/api/templates/partials/manage_relationships.html b/python/api/templates/partials/manage_relationships.html
new file mode 100644
index 0000000..6ab95f1
--- /dev/null
+++ b/python/api/templates/partials/manage_relationships.html
@@ -0,0 +1,23 @@
+{% for rel in contact.related_to %}
+
+{% endfor %}
diff --git a/python/api/templates/partials/need_items.html b/python/api/templates/partials/need_items.html
new file mode 100644
index 0000000..bd80eb4
--- /dev/null
+++ b/python/api/templates/partials/need_items.html
@@ -0,0 +1,21 @@
+{% if needs %}
+
+ {% for need in needs %}
+ -
+
+
{{ need.name }}
+ {% if need.description %}
{{ need.description }}
{% endif %}
+
+
+
+ {% endfor %}
+
+{% else %}
+No needs defined yet.
+{% endif %}
diff --git a/systems/jeeves/services/contact_api.nix b/systems/jeeves/services/contact_api.nix
index b5904ae..0762591 100644
--- a/systems/jeeves/services/contact_api.nix
+++ b/systems/jeeves/services/contact_api.nix
@@ -8,18 +8,13 @@
8069
];
systemd.services.contact-api = {
- description = "Contact Database API with Frontend";
+ description = "Contact Database API";
after = [
"postgresql.service"
"network.target"
];
requires = [ "postgresql.service" ];
wantedBy = [ "multi-user.target" ];
- path = [
- pkgs.nodejs
- pkgs.coreutils
- pkgs.bash
- ];
environment = {
PYTHONPATH = "${inputs.self}";
@@ -27,18 +22,15 @@
POSTGRES_HOST = "/run/postgresql";
POSTGRES_USER = "richie";
POSTGRES_PORT = "5432";
- HOME = "/var/lib/contact-api";
};
serviceConfig = {
Type = "simple";
- ExecStart = "${pkgs.my_python}/bin/python -m python.api.main --host 192.168.90.40 --port 8069 --frontend-dir ${inputs.self}/frontend";
- StateDirectory = "contact-api";
+ ExecStart = "${pkgs.my_python}/bin/python -m python.api.main --host 192.168.90.40 --port 8069";
Restart = "on-failure";
RestartSec = "5s";
StandardOutput = "journal";
StandardError = "journal";
- # Security hardening
NoNewPrivileges = true;
ProtectSystem = "strict";
ProtectHome = "read-only";