diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..420934c --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,81 @@ +name: CI + +on: + pull_request: + branches: + - main + push: + branches: + - main + workflow_dispatch: + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Run lint + run: npm run lint + + test: + name: Test + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm run test + + build: + name: Build + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Run build + run: npm run build diff --git a/.gitignore b/.gitignore index d70be45..519c994 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ lerna-debug.log* node_modules dist dist-ssr +coverage/ *.local # Editor directories and files diff --git a/README.md b/README.md index 0d6babe..9d0b4bc 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ export default { project: ['./tsconfig.json', './tsconfig.node.json'], tsconfigRootDir: __dirname, }, -} +}; ``` - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` diff --git a/package-lock.json b/package-lock.json index ad04035..c523c84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,6 +68,7 @@ "@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/parser": "^7.2.0", "@vitejs/plugin-react": "^4.2.1", + "@vitest/coverage-v8": "^4.0.18", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.29.1", @@ -76,13 +77,15 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", "graphql": "^16.8.1", + "jsdom": "^27.0.1", "postcss": "^8.4.38", "postcss-flexbugs-fixes": "^5.0.2", "postcss-normalize": "^10.0.1", "postcss-preset-env": "^9.1.3", "prettier": "3.3.2", "typescript": "^5.2.2", - "vite": "^5.2.0" + "vite": "^5.2.0", + "vitest": "^4.0.18" } }, "node_modules/@adobe/css-tools": { @@ -434,6 +437,176 @@ "node": ">=14" } }, + "node_modules/@asamuzakjp/css-color": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.2.tgz", + "integrity": "sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.0.0", + "@csstools/css-color-parser": "^4.0.1", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.2.5" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/@csstools/color-helpers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.1.tgz", + "integrity": "sha512-NmXRccUJMk2AWA5A7e5a//3bCIMyOu2hAtdRYrhPPHjDxINuCwX1w6rnIZ4xjLcp0ayv6h8Pc3X0eJUGiAAXHQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/@csstools/css-calc": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.0.1.tgz", + "integrity": "sha512-bsDKIP6f4ta2DO9t+rAbSSwv4EMESXy5ZIvzQl1afmD6Z1XHkVu9ijcG9QR/qSgQS1dVa+RaQ/MfQ7FIB/Dn1Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/@csstools/css-color-parser": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.1.tgz", + "integrity": "sha512-vYwO15eRBEkeF6xjAno/KQ61HacNhfQuuU/eGwH67DplL0zD5ZixUa563phQvUelA07yDczIXdtmYojCphKJcw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.0.1", + "@csstools/css-calc": "^3.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.7.8", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.8.tgz", + "integrity": "sha512-stisC1nULNc9oH5lakAj8MH88ZxeGxzyWNDfbdCxvJSJIvDsHNZqYvscGTgy/ysgXWLJPt6K/4t0/GjvtKcFJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.5" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/code-frame": { "version": "7.26.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.0.tgz", @@ -672,18 +845,18 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -714,12 +887,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.0.tgz", - "integrity": "sha512-aP8x5pIw3xvYr/sXT+SEUwyhrXT8rUJRZltK/qN3Db80dcKpTett8cJxHyjk+xYSVXvNnl2SfcJVjbwxpOSscA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "license": "MIT", "dependencies": { - "@babel/types": "^7.26.0" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -1292,13 +1465,13 @@ } }, "node_modules/@babel/types": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", - "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1326,6 +1499,16 @@ "node": ">=18.x" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@csstools/cascade-layer-name-parser": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-1.0.13.tgz", @@ -1445,6 +1628,23 @@ "@csstools/css-tokenizer": "^2.4.1" } }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.0.27", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.27.tgz", + "integrity": "sha512-sxP33Jwg1bviSUXAV43cVYdmjt2TLnLXNqCWl9xmxHawWVjGz/kEbdkr7F9pxJNBN2Mh+dq0crgItbW6tQvyow==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0" + }, "node_modules/@csstools/css-tokenizer": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.4.1.tgz", @@ -2799,6 +2999,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/netbsd-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", @@ -2816,6 +3033,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/openbsd-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", @@ -2833,6 +3067,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/sunos-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", @@ -4123,15 +4374,15 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -4777,9 +5028,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", - "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", "cpu": [ "arm" ], @@ -4791,9 +5042,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", - "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", "cpu": [ "arm64" ], @@ -4805,9 +5056,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", - "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", "cpu": [ "arm64" ], @@ -4819,9 +5070,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", - "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", "cpu": [ "x64" ], @@ -4832,10 +5083,38 @@ "darwin" ] }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", - "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", "cpu": [ "arm" ], @@ -4847,9 +5126,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", - "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", "cpu": [ "arm" ], @@ -4861,9 +5140,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", - "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", "cpu": [ "arm64" ], @@ -4875,9 +5154,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", - "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", "cpu": [ "arm64" ], @@ -4888,10 +5167,52 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", - "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", "cpu": [ "ppc64" ], @@ -4903,9 +5224,23 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", - "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", "cpu": [ "riscv64" ], @@ -4917,9 +5252,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", - "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", "cpu": [ "s390x" ], @@ -4931,9 +5266,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", - "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", "cpu": [ "x64" ], @@ -4945,9 +5280,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", - "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", "cpu": [ "x64" ], @@ -4958,10 +5293,38 @@ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", - "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", "cpu": [ "arm64" ], @@ -4973,9 +5336,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", - "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", "cpu": [ "ia32" ], @@ -4986,10 +5349,24 @@ "win32" ] }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", - "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", "cpu": [ "x64" ], @@ -5040,6 +5417,13 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, "node_modules/@testing-library/dom": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", @@ -5213,10 +5597,28 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, "node_modules/@types/hoist-non-react-statics": { @@ -5572,6 +5974,189 @@ "vite": "^4.2.0 || ^5.0.0" } }, + "node_modules/@vitest/coverage-v8": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.18.tgz", + "integrity": "sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.0.18", + "ast-v8-to-istanbul": "^0.3.10", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.1", + "obug": "^2.1.1", + "std-env": "^3.10.0", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.0.18", + "vitest": "4.0.18" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/coverage-v8/node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/@vitest/coverage-v8/node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@vitest/coverage-v8/node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@vitest/coverage-v8/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/coverage-v8/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@vitest/expect": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.18", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", @@ -6155,14 +6740,11 @@ } }, "node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, "engines": { "node": ">= 14" } @@ -7725,6 +8307,16 @@ "inherits": "2.0.3" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -7742,6 +8334,25 @@ "license": "ISC", "peer": true }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.11.tgz", + "integrity": "sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -9317,6 +9928,26 @@ "react": ">=16.8" } }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/bidi-js/node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -10263,6 +10894,16 @@ "node": ">=0.10.0" } }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -11605,6 +12246,20 @@ "fastparse": "^1.1.2" } }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/css-what": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", @@ -11866,13 +12521,29 @@ "peer": true }, "node_modules/cssstyle": { - "version": "0.2.37", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.37.tgz", - "integrity": "sha512-FUpKc+1FNBsHUr9IsfSGCovr8VuGOiiuzlgCyppKBjJi2jYTOFLN3oiiNRMIvYqbFzF38mqKj4BgcevzU5/kIA==", + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.7.tgz", + "integrity": "sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "cssom": "0.3.x" + "@asamuzakjp/css-color": "^4.1.1", + "@csstools/css-syntax-patches-for-csstree": "^1.0.21", + "css-tree": "^3.1.0", + "lru-cache": "^11.2.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cssstyle/node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" } }, "node_modules/csstype": { @@ -11956,6 +12627,57 @@ "node": ">=0.10" } }, + "node_modules/data-urls": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.1.tgz", + "integrity": "sha512-euIQENZg6x8mj3fO6o9+fOW8MimUI4PpD/fZBhJfeioZVy9TUpM4UY7KjQNVZFlqwJ0UdzRDzkycB997HEq1BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^15.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls/node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls/node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", + "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/data-view-buffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", @@ -12063,6 +12785,13 @@ "node": ">=0.10.0" } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, "node_modules/decimal.js-light": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", @@ -12990,11 +13719,10 @@ } }, "node_modules/es-module-lexer": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", - "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", - "license": "MIT", - "peer": true + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "license": "MIT" }, "node_modules/es-object-atoms": { "version": "1.0.0", @@ -13856,6 +14584,16 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -14100,6 +14838,16 @@ "node": ">=0.10.0" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/express": { "version": "4.21.1", "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", @@ -15943,13 +16691,16 @@ "peer": true }, "node_modules/html-encoding-sniffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", - "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "whatwg-encoding": "^1.0.1" + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" } }, "node_modules/html-entities": { @@ -15959,6 +16710,13 @@ "license": "MIT", "peer": true }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/html-minifier": { "version": "3.5.21", "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz", @@ -16260,13 +17018,13 @@ "peer": true }, "node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { @@ -17356,6 +18114,13 @@ "node": ">=0.10.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-primitive": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", @@ -18444,6 +19209,130 @@ "jsdom": "^9.12.0" } }, + "node_modules/jest-environment-jsdom/node_modules/acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha512-fu2ygVGuMmlzG8ZeRJ0bvR41nsAkxxhbyk8bZ1SS521Z7vmgJFTQQlfz/Mp/nJexGBz+v8sC9bM6+lNgskt4Ug==", + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/cssstyle": { + "version": "0.2.37", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.37.tgz", + "integrity": "sha512-FUpKc+1FNBsHUr9IsfSGCovr8VuGOiiuzlgCyppKBjJi2jYTOFLN3oiiNRMIvYqbFzF38mqKj4BgcevzU5/kIA==", + "license": "MIT", + "peer": true, + "dependencies": { + "cssom": "0.3.x" + } + }, + "node_modules/jest-environment-jsdom/node_modules/html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "license": "MIT", + "peer": true, + "dependencies": { + "whatwg-encoding": "^1.0.1" + } + }, + "node_modules/jest-environment-jsdom/node_modules/jsdom": { + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-9.12.0.tgz", + "integrity": "sha512-Qw4oqNxo4LyzkSqVIyCnEltTc4xV3g1GBaI88AvYTesWzmWHUSoMNmhBjUBa+6ldXIBJS9xoeLNJPfUAykTyxw==", + "license": "MIT", + "peer": true, + "dependencies": { + "abab": "^1.0.3", + "acorn": "^4.0.4", + "acorn-globals": "^3.1.0", + "array-equal": "^1.0.0", + "content-type-parser": "^1.0.1", + "cssom": ">= 0.3.2 < 0.4.0", + "cssstyle": ">= 0.2.37 < 0.3.0", + "escodegen": "^1.6.1", + "html-encoding-sniffer": "^1.0.1", + "nwmatcher": ">= 1.3.9 < 2.0.0", + "parse5": "^1.5.1", + "request": "^2.79.0", + "sax": "^1.2.1", + "symbol-tree": "^3.2.1", + "tough-cookie": "^2.3.2", + "webidl-conversions": "^4.0.0", + "whatwg-encoding": "^1.0.1", + "whatwg-url": "^4.3.0", + "xml-name-validator": "^2.0.1" + } + }, + "node_modules/jest-environment-jsdom/node_modules/parse5": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz", + "integrity": "sha512-w2jx/0tJzvgKwZa58sj2vAYq/S/K1QJfIB3cWYea/Iu1scFPDQQ3IQiVZTHWtRBwAjv2Yd7S/xeZf3XqLDb3bA==", + "peer": true + }, + "node_modules/jest-environment-jsdom/node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/jest-environment-jsdom/node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "license": "BSD-2-Clause", + "peer": true + }, + "node_modules/jest-environment-jsdom/node_modules/whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "license": "MIT", + "peer": true, + "dependencies": { + "iconv-lite": "0.4.24" + } + }, + "node_modules/jest-environment-jsdom/node_modules/whatwg-url": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-4.8.0.tgz", + "integrity": "sha512-nUvUPuenPFtPfy/X+dAYh/TfRbTBlnXTM5iIfLseJFkkQewmpG9pGR6i87E9qL+lZaJzv+99kkQWoGOtLfkZQQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/whatwg-url/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause", + "peer": true + }, + "node_modules/jest-environment-jsdom/node_modules/xml-name-validator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz", + "integrity": "sha512-jRKe/iQYMyVJpzPH+3HL97Lgu5HrCfii+qSo+TfjKHtOnvbnvdVfMYrn9Q34YV81M2e5sviJlI6Ko9y+nByzvA==", + "license": "WTFPL", + "peer": true + }, "node_modules/jest-environment-node": { "version": "20.0.3", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-20.0.3.tgz", @@ -19811,63 +20700,93 @@ "peer": true }, "node_modules/jsdom": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-9.12.0.tgz", - "integrity": "sha512-Qw4oqNxo4LyzkSqVIyCnEltTc4xV3g1GBaI88AvYTesWzmWHUSoMNmhBjUBa+6ldXIBJS9xoeLNJPfUAykTyxw==", + "version": "27.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.0.1.tgz", + "integrity": "sha512-SNSQteBL1IlV2zqhwwolaG9CwhIhTvVHWg3kTss/cLE7H/X4644mtPQqYvCfsSrGQWt9hSZcgOXX8bOZaMN+kA==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "abab": "^1.0.3", - "acorn": "^4.0.4", - "acorn-globals": "^3.1.0", - "array-equal": "^1.0.0", - "content-type-parser": "^1.0.1", - "cssom": ">= 0.3.2 < 0.4.0", - "cssstyle": ">= 0.2.37 < 0.3.0", - "escodegen": "^1.6.1", - "html-encoding-sniffer": "^1.0.1", - "nwmatcher": ">= 1.3.9 < 2.0.0", - "parse5": "^1.5.1", - "request": "^2.79.0", - "sax": "^1.2.1", - "symbol-tree": "^3.2.1", - "tough-cookie": "^2.3.2", - "webidl-conversions": "^4.0.0", - "whatwg-encoding": "^1.0.1", - "whatwg-url": "^4.3.0", - "xml-name-validator": "^2.0.1" + "@asamuzakjp/dom-selector": "^6.7.2", + "cssstyle": "^5.3.1", + "data-urls": "^6.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^8.0.0", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.1.0", + "ws": "^8.18.3", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } } }, - "node_modules/jsdom/node_modules/acorn": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", - "integrity": "sha512-fu2ygVGuMmlzG8ZeRJ0bvR41nsAkxxhbyk8bZ1SS521Z7vmgJFTQQlfz/Mp/nJexGBz+v8sC9bM6+lNgskt4Ug==", + "node_modules/jsdom/node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, "license": "MIT", - "peer": true, - "bin": { - "acorn": "bin/acorn" + "dependencies": { + "punycode": "^2.3.1" }, "engines": { - "node": ">=0.4.0" + "node": ">=20" } }, "node_modules/jsdom/node_modules/whatwg-url": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-4.8.0.tgz", - "integrity": "sha512-nUvUPuenPFtPfy/X+dAYh/TfRbTBlnXTM5iIfLseJFkkQewmpG9pGR6i87E9qL+lZaJzv+99kkQWoGOtLfkZQQ==", + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", + "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.0" + }, + "engines": { + "node": ">=20" } }, - "node_modules/jsdom/node_modules/whatwg-url/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause", - "peer": true + "node_modules/jsdom/node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } }, "node_modules/jsesc": { "version": "3.0.2", @@ -20651,6 +21570,28 @@ "lz-string": "bin/bin.js" } }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, "node_modules/make-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", @@ -20753,6 +21694,13 @@ "safe-buffer": "^5.1.2" } }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -21246,9 +22194,9 @@ "peer": true }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -23918,7 +24866,6 @@ }, "node_modules/npm/node_modules/lodash._baseindexof": { "version": "3.1.0", - "extraneous": true, "inBundle": true, "license": "MIT" }, @@ -23933,19 +24880,16 @@ }, "node_modules/npm/node_modules/lodash._bindcallback": { "version": "3.0.1", - "extraneous": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/lodash._cacheindexof": { "version": "3.0.2", - "extraneous": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/lodash._createcache": { "version": "3.1.2", - "extraneous": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -23959,7 +24903,6 @@ }, "node_modules/npm/node_modules/lodash._getnative": { "version": "3.9.1", - "extraneous": true, "inBundle": true, "license": "MIT" }, @@ -23975,7 +24918,6 @@ }, "node_modules/npm/node_modules/lodash.restparam": { "version": "3.6.1", - "extraneous": true, "inBundle": true, "license": "MIT" }, @@ -26288,6 +27230,17 @@ "license": "MIT", "peer": true }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -26772,10 +27725,30 @@ } }, "node_modules/parse5": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz", - "integrity": "sha512-w2jx/0tJzvgKwZa58sj2vAYq/S/K1QJfIB3cWYea/Iu1scFPDQQ3IQiVZTHWtRBwAjv2Yd7S/xeZf3XqLDb3bA==", - "peer": true + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } }, "node_modules/parseurl": { "version": "1.3.3", @@ -26922,6 +27895,13 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/pbkdf2": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", @@ -27130,9 +28110,9 @@ } }, "node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -27150,8 +28130,8 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.0", + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, "engines": { @@ -32094,11 +33074,17 @@ "license": "ISC" }, "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", "license": "MIT", - "peer": true + "peer": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } }, "node_modules/public-encrypt": { "version": "4.0.3", @@ -38116,6 +39102,20 @@ "node": ">= 0.12" } }, + "node_modules/request/node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -38350,13 +39350,13 @@ } }, "node_modules/rollup": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", - "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.6" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -38366,22 +39366,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.24.0", - "@rollup/rollup-android-arm64": "4.24.0", - "@rollup/rollup-darwin-arm64": "4.24.0", - "@rollup/rollup-darwin-x64": "4.24.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", - "@rollup/rollup-linux-arm-musleabihf": "4.24.0", - "@rollup/rollup-linux-arm64-gnu": "4.24.0", - "@rollup/rollup-linux-arm64-musl": "4.24.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", - "@rollup/rollup-linux-riscv64-gnu": "4.24.0", - "@rollup/rollup-linux-s390x-gnu": "4.24.0", - "@rollup/rollup-linux-x64-gnu": "4.24.0", - "@rollup/rollup-linux-x64-musl": "4.24.0", - "@rollup/rollup-win32-arm64-msvc": "4.24.0", - "@rollup/rollup-win32-ia32-msvc": "4.24.0", - "@rollup/rollup-win32-x64-msvc": "4.24.0", + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" } }, @@ -38400,6 +39409,13 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -38637,11 +39653,27 @@ "license": "MIT" }, "node_modules/sax": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", - "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", + "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "license": "BlueOak-1.0.0", + "peer": true, + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, "license": "ISC", - "peer": true + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } }, "node_modules/scheduler": { "version": "0.23.2", @@ -39091,6 +40123,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -39618,6 +40657,13 @@ "figgy-pudding": "^3.5.1" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -39669,6 +40715,13 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, "node_modules/stop-iteration-iterator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", @@ -40219,8 +41272,7 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/synckit": { "version": "0.9.2", @@ -40715,6 +41767,81 @@ "node": ">=0.6.0" } }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/title-case": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/title-case/-/title-case-3.0.3.tgz", @@ -40725,6 +41852,26 @@ "tslib": "^2.0.3" } }, + "node_modules/tldts": { + "version": "7.0.23", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.23.tgz", + "integrity": "sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.23" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.23", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.23.tgz", + "integrity": "sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ==", + "dev": true, + "license": "MIT" + }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -40826,17 +41973,16 @@ "peer": true }, "node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" + "tldts": "^7.0.5" }, "engines": { - "node": ">=0.8" + "node": ">=16" } }, "node_modules/tr46": { @@ -41845,6 +42991,665 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/vitest": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.18", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/vitest/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/vitest/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/vitest/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/vitest/node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, "node_modules/vm-browserify": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", @@ -41852,6 +43657,19 @@ "license": "MIT", "peer": true }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -42379,11 +44197,14 @@ "license": "Apache-2.0" }, "node_modules/webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, "license": "BSD-2-Clause", - "peer": true + "engines": { + "node": ">=20" + } }, "node_modules/webpack": { "version": "5.95.0", @@ -42533,13 +44354,30 @@ } }, "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "iconv-lite": "0.4.24" + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, "node_modules/whatwg-fetch": { @@ -42549,6 +44387,16 @@ "license": "MIT", "peer": true }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -42679,6 +44527,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wicked-good-xpath": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz", @@ -42857,11 +44722,21 @@ } }, "node_modules/xml-name-validator": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz", - "integrity": "sha512-jRKe/iQYMyVJpzPH+3HL97Lgu5HrCfii+qSo+TfjKHtOnvbnvdVfMYrn9Q34YV81M2e5sviJlI6Ko9y+nByzvA==", - "license": "WTFPL", - "peer": true + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" }, "node_modules/xmldom-sre": { "version": "0.1.31", diff --git a/package.json b/package.json index 7635092..4c1b8a9 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,9 @@ "scripts": { "start": "vite", "dev": "vite", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", "build": "tsc && vite build", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "format": "prettier --write ./src", @@ -96,6 +99,7 @@ "@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/parser": "^7.2.0", "@vitejs/plugin-react": "^4.2.1", + "@vitest/coverage-v8": "^4.0.18", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.29.1", @@ -104,12 +108,14 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", "graphql": "^16.8.1", + "jsdom": "^27.0.1", "postcss": "^8.4.38", "postcss-flexbugs-fixes": "^5.0.2", "postcss-normalize": "^10.0.1", "postcss-preset-env": "^9.1.3", "prettier": "3.3.2", "typescript": "^5.2.2", - "vite": "^5.2.0" + "vite": "^5.2.0", + "vitest": "^4.0.18" } } diff --git a/src/App.tsx b/src/App.tsx index a1db601..09f5208 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,39 +1,29 @@ -import { Outlet } from 'react-router-dom'; +import { Suspense, useCallback, useEffect } from 'react'; +import { Outlet, useLocation } from 'react-router-dom'; import { Layout } from 'antd'; import { ModuleRegistry } from 'ag-grid-community'; import { EnterpriseCoreModule, LicenseManager } from 'ag-grid-enterprise'; import { AgCharts as AgChartsEnterprise } from 'ag-charts-enterprise'; + import { useAppDispatch } from './app/hooks'; import { AppThunk } from './app/store'; import { initializeSimulationsToRun } from './features/simulationRunner/simulationRunnerSlice'; -import { useLoadHistoricDailyPricesMutation } from './features/coinData/coinPriceRetrievalService'; -import { - CoinPrice, -} from './features/simulationRunConfiguration/simulationRunConfigModels'; -import { ReturnTimeStep } from './features/simulationResults/simulationResultSummaryModels'; -import { - updateCoinPriceHistory, - updateCoinPriceHistoryLoaded, - updateCoinPriceHistoryLoadedStatus, -} from './features/simulationRunConfiguration/simulationRunConfigurationSlice'; import { AntDesignThemeProvider } from './AntDesignThemeProvider'; import { MenuComponent } from './Menu'; +import { ROUTES } from './routesEnum'; import style from './app.module.scss'; -import { batch } from 'react-redux'; - -export interface Success { - data: CoinPrice[]; -} +import { loadPriceHistoryAsync } from './features/coinData/loadPriceHistoryThunk'; const { Content, Header } = Layout; -// TODO: Remove and put in .env -const AG_GRID_LICENSE_KEY = - 'Using_this_{AG_Charts_and_AG_Grid}_Enterprise_key_{AG-076103}_in_excess_of_the_licence_granted_is_not_permitted___Please_report_misuse_to_legal@ag-grid.com___For_help_with_changing_this_key_please_contact_info@ag-grid.com___{Robo_Inc.}_is_granted_a_{Single_Application}_Developer_License_for_the_application_{QuantAMM_APP}_only_for_{1}_Front-End_JavaScript_developer___All_Front-End_JavaScript_developers_working_on_{QuantAMM_APP}_need_to_be_licensed___{QuantAMM_APP}_has_been_granted_a_Deployment_License_Add-on_for_{1}_Production_Environment___This_key_works_with_{AG_Charts_and_AG_Grid}_Enterprise_versions_released_before_{30_January_2026}____[v3]_[0102]_MTc2OTczMTIwMDAwMA==45158aeb0ade13e0a121e419751d0010'; +const isRoute = (value: string): value is ROUTES => + (Object.values(ROUTES) as string[]).includes(value); + +const AG_GRID_LICENSE_KEY = import.meta.env.AG_GRID_LICENCE_KEY ?? ''; const initialiseSimsToRun = (): AppThunk => (dispatch, getState) => { - if (getState().simRunner.simulationsToRun.length == 0) { + if (getState().simRunner.simulationsToRun.length === 0) { dispatch( initializeSimulationsToRun({ pools: getState().simConfig.simulationLiquidityPools, @@ -48,79 +38,30 @@ AgChartsEnterprise.setLicenseKey(AG_GRID_LICENSE_KEY); function App() { const dispatch = useAppDispatch(); - const [loadHistoricPrices] = useLoadHistoricDailyPricesMutation(); - - const loadPriceHistoryAsync = (): AppThunk => async (dispatch, getState) => { - const { - availableCoins, - coinPriceHistoryLoaded, - coinPriceHistoryLoadedStatus, - } = getState().simConfig; - - if (!coinPriceHistoryLoaded && coinPriceHistoryLoadedStatus === 'pending') { - dispatch(updateCoinPriceHistoryLoadedStatus('loading')); - - // 1) Fire off all the requests in parallel - const promises = availableCoins.map(async (coin) => { - const data = await loadHistoricPrices({ - coinCode: coin.coinCode, - }).unwrap(); - - const fullPriceMap = new Map(); - const timesteps = new Map(); + const location = useLocation(); - data.forEach((p, i) => { - fullPriceMap.set(p.unix, p); - const ret = i === 0 ? 0 : p.close / data[i - 1].close - 1; - timesteps.set(p.unix, { date: p.date, unix: p.unix, return: ret }); - }); - - return { - coin, - dailyPriceHistory: data, - dailyPriceHistoryMap: fullPriceMap, - dailyReturns: timesteps, - }; - }); - - const results = await Promise.all(promises); - - batch(() => { - results.forEach( - ({ coin, dailyPriceHistory, dailyPriceHistoryMap, dailyReturns }) => { - dispatch( - updateCoinPriceHistory({ - coinCode: coin.coinCode, - coinName: coin.coinName, - dailyPriceHistory, - dailyPriceHistoryMap, - dailyReturns, - coinComparisons: new Map(), - deploymentByChain: coin.deploymentByChain, - }) - ); - } - ); - dispatch(updateCoinPriceHistoryLoaded(true)); - dispatch(updateCoinPriceHistoryLoadedStatus('loaded')); - }); - } - }; + const initialisePage = useCallback( + (page: string) => { + if (!isRoute(page)) { + return; + } - function initialisePage(page: string) { - switch (page) { - case 'simrunner': { + if (page === ROUTES.SIMULATION_RUNNER) { dispatch(initialiseSimsToRun()); - break; } - } + }, + [dispatch] + ); + useEffect(() => { dispatch(loadPriceHistoryAsync()); - } + }, [dispatch]); - setTimeout(() => { - dispatch(loadPriceHistoryAsync()); - }, 100); + useEffect(() => { + const segment = location.pathname.split('/')[1] ?? ''; + const page = isRoute(segment) ? segment : ROUTES.HOME; + initialisePage(page); + }, [location.pathname, initialisePage]); return ( @@ -129,7 +70,9 @@ function App() { - + }> + + diff --git a/src/Menu.tsx b/src/Menu.tsx index 19362e2..681311c 100644 --- a/src/Menu.tsx +++ b/src/Menu.tsx @@ -39,7 +39,7 @@ export const MenuComponent: FC = ({ initialise }) => { const isDark = useAppSelector(selectTheme); const location = useLocation(); - + const liveProducts = CURRENT_LIVE_FACTSHEETS; useEffect(() => { @@ -58,7 +58,6 @@ export const MenuComponent: FC = ({ initialise }) => { const toggleTheme = (isChecked: boolean) => { dispatch(changeTheme(isChecked || (current as ROUTES) == ROUTES.HOME)); - changeTheme(isChecked); }; const handleClick: MenuProps['onClick'] = useCallback( @@ -92,22 +91,24 @@ export const MenuComponent: FC = ({ initialise }) => { label: 'View Products', type: 'submenu', style: { marginLeft: 'auto' }, // Align to the right - children: liveProducts.factsheets.filter(x => x.status == "LIVE").map((product) => ({ - key: - ROUTES.PRODUCT_EXPLORER + - '/' + - product.poolChain + - '/' + - product.poolId, - label: `View ${product.iconTitle}`, - icon: ( - {product.iconTitle} - ), - })), + children: liveProducts.factsheets + .filter((x) => x.status == 'LIVE') + .map((product) => ({ + key: + ROUTES.PRODUCT_EXPLORER + + '/' + + product.poolChain + + '/' + + product.poolId, + label: `View ${product.iconTitle}`, + icon: ( + {product.iconTitle} + ), + })), }, { key: 'Education', @@ -146,22 +147,24 @@ export const MenuComponent: FC = ({ initialise }) => { label: 'View Products', type: 'submenu', style: { marginLeft: 'auto' }, // Align to the right - children: liveProducts.factsheets.filter(x => x.status == "LIVE").map((product) => ({ - key: - ROUTES.PRODUCT_EXPLORER + - '/' + - product.poolChain + - '/' + - product.poolId, - label: `View ${product.iconTitle}`, - icon: ( - {product.iconTitle} - ), - })), + children: liveProducts.factsheets + .filter((x) => x.status == 'LIVE') + .map((product) => ({ + key: + ROUTES.PRODUCT_EXPLORER + + '/' + + product.poolChain + + '/' + + product.poolId, + label: `View ${product.iconTitle}`, + icon: ( + {product.iconTitle} + ), + })), }, { key: 'About', diff --git a/src/app/hooks.ts b/src/app/hooks.ts index 520e84e..d658a5e 100644 --- a/src/app/hooks.ts +++ b/src/app/hooks.ts @@ -1,6 +1,5 @@ import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; import type { RootState, AppDispatch } from './store'; -// Use throughout your app instead of plain `useDispatch` and `useSelector` export const useAppDispatch = () => useDispatch(); export const useAppSelector: TypedUseSelectorHook = useSelector; diff --git a/src/features/coinData/coinComparisonCalculator.ts b/src/features/coinData/coinComparisonCalculator.ts index 006c7d5..3f54d00 100644 --- a/src/features/coinData/coinComparisonCalculator.ts +++ b/src/features/coinData/coinComparisonCalculator.ts @@ -15,35 +15,46 @@ export function CalculateCoinComparison( }; } +function getMinMaxUnix(returns: ReturnTimeStep[]) { + let min = Number.POSITIVE_INFINITY; + let max = Number.NEGATIVE_INFINITY; + + for (const item of returns) { + if (item.unix < min) min = item.unix; + if (item.unix > max) max = item.unix; + } + + return { min, max }; +} + //tracking error export function calculateTrackingError( rPortfolio: ReturnTimeStep[], rBenchmark: ReturnTimeStep[] ): number { - const rPortfolioMax = rPortfolio.sort((a, b) => (a.unix > b.unix ? -1 : 1))[0] - .unix; - const rBenchmarkMax = rBenchmark.sort((a, b) => (a.unix > b.unix ? -1 : 1))[0] - .unix; - const rPortfolioMin = rPortfolio.sort((a, b) => (a.unix < b.unix ? -1 : 1))[0] - .unix; - const rBenchmarkMin = rBenchmark.sort((a, b) => (a.unix < b.unix ? -1 : 1))[0] - .unix; - - let rangeMin = rPortfolioMin; - if (rangeMin < rBenchmarkMin) { - rangeMin = rBenchmarkMin; + if (rPortfolio.length === 0 || rBenchmark.length === 0) { + return 0; } - let rangeMax = rPortfolioMax; - if (rangeMax > rBenchmarkMax) { - rangeMax = rBenchmarkMax; + + const { min: rPortfolioMin, max: rPortfolioMax } = getMinMaxUnix(rPortfolio); + const { min: rBenchmarkMin, max: rBenchmarkMax } = getMinMaxUnix(rBenchmark); + + const rangeMin = Math.max(rPortfolioMin, rBenchmarkMin); + const rangeMax = Math.min(rPortfolioMax, rBenchmarkMax); + if (rangeMax <= rangeMin) { + return 0; } const comparisonRPortfolio = rPortfolio .filter((x) => rangeMin < x.unix && rangeMax > x.unix) - .sort((a, b) => (a.unix < b.unix ? -1 : 1)); - const comparionRBenchmark = rBenchmark + .sort((a, b) => a.unix - b.unix); + const comparisonRBenchmark = rBenchmark .filter((x) => rangeMin < x.unix && rangeMax > x.unix) - .sort((a, b) => (a.unix < b.unix ? -1 : 1)); + .sort((a, b) => a.unix - b.unix); + + if (comparisonRPortfolio.length === 0 || comparisonRBenchmark.length === 0) { + return 0; + } const rPortfolioMean = comparisonRPortfolio @@ -53,23 +64,23 @@ export function calculateTrackingError( }, 0) / comparisonRPortfolio.length; const rBenchmarkMean = - comparionRBenchmark + comparisonRBenchmark .map((x) => x.return) .reduce((accumulator, current) => { return accumulator + current; - }, 0) / comparionRBenchmark.length; + }, 0) / comparisonRBenchmark.length; const meanDif = rPortfolioMean - rBenchmarkMean; const numerator = comparisonRPortfolio .map((x) => { let rbEquiv = findLastAvailableReturn( - comparionRBenchmark, + comparisonRBenchmark, x.unix, 1, 604800000 /*daily*/ )?.return; - if (rbEquiv == undefined) { + if (rbEquiv === undefined) { rbEquiv = x.return; } @@ -86,31 +97,30 @@ export function calculateTrackingError( export function calculateCovariance( rPortfolio: ReturnTimeStep[], rBenchmark: ReturnTimeStep[] -) { - const rPortfolioMax = rPortfolio.sort((a, b) => (a.unix > b.unix ? -1 : 1))[0] - .unix; - const rBenchmarkMax = rBenchmark.sort((a, b) => (a.unix > b.unix ? -1 : 1))[0] - .unix; - const rPortfolioMin = rPortfolio.sort((a, b) => (a.unix < b.unix ? -1 : 1))[0] - .unix; - const rBenchmarkMin = rBenchmark.sort((a, b) => (a.unix < b.unix ? -1 : 1))[0] - .unix; - - let rangeMin = rPortfolioMin; - if (rangeMin < rBenchmarkMin) { - rangeMin = rBenchmarkMin; +): number { + if (rPortfolio.length === 0 || rBenchmark.length === 0) { + return 0; } - let rangeMax = rPortfolioMax; - if (rangeMax > rBenchmarkMax) { - rangeMax = rBenchmarkMax; + + const { min: rPortfolioMin, max: rPortfolioMax } = getMinMaxUnix(rPortfolio); + const { min: rBenchmarkMin, max: rBenchmarkMax } = getMinMaxUnix(rBenchmark); + + const rangeMin = Math.max(rPortfolioMin, rBenchmarkMin); + const rangeMax = Math.min(rPortfolioMax, rBenchmarkMax); + if (rangeMax <= rangeMin) { + return 0; } const comparisonRPortfolio = rPortfolio .filter((x) => rangeMin < x.unix && rangeMax > x.unix) - .sort((a, b) => (a.unix < b.unix ? -1 : 1)); - const comparionRBenchmark = rBenchmark + .sort((a, b) => a.unix - b.unix); + const comparisonRBenchmark = rBenchmark .filter((x) => rangeMin < x.unix && rangeMax > x.unix) - .sort((a, b) => (a.unix < b.unix ? -1 : 1)); + .sort((a, b) => a.unix - b.unix); + + if (comparisonRPortfolio.length === 0 || comparisonRBenchmark.length === 0) { + return 0; + } let rPortfolioMean = 0; comparisonRPortfolio.forEach((v) => { @@ -121,23 +131,23 @@ export function calculateCovariance( let rBenchmarkMean = 0; - comparionRBenchmark.forEach((v) => { + comparisonRBenchmark.forEach((v) => { rBenchmarkMean += v.return; }); - rBenchmarkMean /= comparionRBenchmark.length; + rBenchmarkMean /= comparisonRBenchmark.length; let sumRiRj = 0; comparisonRPortfolio.forEach((x) => { let rbEquiv = findLastAvailableReturn( - comparionRBenchmark, + comparisonRBenchmark, x.unix, 1, 604800000 /*daily*/ )?.return; - if (rbEquiv == undefined) { + if (rbEquiv === undefined) { rbEquiv = x.return; } diff --git a/src/features/coinData/coinCurrentPriceSlice.test.ts b/src/features/coinData/coinCurrentPriceSlice.test.ts new file mode 100644 index 0000000..70fac10 --- /dev/null +++ b/src/features/coinData/coinCurrentPriceSlice.test.ts @@ -0,0 +1,129 @@ +import { describe, expect, it } from 'vitest'; +import { GqlChain } from '../../__generated__/graphql-types'; +import coinCurrentPricesReducer, { + COIN_PRICE_CHAINS, + fetchCoinCurrentPrices, + selectCoinChainStatus, + selectCoinPrice, + setCoinPriceChains, + setPollingState, +} from './coinCurrentPriceSlice'; + +const getTestChains = (): [GqlChain, GqlChain] => [ + COIN_PRICE_CHAINS[0] ?? GqlChain.Base, + COIN_PRICE_CHAINS[1] ?? GqlChain.Arbitrum, +]; + +describe('coinCurrentPriceSlice view-model logic', () => { + it('updates polling state and interval', () => { + const baseState = coinCurrentPricesReducer(undefined, { type: '@@INIT' }); + const nextState = coinCurrentPricesReducer( + baseState, + setPollingState({ isRunning: true, intervalMs: 15_000 }) + ); + + expect(nextState.polling.isRunning).toBe(true); + expect(nextState.polling.intervalMs).toBe(15_000); + }); + + it('retains data for selected chains and drops removed chains', () => { + const [chainA, chainB] = getTestChains(); + const baseState = coinCurrentPricesReducer(undefined, { type: '@@INIT' }); + const seededState = { + ...baseState, + byChain: { + [chainA]: { + '0xabc': { address: '0xAbC', price: 100, updatedAt: 123 }, + }, + [chainB]: { + '0xdef': { address: '0xDef', price: 200, updatedAt: 456 }, + }, + }, + statusByChain: { + [chainA]: 'succeeded', + [chainB]: 'failed', + }, + errorByChain: { + [chainA]: null, + [chainB]: 'boom', + }, + }; + + const nextState = coinCurrentPricesReducer( + seededState, + setCoinPriceChains([chainA]) + ); + + expect(nextState.byChain[chainA]?.['0xabc']?.price).toBe(100); + expect(nextState.byChain[chainB]).toBeUndefined(); + expect(nextState.statusByChain[chainA]).toBe('succeeded'); + expect(nextState.statusByChain[chainB]).toBeUndefined(); + expect(nextState.errorByChain[chainB]).toBeUndefined(); + }); + + it('handles thunk lifecycle actions for loading, success, and failure', () => { + const [chainA] = getTestChains(); + const baseState = coinCurrentPricesReducer(undefined, { type: '@@INIT' }); + const arg = { client: {} as any, chains: [chainA] }; + + const loadingState = coinCurrentPricesReducer( + baseState, + fetchCoinCurrentPrices.pending('req-1', arg) + ); + expect(loadingState.statusByChain[chainA]).toBe('loading'); + + const successState = coinCurrentPricesReducer( + loadingState, + fetchCoinCurrentPrices.fulfilled( + { + perChain: { + [chainA]: { + '0xabc': { + address: '0xAbC', + price: 123.45, + updatedAt: 111, + }, + }, + } as any, + fetchedAt: 111, + }, + 'req-1', + arg + ) + ); + + expect(successState.statusByChain[chainA]).toBe('succeeded'); + expect(successState.byChain[chainA]?.['0xabc']?.price).toBe(123.45); + expect(successState.lastUpdatedAt).toBe(111); + + const failedState = coinCurrentPricesReducer( + baseState, + fetchCoinCurrentPrices.rejected(new Error('network error'), 'req-2', arg) + ); + expect(failedState.statusByChain[chainA]).toBe('failed'); + expect(failedState.errorByChain[chainA]).toContain('network error'); + }); + + it('selectors return normalized lookup results and fallback status', () => { + const [chainA, chainB] = getTestChains(); + const state = { + currentPrices: { + byChain: { + [chainA]: { + '0xabc': { address: '0xAbC', price: 99, updatedAt: 1 }, + }, + }, + statusByChain: { + [chainA]: 'succeeded', + }, + errorByChain: {}, + lastUpdatedAt: 1, + polling: { isRunning: false, intervalMs: 30_000 }, + }, + } as any; + + expect(selectCoinPrice(chainA, '0xABC')(state)?.price).toBe(99); + expect(selectCoinPrice(chainA, '')(state)).toBeUndefined(); + expect(selectCoinChainStatus(chainB)(state)).toBe('idle'); + }); +}); diff --git a/src/features/coinData/coinCurrentPriceSlice.ts b/src/features/coinData/coinCurrentPriceSlice.ts index 60a2ad2..0f98cdf 100644 --- a/src/features/coinData/coinCurrentPriceSlice.ts +++ b/src/features/coinData/coinCurrentPriceSlice.ts @@ -12,8 +12,11 @@ import { import type { RootState, AppDispatch } from '../../app/store'; import { CURRENT_LIVE_FACTSHEETS } from '../documentation/factSheets/liveFactsheets'; -export const COIN_PRICE_CHAINS: GqlChain[] = - CURRENT_LIVE_FACTSHEETS.factsheets.map((f) => f.poolChain as GqlChain); +export const COIN_PRICE_CHAINS: GqlChain[] = Array.from( + new Set( + CURRENT_LIVE_FACTSHEETS.factsheets.map((f) => f.poolChain as GqlChain) + ) +); export interface CoinPriceEntry { address: string; @@ -83,7 +86,6 @@ export const fetchCoinCurrentPrices = createAsyncThunk< chains && chains.length > 0 ? chains : COIN_PRICE_CHAINS ).slice(); const fetchedAt = Date.now(); - console.log('fetching data'); const results = await Promise.all( usedChains.map(async (chain) => { const { data } = await client.query({ @@ -91,7 +93,6 @@ export const fetchCoinCurrentPrices = createAsyncThunk< variables: { chains: [chain] }, fetchPolicy: 'network-only', }); - console.log(data); const items = data?.tokenGetCurrentPrices ?? []; const mapForChain: Record = {}; @@ -110,7 +111,10 @@ export const fetchCoinCurrentPrices = createAsyncThunk< }) ); - const perChain: Record> = {} as any; + const perChain: Record< + GqlChain, + Record + > = {} as Record>; for (const { chain, mapForChain } of results) { perChain[chain] = mapForChain; } diff --git a/src/features/coinData/coinCurrentPricesPolling.tsx b/src/features/coinData/coinCurrentPricesPolling.tsx index a2e2750..bde276b 100644 --- a/src/features/coinData/coinCurrentPricesPolling.tsx +++ b/src/features/coinData/coinCurrentPricesPolling.tsx @@ -1,12 +1,14 @@ // CurrentPricePollingGate.tsx import { useEffect } from 'react'; -import { useDispatch } from 'react-redux'; -import type { AppDispatch } from '../../app/store'; -import { startCoinCurrentPricesPolling, stopCoinCurrentPricesPolling } from './coinCurrentPriceSlice'; +import { useAppDispatch } from '../../app/hooks'; import { apolloClient } from '../../queries/apolloClient'; +import { + startCoinCurrentPricesPolling, + stopCoinCurrentPricesPolling, +} from './coinCurrentPriceSlice'; export function CurrentPricePollingGate(): null { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); useEffect(() => { dispatch(startCoinCurrentPricesPolling(apolloClient, 30_000)); diff --git a/src/features/coinData/coinData.module.css b/src/features/coinData/coinData.module.css index 1763952..c90fb13 100644 --- a/src/features/coinData/coinData.module.css +++ b/src/features/coinData/coinData.module.css @@ -27,3 +27,22 @@ button.ag-side-button-button { .coinTabSection { padding: 10px; } + +.menuPanel { + width: 256px; + max-height: 500px; + overflow-y: auto; +} + +.gridWrapper { + width: 100%; + padding-left: 60px; + padding-right: 30px; +} + +.chartWrapper { + width: 100%; + padding-left: 60px; + padding-right: 30px; + padding-top: 20px; +} diff --git a/src/features/coinData/coinData.tsx b/src/features/coinData/coinData.tsx index b4eefca..47d8cda 100644 --- a/src/features/coinData/coinData.tsx +++ b/src/features/coinData/coinData.tsx @@ -61,7 +61,7 @@ export interface SeriesConfig { marker: Marker; } -export function CoinData() { +export default function CoinData() { const [currentCoin, setCurrentCoin] = useState('BTC'); const availableCoins = useAppSelector(selectAvailableCoins); const coinLoadStatus = useAppSelector(selectCoinLoadStatus); @@ -82,7 +82,9 @@ export function CoinData() { colId: 'open', headerName: 'open', valueGetter: (params: { data: CoinPrice }) => { - return params.data.open == 1 / 0 ? undefined : params.data.open; + return params.data.open === Number.POSITIVE_INFINITY + ? undefined + : params.data.open; }, filter: 'agNumberColumnFilter', type: ['numericColumn', 'nonEditableColumn'], @@ -92,7 +94,9 @@ export function CoinData() { colId: 'high', headerName: 'high', valueGetter: (params: { data: CoinPrice }) => { - return params.data.high == 1 / 0 ? undefined : params.data.high; + return params.data.high === Number.POSITIVE_INFINITY + ? undefined + : params.data.high; }, filter: 'agNumberColumnFilter', type: ['numericColumn', 'nonEditableColumn'], @@ -102,7 +106,9 @@ export function CoinData() { colId: 'low', headerName: 'low', valueGetter: (params: { data: CoinPrice }) => { - return params.data.low == 1 / 0 ? undefined : params.data.low; + return params.data.low === Number.POSITIVE_INFINITY + ? undefined + : params.data.low; }, filter: 'agNumberColumnFilter', type: ['numericColumn', 'nonEditableColumn'], @@ -226,23 +232,25 @@ export function CoinData() { const getCurrentPriceData = () => { const empty: CoinPrice[] = []; - if (currentCoin == '3mTbillDaily') { + if (currentCoin === '3mTbillDaily') { const tbillData = tBillPrices as TBillYield[]; const tBillCloseData: CoinPrice[] = tbillData.map((x) => { return { date: x.date, unix: new Date(x.date).getTime(), close: x.rate, - open: 1 / 0, - low: 1 / 0, - high: 1 / 0, + open: Number.POSITIVE_INFINITY, + low: Number.POSITIVE_INFINITY, + high: Number.POSITIVE_INFINITY, }; }); return tBillCloseData; } - const availableCoin = availableCoins.find((x) => x.coinCode == currentCoin); + const availableCoin = availableCoins.find( + (x) => x.coinCode === currentCoin + ); return availableCoin?.dailyPriceHistory ?? empty; }; @@ -286,6 +294,121 @@ export function CoinData() { } } + const CoinSelectionPanel = () => ( + + + { + setCurrentCoin(String(x.key)); + }} + activeKey={currentCoin} + /> + + ); + + const LoadingStatusPanel = () => ( + + ); + + const DataTablePanel = () => ( + + + + + + ); + + const PriceChartPanel = () => ( + + + + + + ); + return (
@@ -293,133 +416,11 @@ export function CoinData() { - - - { - setCurrentCoin(x.key); - }} - activeKey={currentCoin} - /> - + - - - - - - - - - - - + + + @@ -429,6 +430,5 @@ export function CoinData() {
); } -/* -*/ +export { CoinData }; diff --git a/src/features/coinData/loadPriceHistoryThunk.ts b/src/features/coinData/loadPriceHistoryThunk.ts new file mode 100644 index 0000000..3faf02b --- /dev/null +++ b/src/features/coinData/loadPriceHistoryThunk.ts @@ -0,0 +1,95 @@ +import { batch } from 'react-redux'; +import { AppThunk } from '../../app/store'; +import { coinPriceRetrievalService } from './coinPriceRetrievalService'; +import { + CoinComparison, + CoinPrice, +} from '../simulationRunConfiguration/simulationRunConfigModels'; +import { ReturnTimeStep } from '../simulationResults/simulationResultSummaryModels'; +import { + updateCoinLoadStatus, + updateCoinPriceHistory, + updateCoinPriceHistoryLoaded, + updateCoinPriceHistoryLoadedStatus, +} from '../simulationRunConfiguration/simulationRunConfigurationSlice'; + +interface LoadPriceHistoryOptions { + includePerCoinLoadStatus?: boolean; +} + +export const loadPriceHistoryAsync = + (options: LoadPriceHistoryOptions = {}): AppThunk => + async (dispatch, getState) => { + const { includePerCoinLoadStatus = false } = options; + + const { + availableCoins, + coinPriceHistoryLoaded, + coinPriceHistoryLoadedStatus, + } = getState().simConfig; + + if (coinPriceHistoryLoaded || coinPriceHistoryLoadedStatus !== 'pending') { + return; + } + + dispatch(updateCoinPriceHistoryLoadedStatus('loading')); + + const results = await Promise.all( + availableCoins.map(async (coin) => { + const dailyPriceHistory = (await dispatch( + coinPriceRetrievalService.endpoints.loadHistoricDailyPrices.initiate({ + coinCode: coin.coinCode, + }) + ) + .unwrap() + .catch(() => [])) as CoinPrice[]; + + if (includePerCoinLoadStatus) { + dispatch(updateCoinLoadStatus(`Daily ${coin.coinCode} prices `)); + } + + const dailyPriceHistoryMap = new Map(); + const dailyReturns = new Map(); + + dailyPriceHistory.forEach((pricePoint, index) => { + dailyPriceHistoryMap.set(pricePoint.unix, pricePoint); + const returnValue = + index === 0 + ? 0 + : pricePoint.close / dailyPriceHistory[index - 1].close - 1; + dailyReturns.set(pricePoint.unix, { + date: pricePoint.date, + unix: pricePoint.unix, + return: returnValue, + }); + }); + + return { + coin, + dailyPriceHistory, + dailyPriceHistoryMap, + dailyReturns, + }; + }) + ); + + batch(() => { + results.forEach( + ({ coin, dailyPriceHistory, dailyPriceHistoryMap, dailyReturns }) => { + dispatch( + updateCoinPriceHistory({ + coinCode: coin.coinCode, + coinName: coin.coinName, + dailyPriceHistory, + dailyPriceHistoryMap, + dailyReturns, + coinComparisons: new Map(), + deploymentByChain: coin.deploymentByChain, + }) + ); + } + ); + dispatch(updateCoinPriceHistoryLoaded(true)); + dispatch(updateCoinPriceHistoryLoadedStatus('loaded')); + }); + }; diff --git a/src/features/documentation/about.tsx b/src/features/documentation/about.tsx index 14fea90..791f9df 100644 --- a/src/features/documentation/about.tsx +++ b/src/features/documentation/about.tsx @@ -3,10 +3,11 @@ import { useState, useEffect } from 'react'; import { SimulationResultsSummaryStep } from '../simulationResults/simulationResultsSummaryStep'; import { getBreakdown, Pool } from '../../services/breakdownService'; import { SimulationRunBreakdown } from '../simulationResults/simulationResultSummaryModels'; +import styles from './documentation.module.css'; const { TabPane } = Tabs; -export function About() { +export default function About() { const [key, setKey] = useState('2'); const [breakdowns, setBreakdowns] = useState([]); const [loading, setLoading] = useState(false); @@ -69,7 +70,7 @@ export function About() { defaultActiveKey={key} key={key} onChange={(key) => setKey(key)} - style={{ paddingLeft: 20, paddingRight: 20 }} + className={styles.simViewTabs} > {loading ? ( @@ -117,3 +118,4 @@ export function About() { ); } +export { About }; diff --git a/src/features/documentation/basics/amm.tsx b/src/features/documentation/basics/amm.tsx index 506eb73..025aa44 100644 --- a/src/features/documentation/basics/amm.tsx +++ b/src/features/documentation/basics/amm.tsx @@ -1,12 +1,13 @@ import { Col, Row } from 'antd'; import { MathJax, MathJaxContext } from 'better-react-mathjax'; import { Fade } from 'react-awesome-reveal'; +import sharedStyles from '../documentation.module.css'; export function AMMDescription() { return ( - +

Simulating Automated Market Makers

diff --git a/src/features/documentation/basics/basics.module.css b/src/features/documentation/basics/basics.module.css new file mode 100644 index 0000000..759497d --- /dev/null +++ b/src/features/documentation/basics/basics.module.css @@ -0,0 +1,17 @@ +.comparisonTable { + border-collapse: collapse; + width: 100%; +} + +.tableCell { + padding: 5px; + border: 1px solid white; +} + +.noteBlock { + margin: 20px 0; + padding: 15px 20px; + border-left: 3px solid rgba(255, 255, 255, 0.3); + background: rgba(255, 255, 255, 0.03); + font-size: 0.95em; +} diff --git a/src/features/documentation/basics/impermanentLoss.tsx b/src/features/documentation/basics/impermanentLoss.tsx index 01b8662..bf15f06 100644 --- a/src/features/documentation/basics/impermanentLoss.tsx +++ b/src/features/documentation/basics/impermanentLoss.tsx @@ -1,6 +1,7 @@ import { Col, Row } from 'antd'; import { MathJax, MathJaxContext } from 'better-react-mathjax'; import { Fade } from 'react-awesome-reveal'; +import sharedStyles from '../documentation.module.css'; export function ImpermanentLoss() { return ( @@ -8,7 +9,7 @@ export function ImpermanentLoss() { - +

Fixed Weight DEXes must suffer Impermanent Loss

diff --git a/src/features/documentation/basics/rebalancingvsRebalancing.tsx b/src/features/documentation/basics/rebalancingvsRebalancing.tsx index b2ff88b..d7ff491 100644 --- a/src/features/documentation/basics/rebalancingvsRebalancing.tsx +++ b/src/features/documentation/basics/rebalancingvsRebalancing.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { MathJax, MathJaxContext } from 'better-react-mathjax'; import { Fade } from 'react-awesome-reveal'; import { Col, Row } from 'antd'; +import styles from './basics.module.css'; const RebalancingVsRebalancing: React.FC = () => { return ( @@ -32,93 +33,45 @@ const RebalancingVsRebalancing: React.FC = () => { The following table compares the core features of LVR, its fee extended model ARB and RVR:

- +
- - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + +
- Model Features - - LVR - - ARB - - RVR - Model FeaturesLVRARBRVR
- CEX Spread - - 0 - - 0 - - TradFi model - CEX Spread00TradFi model
- CEX Fees - - 0 - - 0 - - Fee Present - CEX Fees00Fee Present
- AMM Fees - - 0 - - Fee Present - - Fee Present - AMM Fees0Fee PresentFee Present
- AMM Gas Cost - - 0 - - 0 - - Fixed Costs - AMM Gas Cost00Fixed Costs
- AMM tokens - - 2 - - 2 - - N - AMM tokens22N
@@ -213,15 +166,7 @@ const RebalancingVsRebalancing: React.FC = () => { region beyond pure fee considerations.

-
+
Note: RVR assumes that the market provides infinite liquidity at the bid and ask prices, i.e. RVR maintains the assumption from LVR that there is no slippage. This means diff --git a/src/features/documentation/company.tsx b/src/features/documentation/company.tsx index 4669883..e93c15d 100644 --- a/src/features/documentation/company.tsx +++ b/src/features/documentation/company.tsx @@ -1,18 +1,19 @@ -import { Card, Grid, Timeline, Typography } from "antd"; -import { VisionOverview } from "./landing/desktop/visionOverview"; +import { Card, Grid, Timeline, Typography } from 'antd'; +import { VisionOverview } from './landing/desktop/visionOverview'; +import styles from './documentation.module.css'; const { Title } = Typography; const { useBreakpoint } = Grid; -export function CompanyPage() { +export default function CompanyPage() { const screens = useBreakpoint(); const isMobile = !screens.lg && !screens.xl && !screens.xxl; - return ( - isMobile ?
+ return isMobile ? ( +
{/* Company Section */} -
+
Our Vision

At QuantAMM, our vision is to build a passive fund product that @@ -21,7 +22,7 @@ export function CompanyPage() { infrastructure on-chain.

- + <Title level={3} className={styles.companySectionTitle}> Our Team {[ @@ -43,12 +44,12 @@ export function CompanyPage() { {founder.name}

{founder.role} @@ -58,15 +59,7 @@ export function CompanyPage() { ))} {/* Updated Company Images */} -

+
{[ '8vc.png', '369.png', @@ -83,14 +76,14 @@ export function CompanyPage() { key={index} src={`/companies/${img}`} alt={img} - style={{ width: '15%', height: 'auto' }} + className={styles.companyPartnerImage} /> ))}
{/* Timeline */} -
+
Our Journey H1 2023 - Simulator Build @@ -105,6 +98,9 @@ export function CompanyPage() { May 2025 - QuantAMM Launches BTF -
: - ); - } \ No newline at end of file +
+
+ ) : ( + + ); +} diff --git a/src/features/documentation/documentation.module.css b/src/features/documentation/documentation.module.css index f871eeb..026b695 100644 --- a/src/features/documentation/documentation.module.css +++ b/src/features/documentation/documentation.module.css @@ -6,3 +6,109 @@ .descriptionBold { color: rgb(1, 105, 1); } + +.pad20 { + padding: 20px; +} + +.formItemTop5 { + margin-top: 5px; +} + +.secondaryText { + color: var(--secondary-text-color); +} + +.simViewTabs { + padding-left: 20px; + padding-right: 20px; +} + +.companySection { + padding: 20px; +} + +.companySectionTitle { + margin-top: 20px; +} + +.companyFounderCard { + margin-bottom: 10px; +} + +.companyFounderImage { + width: 100px; + border-radius: 50%; +} + +.companyPartnersWrap { + display: flex; + justify-content: center; + flex-wrap: wrap; + gap: 10px; + margin-top: 20px; +} + +.companyPartnerImage { + width: 15%; + height: auto; +} + +.fontWeightBold { + font-weight: bold; +} + +.researchButtonColMobile { + display: flex; + align-items: center; + justify-content: center; +} + +.researchDesktopContainer { + padding: 10px; +} + +.researchDesktopLeftCol { + padding: 10px; +} + +.researchDesktopMiddleCol { + padding: 10px; +} + +.researchDesktopButtonCol { + padding: 10px; + display: flex !important; + align-items: center !important; + justify-content: center; + height: 100%; +} + +.documentationMenuCol { + max-height: 95vh; + overflow-y: auto; +} + +.documentationMenuButton { + margin: 10px; + margin-top: 15px; + margin-bottom: 15px; + width: 80%; +} + +.documentationContentCol { + padding-bottom: 40px; +} + +.simGuideParagraph { + margin-bottom: 20px; +} + +.simGuideListItem { + margin-bottom: 20px; +} + +.simGuideSubList { + margin-top: 10px; + margin-bottom: 10px; +} diff --git a/src/features/documentation/documentation.tsx b/src/features/documentation/documentation.tsx index d30db4c..10770ea 100644 --- a/src/features/documentation/documentation.tsx +++ b/src/features/documentation/documentation.tsx @@ -22,6 +22,7 @@ import { BalancerPoolDescription } from './poolTypes/balancer'; import AMMDescription from './basics/amm'; import RebalancingVsRebalancing from './basics/rebalancingvsRebalancing'; import { useParams } from 'react-router-dom'; +import styles from './documentation.module.css'; const items = [ { @@ -127,13 +128,13 @@ const submenuKeys = [ 'MinVariance', ]; -export function Documentation() { +export default function Documentation() { const { id } = useParams(); const [openKeys, setOpenKeys] = useState(rootSubmenuKeys); const [current, updateCurrent] = useState(id ? id : 'AutomatedMarketMakers'); const onClick = (e: any) => { - if (submenuKeys.indexOf(e.key) != -1) { + if (submenuKeys.indexOf(e.key) !== -1) { updateCurrent(e.key); } }; @@ -173,15 +174,12 @@ export function Documentation() { return (
- + @@ -192,7 +190,7 @@ export function Documentation() { '_blank' ) } - style={{ marginTop: '15px', marginBottom: '15px', width: '80%', margin:'10px' }} + className={styles.documentationMenuButton} > Open Protocol Contracts GitBook @@ -206,10 +204,12 @@ export function Documentation() { defaultValue={current} /> - + {components[current]}
); } + +export { Documentation }; diff --git a/src/features/documentation/documentationSlice.test.ts b/src/features/documentation/documentationSlice.test.ts new file mode 100644 index 0000000..87f8d76 --- /dev/null +++ b/src/features/documentation/documentationSlice.test.ts @@ -0,0 +1,95 @@ +import { describe, expect, it } from 'vitest'; +import { + selectBasket, + selectEndDate, + selectStartDate, + selectUpdateRuleTrainingFilter, + setBasket, + setEndDate, + setStartDate, + setTrainingResult, + setUpdateRuleTrainingLoadState, +} from './documentationSlice'; +import documentationReducer from './documentationSlice'; + +describe('documentationSlice view-model logic', () => { + it('updates individual training filter fields through reducers', () => { + const baseState = documentationReducer(undefined, { type: '@@INIT' }); + + const withBasket = documentationReducer(baseState, setBasket('macro')); + const withStartDate = documentationReducer( + withBasket, + setStartDate('2024-01-01') + ); + const withEndDate = documentationReducer( + withStartDate, + setEndDate('2024-06-30') + ); + const withLoading = documentationReducer( + withEndDate, + setUpdateRuleTrainingLoadState(true) + ); + + expect(withLoading.updateRuleTrainingFilter.basket).toBe('macro'); + expect(withLoading.updateRuleTrainingFilter.startDate).toBe('2024-01-01'); + expect(withLoading.updateRuleTrainingFilter.endDate).toBe('2024-06-30'); + expect(withLoading.updateRuleTrainingLoadState).toBe(true); + }); + + it('stores the full training result payload', () => { + const baseState = documentationReducer(undefined, { type: '@@INIT' }); + const trainingResult = { + batch_size: 16, + n_parameter_sets: 42, + decay_lr_plateau: 3, + decay_lr_ratio: '0.5', + optimiser: 'adam', + objective: [[1], [2], [3]], + train_objective: [[0.8], [0.9]], + test_objective: [[0.7], [0.75]], + }; + + const nextState = documentationReducer( + baseState, + setTrainingResult(trainingResult) + ); + + expect(nextState.updateRuleTrainingResults).toEqual(trainingResult); + }); + + it('selectors map the docs state shape correctly', () => { + const state = { + docs: { + updateRuleTrainingLoadState: false, + updateRuleTrainingFilter: { + basket: 'btc', + updateRule: '', + parameterType: '', + startDate: '2024-01-01', + endDate: '2024-12-31', + trainingWindow: '', + memorySettings: '', + tradingFunction: '', + strategyRefresh: '', + }, + updateRuleTrainingResults: { + batch_size: 0, + n_parameter_sets: 0, + decay_lr_plateau: 0, + decay_lr_ratio: '', + optimiser: '', + objective: [], + train_objective: [], + test_objective: [], + }, + }, + } as any; + + expect(selectBasket(state)).toBe('btc'); + expect(selectStartDate(state)).toBe('2024-01-01'); + expect(selectEndDate(state)).toBe('2024-12-31'); + expect(selectUpdateRuleTrainingFilter(state)).toEqual( + state.docs.updateRuleTrainingFilter + ); + }); +}); diff --git a/src/features/documentation/documentationSlice.ts b/src/features/documentation/documentationSlice.ts index 751ec77..eff229a 100644 --- a/src/features/documentation/documentationSlice.ts +++ b/src/features/documentation/documentationSlice.ts @@ -69,13 +69,13 @@ export const documentationSlice = createSlice({ state.updateRuleTrainingFilter.startDate = action.payload; }, setTrainingWindow: (state, action: PayloadAction) => { - state.updateRuleTrainingFilter.endDate = action.payload; + state.updateRuleTrainingFilter.trainingWindow = action.payload; }, setMemorySettings: (state, action: PayloadAction) => { state.updateRuleTrainingFilter.memorySettings = action.payload; }, setTradingFunction: (state, action: PayloadAction) => { - state.updateRuleTrainingFilter.endDate = action.payload; + state.updateRuleTrainingFilter.tradingFunction = action.payload; }, setStrategyRefresh: (state, action: PayloadAction) => { state.updateRuleTrainingFilter.strategyRefresh = action.payload; diff --git a/src/features/documentation/factSheets/arbitrumMacro/arbitrumMacro.tsx b/src/features/documentation/factSheets/arbitrumMacro/arbitrumMacro.tsx index d594b1d..5116808 100644 --- a/src/features/documentation/factSheets/arbitrumMacro/arbitrumMacro.tsx +++ b/src/features/documentation/factSheets/arbitrumMacro/arbitrumMacro.tsx @@ -11,7 +11,11 @@ export default function ArbitrumMacroFactSheet() { return ( <> - {isMobile ? : } + {isMobile ? ( + + ) : ( + + )} ); } diff --git a/src/features/documentation/factSheets/arbitrumMacro/arbitrumMacroFactsheetData.tsx b/src/features/documentation/factSheets/arbitrumMacro/arbitrumMacroFactsheetData.tsx index 3f5f547..bb1a567 100644 --- a/src/features/documentation/factSheets/arbitrumMacro/arbitrumMacroFactsheetData.tsx +++ b/src/features/documentation/factSheets/arbitrumMacro/arbitrumMacroFactsheetData.tsx @@ -4,7 +4,7 @@ import { ROUTES } from '../../../../routesEnum'; export const arbitrumMacroFactsheetData: FactsheetModel = { poolId: ROUTES.ARBITRUMMACROFACTSHEET, - inceptionLpPrice:3023, + inceptionLpPrice: 3023, poolChain: 'ARBITRUM', pools: [ 'arbitrumMacroBTFAugTrain', @@ -22,7 +22,8 @@ export const arbitrumMacroFactsheetData: FactsheetModel = { width: '30%', alt: 'ARBITRUM MACRO BTF Icon', }, - objective: 'The Arbitrum Macro BTF provides exposure to some of the mega cap tokens on Arbitrum. The BTF was trained on more bullish market conditions.', + objective: + 'The Arbitrum Macro BTF provides exposure to some of the mega cap tokens on Arbitrum. The BTF was trained on more bullish market conditions.', deploymentLinks: { contractLinks: [ [ @@ -153,7 +154,8 @@ export const arbitrumMacroFactsheetData: FactsheetModel = {

ARB is the Arbitrum native token. While this token carries potential - protocol risk and higher volatility, it is key token of the Arbitrum ecosystem. + protocol risk and higher volatility, it is key token of the Arbitrum + ecosystem.

), @@ -244,9 +246,9 @@ export const arbitrumMacroFactsheetData: FactsheetModel = { trainingDescription: ( <>

- The channel following strategy has parameters that determine how aggressive - a strategy re-weights to different assets as well as the memory of - prices that get taken into account. + The channel following strategy has parameters that determine how + aggressive a strategy re-weights to different assets as well as the + memory of prices that get taken into account.

A training period of Jan 2023 - March 2025 was selected and parameters @@ -255,10 +257,10 @@ export const arbitrumMacroFactsheetData: FactsheetModel = { QuantAMM simulator framework. A parameter set was selected that maximised the Sharpe Ratio of the strategy. This was selected over other objectives such as maximising Ulcer or Calmar Ratios as the parameter - set showed better test set statistics. Random 73-day length windows - were selected within the training price range and optimisation was - performed via stochastic gradient descent for 6000 steps with batches of - 6 windows per step. + set showed better test set statistics. Random 73-day length windows were + selected within the training price range and optimisation was performed + via stochastic gradient descent for 6000 steps with batches of 6 windows + per step.

), @@ -269,7 +271,8 @@ export const arbitrumMacroFactsheetData: FactsheetModel = { variations: [ { name: 'Memory Days', - tooltip: 'Memory days is the number of days of prices used in the strategy.', + tooltip: + 'Memory days is the number of days of prices used in the strategy.', value: [ 'ARB - 0.0225783657', 'BTC - 44.0194313', @@ -284,7 +287,8 @@ export const arbitrumMacroFactsheetData: FactsheetModel = { variations: [ { name: 'Lambda', - tooltip: 'Lambda is the parameter used in the gradient estimators for the channel following.', + tooltip: + 'Lambda is the parameter used in the gradient estimators for the channel following.', value: [ 'ARB - 0.0000002397926992946897', 'BTC - 0.9197115276049956', @@ -299,7 +303,8 @@ export const arbitrumMacroFactsheetData: FactsheetModel = { variations: [ { name: 'k', - tooltip: 'k is the on-chain value stored in the contracts and is the exact parameter used in the strategy calculations.', + tooltip: + 'k is the on-chain value stored in the contracts and is the exact parameter used in the strategy calculations.', value: [ 'ARB - 0.0000095013382493053445', 'BTC - 150654832.73345056', @@ -314,7 +319,8 @@ export const arbitrumMacroFactsheetData: FactsheetModel = { variations: [ { name: 'Exponent', - tooltip: 'The exponent is a variable used in the channel following strategy that dictates how big a price change has to be before the strategy starts to notice it.', + tooltip: + 'The exponent is a variable used in the channel following strategy that dictates how big a price change has to be before the strategy starts to notice it.', value: [ 'ARB - 0.2256153584663592', 'BTC - 10.6676706071348804', @@ -329,7 +335,8 @@ export const arbitrumMacroFactsheetData: FactsheetModel = { variations: [ { name: 'Width', - tooltip: 'Width is the parameter that defines the channel width for the mean reversion strategy.', + tooltip: + 'Width is the parameter that defines the channel width for the mean reversion strategy.', value: [ 'ARB - 0.000002022137814235886', 'BTC - 12.633082257097033', @@ -344,7 +351,8 @@ export const arbitrumMacroFactsheetData: FactsheetModel = { variations: [ { name: 'Amplitude', - tooltip: 'Amplitude is the parameter that defines the oscillation amplitude for the mean reversion strategy.', + tooltip: + 'Amplitude is the parameter that defines the oscillation amplitude for the mean reversion strategy.', value: [ 'ARB - 0.0000000053211805246145698', 'BTC - 604.39416486194204', @@ -359,7 +367,8 @@ export const arbitrumMacroFactsheetData: FactsheetModel = { variations: [ { name: 'Pre-exp Scaling', - tooltip: 'Pre-exp scaling is the parameter used for scaling before exponentiation in the strategy calculations.', + tooltip: + 'Pre-exp scaling is the parameter used for scaling before exponentiation in the strategy calculations.', value: [ 'ARB - 71.02225192700019887', 'BTC - 52117.4385457861571922', @@ -371,16 +380,19 @@ export const arbitrumMacroFactsheetData: FactsheetModel = { }, ], iconTitle: 'Arbitrum Macro', - iconDescription: ['The arbitrum ecosystem basket', 'Mega Caps with Yield Focus'], + iconDescription: [ + 'The arbitrum ecosystem basket', + 'Mega Caps with Yield Focus', + ], status: 'LIVE', iconOpacity: 1, iconFocus: true, depositorBadges: { - prefix:'Safe_Haven_', - gold:1748213999, - silver:1749423599, - bronze:1750633199 + prefix: 'Safe_Haven_', + gold: 1748213999, + silver: 1749423599, + bronze: 1750633199, }, - targetPoolJson:'safeHavenBTFAugTest', - launchUnixTimestamp:1747267200 + targetPoolJson: 'safeHavenBTFAugTest', + launchUnixTimestamp: 1747267200, }; diff --git a/src/features/documentation/factSheets/arbitrumMacro/arbitrumMacroSimView.tsx b/src/features/documentation/factSheets/arbitrumMacro/arbitrumMacroSimView.tsx index 4ec6de4..c7b5411 100644 --- a/src/features/documentation/factSheets/arbitrumMacro/arbitrumMacroSimView.tsx +++ b/src/features/documentation/factSheets/arbitrumMacro/arbitrumMacroSimView.tsx @@ -3,10 +3,11 @@ import { useState, useEffect } from 'react'; import { SimulationResultsSummaryStep } from '../../../simulationResults/simulationResultsSummaryStep'; import { getBreakdown, Pool } from '../../../../services/breakdownService'; import { SimulationRunBreakdown } from '../../../simulationResults/simulationResultSummaryModels'; +import sharedStyles from '../../documentation.module.css'; const { TabPane } = Tabs; -export function ArbitrumMacroSimulatorExample() { +export default function ArbitrumMacroSimulatorExample() { const [key, setKey] = useState('2'); const [breakdowns, setBreakdowns] = useState([]); const [loading, setLoading] = useState(false); @@ -30,19 +31,19 @@ export function ArbitrumMacroSimulatorExample() { poolNames = [ 'arbitrumMacroBTFAugTrainFull', 'arbitrumMacroCFMMAugTrainFull', - 'arbitrumMacroHodlAugTrainFull' + 'arbitrumMacroHodlAugTrainFull', ]; } else if (key === '2') { poolNames = [ 'arbitrumMacroCFMMAugTestFull', 'arbitrumMacroBTFAugTestFull', - 'arbitrumMacroHodlAugTestFull' + 'arbitrumMacroHodlAugTestFull', ]; } else if (key === '3') { poolNames = [ 'arbitrumMacroBTF2025TestFull', 'arbitrumMacroCFMM2025TestFull', - 'arbitrumMacroHodl2025TestFull' + 'arbitrumMacroHodl2025TestFull', ]; } @@ -55,15 +56,15 @@ export function ArbitrumMacroSimulatorExample() { }); // Trigger loading of breakdowns }, [key]); // Dependency array ensures that effect runs when `key` changes - const seriesName= { + const seriesName = { 'Power Channel': '#c7b283', 'Balancer Weighted': '#528aae', HODL: '#52ad80', - } - const seriesStrokeColor= { + }; + const seriesStrokeColor = { 'Power Channel': 'ARBITRUM MACRO BTF', 'Balancer Weighted': 'Traditional DEX', - } + }; return (
@@ -72,7 +73,7 @@ export function ArbitrumMacroSimulatorExample() { defaultActiveKey={key} key={key} onChange={(key) => setKey(key)} - style={{ paddingLeft: 20, paddingRight: 20 }} + className={sharedStyles.simViewTabs} > {loading ? ( @@ -81,32 +82,38 @@ export function ArbitrumMacroSimulatorExample() { )} - + {loading ? ( ) : ( )} - + {loading ? ( ) : ( )} @@ -116,3 +123,5 @@ export function ArbitrumMacroSimulatorExample() {
); } + +export { ArbitrumMacroSimulatorExample }; diff --git a/src/features/documentation/factSheets/baseMacro/baseMacro.tsx b/src/features/documentation/factSheets/baseMacro/baseMacro.tsx index e142b29..5cca61f 100644 --- a/src/features/documentation/factSheets/baseMacro/baseMacro.tsx +++ b/src/features/documentation/factSheets/baseMacro/baseMacro.tsx @@ -11,7 +11,11 @@ export default function BaseMacroFactSheet() { return ( <> - {isMobile ? : } + {isMobile ? ( + + ) : ( + + )} ); } diff --git a/src/features/documentation/factSheets/baseMacro/baseMacroFactsheetData.tsx b/src/features/documentation/factSheets/baseMacro/baseMacroFactsheetData.tsx index 1035b5f..7a9230f 100644 --- a/src/features/documentation/factSheets/baseMacro/baseMacroFactsheetData.tsx +++ b/src/features/documentation/factSheets/baseMacro/baseMacroFactsheetData.tsx @@ -4,7 +4,7 @@ import { ROUTES } from '../../../../routesEnum'; export const baseMacroFactsheetData: FactsheetModel = { poolId: ROUTES.BASEMACROFACTSHEET, - inceptionLpPrice:29310.05, + inceptionLpPrice: 29310.05, poolChain: 'BASE', pools: [ 'baseMacroBTFAugTest', @@ -23,12 +23,13 @@ export const baseMacroFactsheetData: FactsheetModel = { alt: 'BASE MACRO BTF Icon', }, depositorBadges: { - prefix:'Base_Macro_', - gold:1749423599, - silver:1750633199, - bronze:1751756400 + prefix: 'Base_Macro_', + gold: 1749423599, + silver: 1750633199, + bronze: 1751756400, }, - objective: 'The Base Macro BTF provides exposure to some of the mega-cap tokens on Base. The BTF was trained on more bullish market conditions.', + objective: + 'The Base Macro BTF provides exposure to some of the mega-cap tokens on Base. The BTF was trained on more bullish market conditions.', deploymentLinks: { contractLinks: [ [ @@ -207,10 +208,10 @@ export const baseMacroFactsheetData: FactsheetModel = {

Balancer V3 has modern features such as pausing a pool to mitigate - this; however, a loss in such a case is dependent on the timing of any - intervention. If a pool is paused or in a recovery state, you can - still withdraw the underlying assets at a proportional quantity to - your LP tokens. + this; however, a loss in such a case is dependent on the timing of + any intervention. If a pool is paused or in a recovery state, you + can still withdraw the underlying assets at a proportional quantity + to your LP tokens.

), @@ -268,10 +269,10 @@ export const baseMacroFactsheetData: FactsheetModel = { QuantAMM simulator framework. A parameter set was selected that maximized the Sharpe Ratio of the strategy. This was selected over other objectives such as maximizing Ulcer or Calmar Ratios as the parameter - set showed better test set statistics. Random 73-day length windows - were selected within the training price range, and optimization was - performed via stochastic gradient descent for 6000 steps with batches of - 6 windows per step. + set showed better test set statistics. Random 73-day length windows were + selected within the training price range, and optimization was performed + via stochastic gradient descent for 6000 steps with batches of 6 windows + per step.

), @@ -281,12 +282,14 @@ export const baseMacroFactsheetData: FactsheetModel = { variations: [ { name: 'Guard Rails', - tooltip: 'Absolute minimum weight guard rails of 10% and 3% were tested. The final guard rail chosen was 3%.', + tooltip: + 'Absolute minimum weight guard rails of 10% and 3% were tested. The final guard rail chosen was 3%.', value: ['3%'], }, { name: 'Speed Limit', - tooltip: 'The speed limit weights can change in one day (epsilon max) was selected to be 0.432. The speed limit is tied to a maximum trade size of 10% of pool constituent reserves.', + tooltip: + 'The speed limit weights can change in one day (epsilon max) was selected to be 0.432. The speed limit is tied to a maximum trade size of 10% of pool constituent reserves.', value: ['0.432'], }, ], @@ -296,7 +299,8 @@ export const baseMacroFactsheetData: FactsheetModel = { variations: [ { name: 'Lambda', - tooltip: 'Lambda is the parameter used in the gradient estimators for the power channel. This is the on-chain value stored in the contracts.', + tooltip: + 'Lambda is the parameter used in the gradient estimators for the power channel. This is the on-chain value stored in the contracts.', value: [ 'cbBTC - 0.9784309018144351', 'AERO - 0.9925922273835435', @@ -306,7 +310,8 @@ export const baseMacroFactsheetData: FactsheetModel = { }, { name: 'Memory Days', - tooltip: 'Memory days is a conversion of the lambda setting to a more understandable unit of the number of days of prices used in the strategy.', + tooltip: + 'Memory days is a conversion of the lambda setting to a more understandable unit of the number of days of prices used in the strategy.', value: [ 'cbBTC - 167.272730', 'AERO - 365.0', @@ -321,7 +326,8 @@ export const baseMacroFactsheetData: FactsheetModel = { variations: [ { name: 'Aggressiveness', - tooltip: 'Otherwise known as k_per_day. This is the multiplier applied to the strategy signal to get the weight change per day.', + tooltip: + 'Otherwise known as k_per_day. This is the multiplier applied to the strategy signal to get the weight change per day.', value: [ 'cbBTC - 5.608611948', 'AERO - 43.81829052', @@ -331,7 +337,8 @@ export const baseMacroFactsheetData: FactsheetModel = { }, { name: 'k', - tooltip: 'k is the on-chain value stored in the contracts and is the exact parameter used in the strategy calculations.', + tooltip: + 'k is the on-chain value stored in the contracts and is the exact parameter used in the strategy calculations.', value: [ 'cbBTC - 938.167832', 'AERO - 15993.676043', @@ -346,7 +353,8 @@ export const baseMacroFactsheetData: FactsheetModel = { variations: [ { name: 'Exponent', - tooltip: 'The exponent is a variable used in the power channel strategy that dictates how big a price change has to be before the strategy starts to notice it. It is the primary difference between other strategies like momentum.', + tooltip: + 'The exponent is a variable used in the power channel strategy that dictates how big a price change has to be before the strategy starts to notice it. It is the primary difference between other strategies like momentum.', value: [ 'cbBTC - 1', 'AERO - 2.4705463110202333', @@ -359,12 +367,12 @@ export const baseMacroFactsheetData: FactsheetModel = { ], iconTitle: 'Base Macro', iconDescription: [ - 'Base is a pivotal DeFi L2', - 'A BTF with key Base mega caps', - ], + 'Base is a pivotal DeFi L2', + 'A BTF with key Base mega caps', + ], status: 'LIVE', iconOpacity: 1, iconFocus: true, - targetPoolJson:'baseMacroBTFAugTest', - launchUnixTimestamp:undefined + targetPoolJson: 'baseMacroBTFAugTest', + launchUnixTimestamp: undefined, }; diff --git a/src/features/documentation/factSheets/baseMacro/baseMacroSimView.tsx b/src/features/documentation/factSheets/baseMacro/baseMacroSimView.tsx index 108b0b5..5dd05f8 100644 --- a/src/features/documentation/factSheets/baseMacro/baseMacroSimView.tsx +++ b/src/features/documentation/factSheets/baseMacro/baseMacroSimView.tsx @@ -3,10 +3,11 @@ import { useState, useEffect } from 'react'; import { SimulationResultsSummaryStep } from '../../../simulationResults/simulationResultsSummaryStep'; import { getBreakdown, Pool } from '../../../../services/breakdownService'; import { SimulationRunBreakdown } from '../../../simulationResults/simulationResultSummaryModels'; +import sharedStyles from '../../documentation.module.css'; const { TabPane } = Tabs; -export function BaseMacroSimulatorExample() { +export default function BaseMacroSimulatorExample() { const [key, setKey] = useState('2'); const [breakdowns, setBreakdowns] = useState([]); const [loading, setLoading] = useState(false); @@ -30,19 +31,19 @@ export function BaseMacroSimulatorExample() { poolNames = [ 'baseMacroBTFAugTrainFull', 'baseMacroCFMMAugTrainFull', - 'baseMacroHodlAugTrainFull' + 'baseMacroHodlAugTrainFull', ]; } else if (key === '2') { poolNames = [ 'baseMacroCFMMAugTestFull', 'baseMacroBTFAugTestFull', - 'baseMacroHodlAugTestFull' + 'baseMacroHodlAugTestFull', ]; } else if (key === '3') { poolNames = [ 'baseMacroBTF2025TestFull', 'baseMacroCFMM2025TestFull', - 'baseMacroHodl2025TestFull' + 'baseMacroHodl2025TestFull', ]; } @@ -55,15 +56,15 @@ export function BaseMacroSimulatorExample() { }); // Trigger loading of breakdowns }, [key]); // Dependency array ensures that effect runs when `key` changes - const seriesName= { + const seriesName = { 'Power Channel': '#c7b283', 'Balancer Weighted': '#528aae', HODL: '#52ad80', - } - const seriesStrokeColor= { + }; + const seriesStrokeColor = { 'Power Channel': 'BASE MACRO BTF', 'Balancer Weighted': 'Traditional DEX', - } + }; return (
@@ -72,7 +73,7 @@ export function BaseMacroSimulatorExample() { defaultActiveKey={key} key={key} onChange={(key) => setKey(key)} - style={{ paddingLeft: 20, paddingRight: 20 }} + className={sharedStyles.simViewTabs} > {loading ? ( @@ -81,32 +82,38 @@ export function BaseMacroSimulatorExample() { )} - + {loading ? ( ) : ( )} - + {loading ? ( ) : ( )} @@ -116,3 +123,5 @@ export function BaseMacroSimulatorExample() {
); } + +export { BaseMacroSimulatorExample }; diff --git a/src/features/documentation/factSheets/desktop/factsheetDesktop.tsx b/src/features/documentation/factSheets/desktop/factsheetDesktop.tsx index 888f076..595210e 100644 --- a/src/features/documentation/factSheets/desktop/factsheetDesktop.tsx +++ b/src/features/documentation/factSheets/desktop/factsheetDesktop.tsx @@ -14,11 +14,57 @@ import { selectTheme } from '../../../themes/themeSlice'; import { FactsheetModel } from '../../landing/desktop/factsheetModel'; import { FAQItems } from '../../landing/faqItems'; import ButtonGroup from 'antd/es/button/button-group'; +import styles from '../factsheetDesktop.module.css'; interface FactsheetDesktopProps { model: FactsheetModel; } +function FactsheetHeroObjectiveSection({ model }: FactsheetDesktopProps) { + return ( + + + +
+ {model.factsheetImage.alt} +

{model.mainTitle}

+

{model.mainDescription}

+
+ + + +

BTF Objective

+

{model.objective}

+

Responsive Strategy Objective

+

+ The BTF structure allows this to be done in a feeless manner for the + LP with continuous on-chain rebalancing rather than the traditional + monthly or quarterly rebalances. Re-weighting is performed daily. +

+

+ BTFs also augment returns with swap fees associated with providing a + decentralised liquidity pool and provide an ERC20 token that can be + used in other DeFi applications. +

+

+ It is crucial to recognize that BTFs carry risks. Reallocation + strategies are not market-neutral and involve directional assumptions + about asset allocation. Furthermore, the value of assets can be + affected by macro economic factors and global events. +

+ + +
+ ); +} + export function FactSheetDesktop(props: FactsheetDesktopProps) { const [breakdowns, setBreakdowns] = useState< Record @@ -95,10 +141,13 @@ export function FactSheetDesktop(props: FactsheetDesktopProps) { {props.model.defaultPeriod[1]} - {props.model.alternatePeriod[0] != '' ? - - {props.model.alternatePeriod[1]} - : <>} + {props.model.alternatePeriod[0] !== '' ? ( + + {props.model.alternatePeriod[1]} + + ) : ( + <> + )} ); @@ -112,117 +161,55 @@ export function FactSheetDesktop(props: FactsheetDesktopProps) { return (
- - - -
- {props.model.factsheetImage.alt} -

- {props.model.mainTitle} -

-

{props.model.mainDescription}

-
- - - -

BTF Objective

-

{props.model.objective}

-

Responsive Strategy Objective

-

- The BTF structure allows this to be done in a feeless manner for the - LP with continuous on-chain rebalancing rather than the traditional - monthly or quarterly rebalances. Re-weighting is performed daily. -

-

- BTFs also augment returns with swap fees associated with providing a - decentralised liquidity pool and provide an ERC20 token that can be - used in other DeFi applications. -

-

- It is crucial to recognize that BTFs carry risks. Reallocation - strategies are not market-neutral and involve directional - assumptions about asset allocation. Furthermore, the value of assets - can be affected by macro economic factors and global events. -

- - -
+ -

OVERVIEW

+

OVERVIEW

- + - + +
GENERAL DETAILS - - + +
} - style={{ height: '100%' }} + className={styles.cardHeightFull} > -
- Deployment Links -
+
Deployment Links
{props.model.deploymentLinks.contractLinks.map( (link, index) => { @@ -230,7 +217,7 @@ export function FactSheetDesktop(props: FactsheetDesktopProps) { - ))} - - - ))} -
-
- + {parameter.name} + + } + > + + {parameter.variations.map((variation, variationIndex) => ( + + +

+ {variation.name}:{' '} +

+ {variation.value.map((val, valIndex) => ( + + ))} +
+ + ))} +
+ + ))}
- +
diff --git a/src/features/documentation/factSheets/factsheetDesktop.module.css b/src/features/documentation/factSheets/factsheetDesktop.module.css new file mode 100644 index 0000000..7a8b16d --- /dev/null +++ b/src/features/documentation/factSheets/factsheetDesktop.module.css @@ -0,0 +1,145 @@ +.centeredHero { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 100%; +} + +.heroImage { + height: auto; +} + +.heroTitle { + text-align: center; + margin: 0; +} + +.heroDescription { + text-align: center; +} + +.sectionTitle { + margin-left: 10px; +} + +.rowHeight52 { + height: 52vh; +} + +.rowHeight75 { + height: 75vh; +} + +.rowHeight80 { + height: 80vh; +} + +.rowHeight110 { + height: 110vh; +} + +.rowHeight130 { + height: 130vh; +} + +.rowMarginTop5 { + margin-top: 5px; +} + +.fullHeight { + height: 100%; +} + +.cardHeightFull { + height: 100%; +} + +.cardHeight80 { + height: 80vh; +} + +.cardHeight110 { + height: 110vh; +} + +.cardHeight130 { + height: 130vh; +} + +.cardHeightAuto { + height: auto; +} + +.cardHeight80Scroll { + height: 80vh; + overflow-y: auto; +} + +.cardHeight110Scroll { + height: 110vh; + overflow-y: auto; +} + +.cardHeight130Scroll { + height: 130vh; + overflow-y: auto; +} + +.cardMarginSmall { + margin: 5px; +} + +.cardMarginHeight57 { + margin: 5px; + height: 57vh; +} + +.titleRow { + display: flex; + justify-content: space-between; + align-items: center; +} + +.titleRowGap { + gap: 12px; +} + +.listHeading { + margin: 10px; + width: 80%; + text-align: center; +} + +.listButton { + margin: 10px; + width: 80%; +} + +.listButtonMuted { + margin: 10px; + width: 80%; + background-color: transparent !important; + color: var(--tooltip-text-color) !important; +} + +.paddingTop30 { + padding-top: 30px; +} + +.radioGroupNormal { + font-weight: normal; +} + +.collapseBase { + width: 100%; +} + +.scrollArea55 { + overflow-y: auto; + height: 55vh; +} + +.buttonMarginSmall { + margin: 5px; +} diff --git a/src/features/documentation/factSheets/factsheetMobile.module.css b/src/features/documentation/factSheets/factsheetMobile.module.css new file mode 100644 index 0000000..f9da4a9 --- /dev/null +++ b/src/features/documentation/factSheets/factsheetMobile.module.css @@ -0,0 +1,219 @@ +.centeredHero { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 100%; +} + +.heroImage { + height: auto; +} + +.heroTitle { + text-align: center; + margin: 0; +} + +.heroDescription { + text-align: center; +} + +.sectionTitle { + margin-left: 10px; +} + +.rowHeight65 { + height: 65vh; +} + +.rowHeight80 { + height: 80vh; +} + +.rowHeight90 { + height: 90vh; +} + +.rowHeight110 { + height: 110vh; +} + +.rowHeight130 { + height: 130vh; +} + +.rowHeight155 { + height: 155vh; +} + +.rowHeight200 { + height: 200vh; +} + +.rowHeight250 { + height: 250vh; +} + +.rowHeight500 { + height: 500vh; +} + +.rowMarginTop20 { + margin-top: 20px; +} + +.rowMarginTop100 { + margin-top: 100px; +} + +.fullHeight { + height: 100%; +} + +.colHeight250 { + height: 250vh; +} + +.cardHeight65 { + height: 65vh; +} + +.cardHeight80 { + height: 80vh; +} + +.cardHeight90 { + height: 90vh; +} + +.cardHeight100 { + height: 100%; +} + +.cardHeight100Scroll { + height: 100vh; + overflow-y: auto; +} + +.cardHeight110Scroll { + height: 110vh; + overflow-y: auto; +} + +.cardHeight130 { + height: 130vh; +} + +.cardHeight250Scroll { + height: 250vh; + overflow-y: auto; +} + +.cardHeightAuto { + height: auto; +} + +.cardMarginTop15 { + margin-top: 15px; +} + +.cardMarginSmall { + margin: 5px; +} + +.cardMarginHeight57 { + margin: 5px; + height: 57vh; +} + +.cardHeightAutoMarginTop20 { + height: auto; + margin-top: 20px; +} + +.titleRow { + display: flex; + justify-content: space-between; + align-items: center; +} + +.titleRowGap { + gap: 12px; +} + +.listHeading { + margin: 10px; + width: 80%; + text-align: center; +} + +.listButton { + margin: 10px; + width: 80%; +} + +.listButtonMuted { + margin: 10px; + width: 80%; + background-color: transparent !important; + color: var(--tooltip-text-color) !important; +} + +.paddingTop10 { + padding-top: 10px; +} + +.paddingTop30 { + padding-top: 30px; +} + +.radioGroupNormal { + font-weight: normal; +} + +.radioGroupStacked { + font-weight: normal; + display: flex; + flex-direction: column; + gap: 8px; + width: 100%; +} + +.collapseBase { + width: 100%; +} + +.collapseMarginTop10 { + margin-top: 10px; +} + +.collapseMarginTop20 { + margin-top: 20px; +} + +.metricItem { + margin-bottom: 20px; +} + +.spaceFullWidth { + width: 100%; + margin-bottom: 12px; +} + +.buttonFullWidth { + width: 100%; +} + +.radioButtonFullWidth { + width: 100%; +} + +.scrollArea48 { + overflow-y: auto; + height: 48vh; +} + +.buttonMarginSmall { + margin: 5px; +} diff --git a/src/features/documentation/factSheets/liveFactsheets.ts b/src/features/documentation/factSheets/liveFactsheets.ts index 71c73f4..f6c31e8 100644 --- a/src/features/documentation/factSheets/liveFactsheets.ts +++ b/src/features/documentation/factSheets/liveFactsheets.ts @@ -1,19 +1,18 @@ -import { baseMacroFactsheetData } from "./baseMacro/baseMacroFactsheetData"; -import { FactsheetModel } from "../landing/desktop/factsheetModel"; -import { safeHavenFactsheetData } from "./safeHaven/safeHavenfactsheetData"; -import { truflationBitcoinFactsheetData } from "./truflationBitcoin/truflationBitcoinFactsheetData"; -import { sonicMacroFactsheetData } from "./sonicMacro/sonicMacroFactsheetData"; +import { baseMacroFactsheetData } from './baseMacro/baseMacroFactsheetData'; +import { FactsheetModel } from '../landing/desktop/factsheetModel'; +import { safeHavenFactsheetData } from './safeHaven/safeHavenfactsheetData'; +import { truflationBitcoinFactsheetData } from './truflationBitcoin/truflationBitcoinFactsheetData'; +import { sonicMacroFactsheetData } from './sonicMacro/sonicMacroFactsheetData'; -interface LiveFactsheets{ - factsheets: FactsheetModel[]; +interface LiveFactsheets { + factsheets: FactsheetModel[]; } export const CURRENT_LIVE_FACTSHEETS: LiveFactsheets = { - factsheets: [ - truflationBitcoinFactsheetData, - safeHavenFactsheetData, - baseMacroFactsheetData, - sonicMacroFactsheetData - - ], -}; \ No newline at end of file + factsheets: [ + truflationBitcoinFactsheetData, + safeHavenFactsheetData, + baseMacroFactsheetData, + sonicMacroFactsheetData, + ], +}; diff --git a/src/features/documentation/factSheets/mobile/factsheetMobile.tsx b/src/features/documentation/factSheets/mobile/factsheetMobile.tsx index a7b72bb..ee57fda 100644 --- a/src/features/documentation/factSheets/mobile/factsheetMobile.tsx +++ b/src/features/documentation/factSheets/mobile/factsheetMobile.tsx @@ -16,11 +16,58 @@ import { import { FactsheetModel } from '../../landing/desktop/factsheetModel'; import { FAQItems } from '../../landing/faqItems'; import { useNavigate } from 'react-router-dom'; +import styles from '../factsheetMobile.module.css'; interface FactsheetDesktopProps { model: FactsheetModel; } +function FactsheetHeroObjectiveSection({ model }: FactsheetDesktopProps) { + return ( + + + +
+ {model.factsheetImage.alt} +

{model.mainTitle}

+

{model.mainDescription}

+
+ + + + +

BTF Objective

+

{model.objective}

+

Responsive Strategy Objective

+

+ The BTF structure allows this to be done in a feeless manner for the + LP with continuous on-chain rebalancing rather than the traditional + monthly or quarterly rebalances. Re-weighting is performed daily. +

+

+ BTFs also augment returns with swap fees associated with providing a + decentralised liquidity pool and provide an ERC20 token that can be + used in other DeFi applications. +

+

+ It is crucial to recognize that BTFs carry risks. Reallocation + strategies are not market-neutral and involve directional assumptions + about asset allocation. Furthermore, the value of assets can be + affected by macro economic factors and global events. +

+ + +
+ ); +} + export function FactSheetMobile(props: FactsheetDesktopProps) { const [breakdowns, setBreakdowns] = useState< Record @@ -30,7 +77,7 @@ export function FactSheetMobile(props: FactsheetDesktopProps) { const [faqEli5, setFAQEli5] = useState('ELI5'); const isDarkTheme = useAppSelector(selectTheme); const navigate = useNavigate(); - + useEffect(() => { const loadBreakdowns = async ( poolNames: Pool[] @@ -90,10 +137,13 @@ export function FactSheetMobile(props: FactsheetDesktopProps) { {props.model.defaultPeriod[1]} - {props.model.alternatePeriod[0] != '' ? - - {props.model.alternatePeriod[1]} - : <>} + {props.model.alternatePeriod[0] !== '' ? ( + + {props.model.alternatePeriod[1]} + + ) : ( + <> + )} ); @@ -137,78 +187,22 @@ export function FactSheetMobile(props: FactsheetDesktopProps) { return (
+ -
- {props.model.factsheetImage.alt} -

- {props.model.mainTitle} -

-

{props.model.mainDescription}

-
- - - - -

BTF Objective

-

{props.model.objective}

-

Responsive Strategy Objective

-

- The BTF structure allows this to be done in a feeless manner for the - LP with continuous on-chain rebalancing rather than the traditional - monthly or quarterly rebalances. Re-weighting is performed daily. -

-

- BTFs also augment returns with swap fees associated with providing a - decentralised liquidity pool and provide an ERC20 token that can be - used in other DeFi applications. -

-

- It is crucial to recognize that BTFs carry risks. Reallocation - strategies are not market-neutral and involve directional - assumptions about asset allocation. Furthermore, the value of assets - can be affected by macro economic factors and global events. -

+

OVERVIEW

- + -

OVERVIEW

- - -
- - - - + +
GENERAL DETAILS
} - style={{ height: '90vh' }} + className={styles.cardHeight90} > -
- Deployment Links -
+
Deployment Links
{props.model.deploymentLinks.contractLinks.map( (link, index) => { @@ -248,7 +234,7 @@ export function FactSheetMobile(props: FactsheetDesktopProps) {
} - style={{ height: '100%' }} + className={styles.cardHeightFull} > -
+
{props.model.updateRule}
@@ -269,18 +252,11 @@ export function TruflationFactSheetDesktop(props: FactsheetDesktopProps) { - + +
Test Window Strategy Explorer @@ -299,7 +275,7 @@ export function TruflationFactSheetDesktop(props: FactsheetDesktopProps) {
} - style={{ height: '100%' }} + className={styles.cardHeightFull} > {view === 'drawdowns' ? ( @@ -317,7 +293,7 @@ export function TruflationFactSheetDesktop(props: FactsheetDesktopProps) { ) : ( - + +
SIMULATED BTF TOTAL $ VALUE OVER TIME {renderPeriodSelector(false)}
} - style={{ margin: '5px' }} + className={styles.cardMarginSmall} >