diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml index 1e9970ca..b74c82ea 100644 --- a/.github/workflows/web.yml +++ b/.github/workflows/web.yml @@ -30,5 +30,6 @@ jobs: - run: npm run lint # The local binary, not npx, which would fetch on demand. - run: ./node_modules/.bin/tsc -b + - run: npm test - run: npm run build:app - run: npm audit --audit-level=high diff --git a/web/README.md b/web/README.md index 819f8de3..a86e6480 100644 --- a/web/README.md +++ b/web/README.md @@ -11,7 +11,7 @@ npm install npm run dev # http://localhost:5173, API proxied to the backend ``` -`npm run lint` and `npx tsc -b` are what CI checks. +`npm run lint`, `npx tsc -b`, and `npm test` are what CI checks. ## Building for the platform diff --git a/web/package-lock.json b/web/package-lock.json index 125dbb15..b42d6ee8 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -29,19 +29,242 @@ "tailwindcss": "^4.3.2" }, "devDependencies": { + "@testing-library/jest-dom": "^7.0.1", + "@testing-library/react": "^16.3.3", + "@testing-library/user-event": "^14.6.8", "@types/node": "^26.6.5", "@types/react": "^19.3.0", "@types/react-dom": "^19.3.0", "@vitejs/plugin-react": "^6.1.2", + "jsdom": "^30.1.3", "oxlint": "^1.89.0", "typescript": "~7.0.2", - "vite": "^8.3.5" + "vite": "^8.3.5", + "vitest": "^5.0.5" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.5.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@asamuzakjp/css-color": { + "version": "7.1.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.4.3", + "@csstools/css-color-parser": "^4.2.6", + "@csstools/css-parser-algorithms": "^4.0.2", + "@csstools/css-tokenizer": "^4.0.2", + "lru-cache": "^11.5.3" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "10.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "bidi-js": "^1.1.0", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.5.3" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.10", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.2.1" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.1.2", + "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/@csstools/css-calc": { + "version": "3.4.3", + "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.2", + "@csstools/css-tokenizer": "^4.0.2" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.2.6", + "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.1.2", + "@csstools/css-calc": "^3.4.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.2", + "@csstools/css-tokenizer": "^4.0.2" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.2", + "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.2" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.15", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.2", + "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/@exodus/bytes": { + "version": "1.16.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } } }, "node_modules/@floating-ui/core": { "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", - "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", "license": "MIT", "dependencies": { "@floating-ui/utils": "^0.2.11" @@ -49,8 +272,6 @@ }, "node_modules/@floating-ui/dom": { "version": "1.7.6", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", - "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", "license": "MIT", "dependencies": { "@floating-ui/core": "^1.7.5", @@ -59,8 +280,6 @@ }, "node_modules/@floating-ui/react-dom": { "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", - "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", "license": "MIT", "dependencies": { "@floating-ui/dom": "^1.7.6" @@ -72,14 +291,10 @@ }, "node_modules/@floating-ui/utils": { "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", - "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", "license": "MIT" }, "node_modules/@fontsource-variable/inter": { "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.3.0.tgz", - "integrity": "sha512-OupL48va4JNofb97w6NYeF9S7W/kHNKM0Er8Dem5nqi4jeOLrVJDoE8tZEpnMJmtkvNbB1EIPPwHcdkF6b1oUA==", "license": "OFL-1.1", "funding": { "url": "https://github.com/sponsors/ayuhito" @@ -87,8 +302,6 @@ }, "node_modules/@fontsource/jetbrains-mono": { "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@fontsource/jetbrains-mono/-/jetbrains-mono-5.3.0.tgz", - "integrity": "sha512-fqDfB5I9f1p1TV486aUgB9t8zP84P0O1FtQR5Ol9vjwPy+S+EIGlVYm1cvj2W5shcZMTg2nZFdVMoH5wFu8a1A==", "license": "OFL-1.1", "funding": { "url": "https://github.com/sponsors/ayuhito" @@ -96,8 +309,6 @@ }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -106,8 +317,6 @@ }, "node_modules/@jridgewell/remapping": { "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -116,8 +325,6 @@ }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "license": "MIT", "engines": { "node": ">=6.0.0" @@ -125,14 +332,10 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", - "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -141,17 +344,15 @@ }, "node_modules/@oxc-project/types": { "version": "0.154.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.154.0.tgz", - "integrity": "sha512-KYOUGQFlLWR+s4QMV2Q9ko38TZm7EdO87Qm7rvZLqk/webse/cDjtyxKhg/47l0byCbs/k46LT8qc4PKjAikzA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/oxc-project" } }, "node_modules/@oxlint/binding-android-arm-eabi": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.89.0.tgz", - "integrity": "sha512-Pflpba5F3gRAQ12sWglRxvWTHEdAusNDvmi4Y5N1T5SY1AGkM7AixOnET8qkrnLJ/F32qAlNokO6eCknT8tmnw==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.90.0.tgz", + "integrity": "sha512-sgfalb1HuxYjiAa4wESZ626z1FGusqNcXYHyB5oIOEprS2sbPg0UYEfo83uF1GNG70DIfZxHkXuE6KFrlSdn8Q==", "cpu": [ "arm" ], @@ -166,9 +367,9 @@ } }, "node_modules/@oxlint/binding-android-arm64": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.89.0.tgz", - "integrity": "sha512-dYmyD1x7LaPlb79vZWUcpTShk8dI9z1I+IubtqrBxWNJaDhQdPHwcsvg7q5StTU4xWyd9AwPfLaTYFZY8NZgtQ==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.90.0.tgz", + "integrity": "sha512-RcKhz1ETMq+fazE9TKS2+5NIRGwigm/nW8egqF0LI+A8Yw2iW5r0E/C3XAPRiwIm/vcPiutIRIa6IQCIKhZsTQ==", "cpu": [ "arm64" ], @@ -183,9 +384,9 @@ } }, "node_modules/@oxlint/binding-darwin-arm64": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.89.0.tgz", - "integrity": "sha512-Z/oxOPiIqiS7fItRdxaaOEC+6GD3OpGi0IxoR0dMefzQHolsKLzScw2DNcu/5gUPok3tv+G9mslvYB5bo3i0qg==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.90.0.tgz", + "integrity": "sha512-PSrxMvP6mx+p1karZcUxLdy9lpaP+JWQPti4al9FgTaPhxE+pT/BjhGExloVVSWgZgIfOlLOjVyyq8BXlodhLQ==", "cpu": [ "arm64" ], @@ -200,9 +401,9 @@ } }, "node_modules/@oxlint/binding-darwin-x64": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.89.0.tgz", - "integrity": "sha512-eAjTkHSjAxrdCPJxO6y5sPQL5pYLKb2kvxZz9UU7tXnOZk9I/HLtkvx9S7GHY883k+AmL/eC7mqFt+w7WCwk0w==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.90.0.tgz", + "integrity": "sha512-OLbU9qsrV2Q8QxT2OEzOPdO8zCCpyI8qB+hDI8yr6RsYELtkf0FAZhJdfjM/RnLHKzt44HWwAPnk92vBtKofvw==", "cpu": [ "x64" ], @@ -217,9 +418,9 @@ } }, "node_modules/@oxlint/binding-freebsd-x64": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.89.0.tgz", - "integrity": "sha512-0lNCA9Tl1n1FKpqy2/9ckp0kIuSrtJ/l9bMRgaEhOtM7TsOHshFuwOcvW8oA9teXLMOJT61LjEKWiaHDFGxF/w==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.90.0.tgz", + "integrity": "sha512-fBA4pWyVDARmNucQqpyFuPC49xYf1r+sX7dxPL5fNPQrSMLum39Sn56tZ9JPleWC7FzLPwSQ7plYYfirYBHF8A==", "cpu": [ "x64" ], @@ -234,9 +435,9 @@ } }, "node_modules/@oxlint/binding-linux-arm-gnueabihf": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.89.0.tgz", - "integrity": "sha512-OTFEvi9W+JczaZD1Caj17SGCoW8H7TE+fvx0D7ATEWa1Lohn91UMxFQ6r145SiGy/yCxeIwptMys/RAM7gSVSQ==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.90.0.tgz", + "integrity": "sha512-zhxH78eP2nR6/pGdhBc7MIfjhhcgATeu8fkmkFJbuzfk6Cj0FzBxyEhL85yDqrCybjx+G2YDzZGsxIO9kvO4Iw==", "cpu": [ "arm" ], @@ -251,9 +452,9 @@ } }, "node_modules/@oxlint/binding-linux-arm-musleabihf": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.89.0.tgz", - "integrity": "sha512-fRxgNHvBLswj6MrhW2uBq5UDhPRQlCbHeq/Nf8yuG9zOxhDsXqX/mi2PX1axgsQxMm88el7HLiJAzglAnflYxw==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.90.0.tgz", + "integrity": "sha512-MvYmpHyJqUJjEvkZbJHdwfdL45cGn8EVBkYmoXFiaYFW9suoD3dbJl5mBdpy0DVgdjlyddmldbRtk3cCgETadQ==", "cpu": [ "arm" ], @@ -268,9 +469,9 @@ } }, "node_modules/@oxlint/binding-linux-arm64-gnu": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.89.0.tgz", - "integrity": "sha512-v9ENzHvwrQoYSWTtviH1pOYc7R9UovDxVofnck50cNmPihRRTcrg5X0UlRbLOZ7IVtxPOlh2hb7kDO+FpE8SfQ==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.90.0.tgz", + "integrity": "sha512-L13Sy+Kg0nZhwTsToniqjUFPA2TuM6962qCZ5Wp5KeB517Tw7ApdLTTtlb+bF86fcZZa2EtvCVvAJxk4Dya/MQ==", "cpu": [ "arm64" ], @@ -288,9 +489,9 @@ } }, "node_modules/@oxlint/binding-linux-arm64-musl": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.89.0.tgz", - "integrity": "sha512-vfffe8mQ484AzVadlUOFpgL+JDwhvs9hrzxTgQP1FVfi4ZEUUYeffXlzC9dFV7Dgnu5s/KrNKvfmG6HxBd/ISQ==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.90.0.tgz", + "integrity": "sha512-wFDdZWGpzIPFdJFjSLWnKB0TGzFriTKUIrl04iMqMyM9BBIJTuerfCHYa0WpDSjr5x3lcW/CHLyacSt886WpGg==", "cpu": [ "arm64" ], @@ -308,9 +509,9 @@ } }, "node_modules/@oxlint/binding-linux-ppc64-gnu": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.89.0.tgz", - "integrity": "sha512-Buonlp4hVS7RGNP5M7bPpGNQrenyzA2C64JcO/oz9C1c6dGOXJ7VEAnaiAMY7voou0JYyCJdvDVlFQc5DZX3DQ==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.90.0.tgz", + "integrity": "sha512-FRywfn+MoKn8xpBSzgNwNVliSKRI2QXkxi/iF7o6FEQs8Bh8z2RpVTaFAcO3pchXjvB2eGNFIoLLQABIQ+u7VA==", "cpu": [ "ppc64" ], @@ -328,9 +529,9 @@ } }, "node_modules/@oxlint/binding-linux-riscv64-gnu": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.89.0.tgz", - "integrity": "sha512-1eVwJJrJ3lYIPT+cm7BcBBZZXY6IQZP/oyonaruTor/MrVyWOpvnwt1oD5CLdsHg5aVBIxXodQSvt+1gK6ck5w==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.90.0.tgz", + "integrity": "sha512-IS2PaUgpc/qeLADTyhUNmdx3x4ijs9pjJdPKBYHaT+mAE7k3FsjnHPTjhpsak1b++h6CIPhv6WX9Z+y7Pw0voQ==", "cpu": [ "riscv64" ], @@ -348,9 +549,9 @@ } }, "node_modules/@oxlint/binding-linux-riscv64-musl": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.89.0.tgz", - "integrity": "sha512-fRQQsKUGShdEL5Fl9o9uhj3grkZbeXKLv0icQ5tspUCNYSDy8wzzEcP1WC2bKdTQ0rSszbKlO3iiHORxV22DLg==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.90.0.tgz", + "integrity": "sha512-s7JFKRkGS8O7i2ZVODfuO++NC7sH6HDqpMn8lYxw91w0SXJOxnAVjQ8utvmDwLMMT0jaSkuOKuUYJDGDtu4sHQ==", "cpu": [ "riscv64" ], @@ -368,9 +569,9 @@ } }, "node_modules/@oxlint/binding-linux-s390x-gnu": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.89.0.tgz", - "integrity": "sha512-dEtYjIy6+fHcjKklsmtmMkZRhwHr2UbzjmpvuPtQYSeN9awQ2hx5JLms5Wvri+8P/NZtD8v5hfgHALUql+/Ycg==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.90.0.tgz", + "integrity": "sha512-b0GxrOQr5VONCkd7KLSqmP6b6nqZuTpuS8QPQHzltmqdXP5bY0Jl0CZ0QmlCTrS3ExaLYbioFYOARzNbYvcApQ==", "cpu": [ "s390x" ], @@ -388,9 +589,9 @@ } }, "node_modules/@oxlint/binding-linux-x64-gnu": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.89.0.tgz", - "integrity": "sha512-hqjwIj5KOYM8Wjef/p7IruNi1iByLhSzbMBk62y22P/E8q7Jj0Pp+DeUTKTRYyZYCDtU1wEGlnWOnttHbyNIww==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.90.0.tgz", + "integrity": "sha512-iaJeShJtgUp8rZbt9YUdTAGOpmX5KogCLba17Jp1tWdJ3F4M3D2g2TKUyI1FMTHxKH9NNHuQcuC2kZRg6IV7yQ==", "cpu": [ "x64" ], @@ -408,9 +609,9 @@ } }, "node_modules/@oxlint/binding-linux-x64-musl": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.89.0.tgz", - "integrity": "sha512-iAQN3e6o9UgUCcdGwqRx/jYcKbG3HK7ur3rH6lTqVcQeNGZ85AeWIg6cgn/edwFQ0ofEZZItMwkzyCVTahnvEA==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.90.0.tgz", + "integrity": "sha512-/aZoXnL5WBLCXuzfHqvEFXcm4iwYmr/cYByHDTXzLFzJdE5DC7bm6phpWqHv6BeqpVgY9IpBAxGDv2WEkl5JCg==", "cpu": [ "x64" ], @@ -428,9 +629,9 @@ } }, "node_modules/@oxlint/binding-openharmony-arm64": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.89.0.tgz", - "integrity": "sha512-MNxjEByz3N/7Q4qFQuPWvc14TpN9LoD3pECEDsJTZbP/wyB7U+hduUXEl7QAbbgVezFwsb9sWtNUO6XVBndOhg==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.90.0.tgz", + "integrity": "sha512-SMhk7ozwoxYDKfYqGZCQEfULOKDneF9uhaoIf2neoDLWvNtjY9yVeL7YHmE5L9WdKvMpWNwwZxhenxBu6AgfOg==", "cpu": [ "arm64" ], @@ -445,9 +646,9 @@ } }, "node_modules/@oxlint/binding-win32-arm64-msvc": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.89.0.tgz", - "integrity": "sha512-xxe+i9wfuFso/VnoL4mfj0UlzE+cfCFiUB06vkXCMyQdgVVmItF5FfAF4tATAktSU6uHruWR+FjqvAT9cHZZzg==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.90.0.tgz", + "integrity": "sha512-KBvR3A9fO7fCi2vCP8ifWvlia7tGEQV9NaYh+IGwa/7Groy48Mt/UaYqMK9/zOtAWl20BUCf6RPqzJ32fRw33Q==", "cpu": [ "arm64" ], @@ -462,9 +663,9 @@ } }, "node_modules/@oxlint/binding-win32-ia32-msvc": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.89.0.tgz", - "integrity": "sha512-saFy07cO94E664glwgLmKLI40HgTL1PY86y4T6+ud4niX4H0Kdqjcmqmjps7mQNdBytV5guGbXDdyQtRfeLbLA==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.90.0.tgz", + "integrity": "sha512-+x42rDHQGbTCuT3EzpWAzhP3NoVPE/cJsG4gnrakOSg0olV+GCdMpPU23Nvrl3zZMnXxFZdTtDv4D3SuaA5ZeQ==", "cpu": [ "ia32" ], @@ -479,9 +680,9 @@ } }, "node_modules/@oxlint/binding-win32-x64-msvc": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.89.0.tgz", - "integrity": "sha512-hLFJ/o0f/dTJh/EW0vmcbREYSQO9tYgfiAAlsOkiLFiJuRRWvYtFELUXN5o2ijpSo/tlO1n+YlWN58tZwPG18A==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.90.0.tgz", + "integrity": "sha512-30JiaFEBUB9peEOcvVM8ggo88SoN/IdSr9DW505JdBji/R6dM66gKEiG65hw6qcaF2f4mO0cEJ+RwRZprmun9g==", "cpu": [ "x64" ], @@ -497,20 +698,14 @@ }, "node_modules/@radix-ui/number": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.3.tgz", - "integrity": "sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==", "license": "MIT" }, "node_modules/@radix-ui/primitive": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.2.0.tgz", - "integrity": "sha512-KABY+VMIRPJL+Vyzv/335FEhXXD/xDv8BMb1kl48/aXJZMH0LtHCTx5ga5RnxaEP3nZHtmTLf1P9vanmFX46bw==", "license": "MIT" }, "node_modules/@radix-ui/react-arrow": { "version": "1.1.17", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.17.tgz", - "integrity": "sha512-67jjcVvxjXCCStnTW3Z+M7jmaFMuMR7M2JgpDzH4w7JSRrikmzhjfYgP/09ECb8n7whnsgh1ifJ+Ie96qEj+pQ==", "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.2.0" @@ -532,8 +727,6 @@ }, "node_modules/@radix-ui/react-collection": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.2.0.tgz", - "integrity": "sha512-+yiQzqcc7Sy7/5s0jrMCjpPRFB3538VNrXPWdTLZhE/SCfgEws4aI6lHWN/vvcnyhHndROWBzqmqB4QBrITZ9Q==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.5", @@ -559,8 +752,6 @@ }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz", - "integrity": "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -574,8 +765,6 @@ }, "node_modules/@radix-ui/react-context": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.3.0.tgz", - "integrity": "sha512-v5AYEocMISkbJ+1fTXzZpiFv7NRr3D2WiYbLIKf+FkmJ8VkP6d503eV2n34MFussHXY1pcoq3yfJiQH77dc4Mg==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -589,8 +778,6 @@ }, "node_modules/@radix-ui/react-dialog": { "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.3.1.tgz", - "integrity": "sha512-+0E4qvVTwjAWra9sEhJ+9BTsvHE00zkUuNw1VGh7S+Gc13dfYk+S1PVQdchWDj+XYHhAai7Z7IpzXVDCGsDplg==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.2.0", @@ -626,8 +813,6 @@ }, "node_modules/@radix-ui/react-direction": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.2.0.tgz", - "integrity": "sha512-FtRA52a/r+LKStFT+neLUm+8vSe7wbzkbQGGeU8ixFiwC8NU14Evoq9ATECqL3eGbcUzJR4fyfA4KkK/q6dfqg==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -641,8 +826,6 @@ }, "node_modules/@radix-ui/react-dismissable-layer": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.2.1.tgz", - "integrity": "sha512-KIfDcAsx+oVylFZ4xryVKusJUrLLhTQmfnJW4kwk4Km5myvhHCrj04hnWWUC166+xD4DHdH8tQGylwYX1h6+Pg==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.2.0", @@ -668,8 +851,6 @@ }, "node_modules/@radix-ui/react-dropdown-menu": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.2.1.tgz", - "integrity": "sha512-XsHcmYG2AcqnvTqrOXXWcMrhquEIlqucizuDH1MV/qMRW+DndpBCMvNeTRXOxqmG1H5j89M/+I0/34Zdd+zDcQ==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.2.0", @@ -697,8 +878,6 @@ }, "node_modules/@radix-ui/react-focus-guards": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.2.0.tgz", - "integrity": "sha512-MPm5R0iRGUDrDnPNxmLBkv+X7pN4XKzt+MbWJEuJQsH3NFH4omrbpXpucyYCCKTSt+na/ZxGhEWQrWTZ0285cA==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -712,8 +891,6 @@ }, "node_modules/@radix-ui/react-focus-scope": { "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.3.1.tgz", - "integrity": "sha512-ugeMhbXt4y5S2T1GnIqbWDKEN962PDwnX1fN0eKOOgf5f63Dh2mn8F1FUySlgW0iF6GiVsRF32DzuDIWTUyQJg==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.5", @@ -737,8 +914,6 @@ }, "node_modules/@radix-ui/react-id": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.4.tgz", - "integrity": "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==", "license": "MIT", "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.4" @@ -755,8 +930,6 @@ }, "node_modules/@radix-ui/react-menu": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.2.1.tgz", - "integrity": "sha512-G34BsU90SBj5foYpz8pQqq/Q0Dlfyyl6KitWEfRwXC0QvfwhWNAwS49DWP2+rRJ0iwv20Z2Qx7Sk+T03bGo5JA==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.2.0", @@ -795,8 +968,6 @@ }, "node_modules/@radix-ui/react-popover": { "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.3.1.tgz", - "integrity": "sha512-ZNxvXRx5W2Wos4fXCXb5lpdqN0Z5chf+mStOUOTr4ik7d+62MRvoe9rzCA/eGNg36lxXqSrKEKmwuZEcUrIstw==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.2.0", @@ -833,8 +1004,6 @@ }, "node_modules/@radix-ui/react-popper": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.4.0.tgz", - "integrity": "sha512-yCWRvLeGv9Pz3+efwHqGPp70mZ56r3kfIgQVF56Oj2aAa4uP/Tb+IE59wNcc+758rctqbCKx4LbK8II/r7Bn0Q==", "license": "MIT", "dependencies": { "@floating-ui/react-dom": "^2.0.0", @@ -865,8 +1034,6 @@ }, "node_modules/@radix-ui/react-portal": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.2.0.tgz", - "integrity": "sha512-StX7xXnaRU41VvFtMm9zlhVGz8XF6GmWWTXftkQaY+tqVkbs/R9UmAp5mLzHHapk5hM9RMZQvO/3usyIP/g42g==", "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.2.0", @@ -889,8 +1056,6 @@ }, "node_modules/@radix-ui/react-presence": { "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.11.tgz", - "integrity": "sha512-Z7/atlkab36Z8hm+V8zYc79r0W+tiOB1o0tun1By94JyadkekLQmtEAnBSP9XKXBNQsStoStS4+gX10JpS5akA==", "license": "MIT", "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.4" @@ -912,8 +1077,6 @@ }, "node_modules/@radix-ui/react-primitive": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.2.0.tgz", - "integrity": "sha512-HCTSPMNqv55Qk0IX4geJbpyXazmqulcimwuyjxTkKi29v+QZbG2Zho9BnbBq4YS2PF+KlNb2CXoLz66uUmblkg==", "license": "MIT", "dependencies": { "@radix-ui/react-slot": "1.5.0" @@ -935,8 +1098,6 @@ }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.2.1.tgz", - "integrity": "sha512-UwraORzVUeEPMclhO1p0vz0qI+TbdDLO9i+FiOqhJyHKwdpCRCck9yVEscUnAMIzCiuNoQa8D4A9bIpOPXGt6Q==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.2.0", @@ -968,8 +1129,6 @@ }, "node_modules/@radix-ui/react-select": { "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.4.1.tgz", - "integrity": "sha512-888ZwW0Md6GyptuqYABRX9nLKgoYfcooCyBBvVaX5Jp3e+8Jp2i4Zo/Uy50Hm+Ysn2JfloQXstW4U36w8teVtg==", "license": "MIT", "dependencies": { "@radix-ui/number": "1.1.3", @@ -1011,8 +1170,6 @@ }, "node_modules/@radix-ui/react-slot": { "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.5.0.tgz", - "integrity": "sha512-tyYd9ddPXAysAbdbD9eYxgwpCSv6f3PncqVSNKQFSR5lcJVLz3cb/pANDoYrLiHBpXv7skortyUgGUWsYuPPFQ==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.5" @@ -1029,8 +1186,6 @@ }, "node_modules/@radix-ui/react-switch": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.4.1.tgz", - "integrity": "sha512-NpBGwc40rY02ZmSvuyeKk0Bvc8ZsNeTMi4qHvJ+ZH4EiYCv0zIXEgdCW4bVdzgIUeP9xxbLJSxKOUfqITYNmkA==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.2.0", @@ -1057,8 +1212,6 @@ }, "node_modules/@radix-ui/react-tooltip": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.4.1.tgz", - "integrity": "sha512-NxLFGSFtZ78zVfcoOhTwR1xwqo5qx1Jh8Pw6FMZ9McCaqfRzz7/gIRahs2Oy5XlPgQ+8QkUcKbN5B0U44J/kCg==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.2.0", @@ -1093,8 +1246,6 @@ }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.5.tgz", - "integrity": "sha512-Y5GBf4NKeADoXI1geiMlMuMoURT3k9MizzzKhGb/m55H94aGigZ01PIpkvsO6lML13BA6XYAXSJ6gza7vTn5nw==", "license": "MIT", "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.4" @@ -1111,8 +1262,6 @@ }, "node_modules/@radix-ui/react-use-controllable-state": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.3.0.tgz", - "integrity": "sha512-TAcBI7bw2ljYCbv4+zuTSd6zICu8pRt6SDN2vJ/Z06K2MXh6I9WYFe/A9ovlFDtR5HVHGm5iDxDoABm/++dbKg==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.2.0", @@ -1131,8 +1280,6 @@ }, "node_modules/@radix-ui/react-use-effect-event": { "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.5.tgz", - "integrity": "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==", "license": "MIT", "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.4" @@ -1149,8 +1296,6 @@ }, "node_modules/@radix-ui/react-use-is-hydrated": { "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.3.tgz", - "integrity": "sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -1164,8 +1309,6 @@ }, "node_modules/@radix-ui/react-use-layout-effect": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.4.tgz", - "integrity": "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -1179,8 +1322,6 @@ }, "node_modules/@radix-ui/react-use-rect": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.4.tgz", - "integrity": "sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==", "license": "MIT", "dependencies": { "@radix-ui/rect": "1.1.3" @@ -1197,8 +1338,6 @@ }, "node_modules/@radix-ui/react-use-size": { "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.5.tgz", - "integrity": "sha512-V1McDQkMGzgivwqdu7u96APs7oi0Jvj9LTzgq3WWrDZqxWdAqU9yZnZQqk5OizGFR8badUQwZsrlao7MOlWYOA==", "license": "MIT", "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.4" @@ -1215,8 +1354,6 @@ }, "node_modules/@radix-ui/react-visually-hidden": { "version": "1.2.13", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.13.tgz", - "integrity": "sha512-feijiN1oPk4rck4u9Dm9tsU4mT6Wp9oeEEhaz1WUEs1VuqLf8xzsFG+Qk+uWq6TnMH9O+WRECRg0NftyP9E4Fw==", "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.2.0" @@ -1238,8 +1375,6 @@ }, "node_modules/@radix-ui/rect": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.3.tgz", - "integrity": "sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==", "license": "MIT" }, "node_modules/@rolldown/binding-android-arm-eabi": { @@ -1486,8 +1621,6 @@ }, "node_modules/@rolldown/binding-win32-x64-msvc": { "version": "1.2.14", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.14.tgz", - "integrity": "sha512-9mE94bf2GZUaR/fUSXCMpBoWkctu421Mi0ORpm4zqfbkjKep7RxzPFa5etcGs5h+2KApoWBOA/hAJa/7saGgEQ==", "cpu": [ "x64" ], @@ -1502,14 +1635,10 @@ }, "node_modules/@rolldown/pluginutils": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "license": "MIT" }, "node_modules/@tailwindcss/node": { "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", - "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.5", @@ -1523,8 +1652,6 @@ }, "node_modules/@tailwindcss/oxide": { "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", - "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", "license": "MIT", "engines": { "node": ">= 20" @@ -1729,66 +1856,6 @@ "node": ">=14.0.0" } }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.11.1", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.11.1", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { - "version": "1.2.2", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { - "version": "2.8.1", - "inBundle": true, - "license": "0BSD", - "optional": true - }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", @@ -1807,8 +1874,6 @@ }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", - "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", "cpu": [ "x64" ], @@ -1823,8 +1888,6 @@ }, "node_modules/@tailwindcss/vite": { "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz", - "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==", "license": "MIT", "dependencies": { "@tailwindcss/node": "4.3.3", @@ -1849,9 +1912,9 @@ } }, "node_modules/@tanstack/query-core": { - "version": "5.104.5", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.104.5.tgz", - "integrity": "sha512-Q94gI8Opd+CbdVNOdj8ZCyV8WqLYitrDq1t96Xc8WZT6lY0r5iRnJLXYDxMex7EEf2v+mXvn4FcjaBKSxXZQ9A==", + "version": "5.105.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.105.1.tgz", + "integrity": "sha512-B/rNlZoZ54w1dlddiUt9oEe1SLM+37c00SKLJEg9YH+7as3Y54P81o9Fh+2ujZ36tXPnQJ5oVe7AcyEWkyFhPg==", "license": "MIT", "funding": { "type": "github", @@ -1859,12 +1922,12 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.104.5", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.104.5.tgz", - "integrity": "sha512-RORgFe052U+93weaNuqg7qHq7vNVNzXN/VrXqD4bbAuQCWUqOl2lV5Ui9flWk5CVvMyL/Bh27QWhzwH0aaOx9g==", + "version": "5.105.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.105.1.tgz", + "integrity": "sha512-qcu+/+PtBOjW8qU4/0l47QVmNG7hPOWsCLzP0bTRhE1X7HYa757C4732wXTUH0QhCjTV1yedX8onsCiEwlNYrA==", "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.104.5" + "@tanstack/query-core": "5.105.1" }, "funding": { "type": "github", @@ -1905,8 +1968,6 @@ }, "node_modules/@tanstack/react-store": { "version": "0.11.2", - "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.11.2.tgz", - "integrity": "sha512-oj5O5DmiMWE+ih+SioX2ayC1kbK7d3fpLx0B75fPJr9nY3zU+hoW010yr64qXsY/9fyiLVE2bn5mGNV3owm8RQ==", "license": "MIT", "dependencies": { "@tanstack/store": "0.11.2", @@ -1942,18 +2003,128 @@ }, "node_modules/@tanstack/store": { "version": "0.11.2", - "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.11.2.tgz", - "integrity": "sha512-sJ4mjol8uQsHV0gOJzzjwXfh2Fwm+Sz0+8deqiTm4jGbMdjzNSW+xZCFm0kUa870uhd8yi+DpKnZb5Kc4apa0Q==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@testing-library/dom": { + "version": "10.4.2", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=22", + "npm": ">=6", + "yarn": ">=1" + }, + "peerDependencies": { + "@testing-library/dom": ">=10 <11", + "vitest": ">= 0.32" + }, + "peerDependenciesMeta": { + "vitest": { + "optional": true + } + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.8", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "26.6.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.5.tgz", - "integrity": "sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg==", "devOptional": true, "license": "MIT", "dependencies": { @@ -1962,8 +2133,6 @@ }, "node_modules/@types/react": { "version": "19.3.0", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", - "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", "devOptional": true, "license": "MIT", "dependencies": { @@ -1972,8 +2141,6 @@ }, "node_modules/@types/react-dom": { "version": "19.3.0", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz", - "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==", "devOptional": true, "license": "MIT", "peerDependencies": { @@ -2305,8 +2472,6 @@ }, "node_modules/@typescript/typescript-win32-x64": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", - "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", "cpu": [ "x64" ], @@ -2322,8 +2487,6 @@ }, "node_modules/@vitejs/plugin-react": { "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.2.tgz", - "integrity": "sha512-fhJNGUdscQa4HkI0xFxwonWyhnVReCPDbXI7FH7jmzpnVCrFQI7PRrhWecO263610vOrs9tr2Y4xcf8uAdFxbw==", "dev": true, "license": "MIT", "dependencies": { @@ -2350,23 +2513,114 @@ } } }, - "node_modules/aria-hidden": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", - "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "node_modules/@vitest/mocker": { + "version": "5.0.5", + "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.0.0" + "@jridgewell/trace-mapping": "0.3.31", + "@vitest/spy": "5.0.5", + "estree-walker": "^3.0.3", + "magic-string": "^1.4.3" }, - "engines": { - "node": ">=10" - } - }, - "node_modules/class-variance-authority": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", - "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", - "license": "Apache-2.0", + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9 || ^3.0.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/magic-string": { + "version": "1.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.6.0" + } + }, + "node_modules/@vitest/spy": { + "version": "5.0.5", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/bidi-js": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/chai": { + "version": "6.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "license": "Apache-2.0", "dependencies": { "clsx": "^2.1.1" }, @@ -2376,8 +2630,6 @@ }, "node_modules/clsx": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "license": "MIT", "engines": { "node": ">=6" @@ -2389,17 +2641,55 @@ "integrity": "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==", "license": "MIT" }, + "node_modules/css-tree": { + "version": "3.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "dev": true, + "license": "MIT" + }, "node_modules/csstype": { "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "devOptional": true, "license": "MIT" }, + "node_modules/data-urls": { + "version": "8.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^17.1.2" + }, + "engines": { + "node": "^22.14.0 || >=24.0.0" + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "dev": true, + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", "engines": { "node": ">=8" @@ -2407,14 +2697,16 @@ }, "node_modules/detect-node-es": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", - "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/enhanced-resolve": { "version": "5.25.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.25.1.tgz", - "integrity": "sha512-nGXts5znJzmWPu+mIE9izCOzdg63oJca2mDzGWWTth7sr4aCToKcoyFVBQwN75Ij5Pf6p510EwkTqViTRzDV+w==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -2424,10 +2716,40 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "8.1.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "dev": true, + "license": "MIT" + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fdir": { "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "license": "MIT", "engines": { "node": ">=12.0.0" @@ -2442,18 +2764,19 @@ } }, "node_modules/framer-motion": { - "version": "14.6.0", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-14.6.0.tgz", - "integrity": "sha512-MzBgZxGgoHPfSLEXmo4o5XlZvb+nVRFdHtGjVhm6LMIEJKTt/8hNO023euL5mYyzc8llV/hQTfjnN68tNRHalw==", + "version": "14.9.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-14.9.0.tgz", + "integrity": "sha512-g1hZu8WoTwgsoVAaqEEcefhvR03cKGtkmrKaRrWpXhnM3LGR8jl1u2qyJ1PMVlO7LKQHE8TTRQYUlMmnrY4L+g==", "license": "MIT", "dependencies": { - "motion-dom": "14.6.0", - "motion-utils": "14.6.0", + "motion-dom": "14.9.0", + "motion-utils": "14.7.0", "tslib": "^2.4.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" + "react-dom": "^18.0.0 || ^19.0.0", + "remix": "^3.1.0" }, "peerDependenciesMeta": { "react": { @@ -2461,6 +2784,9 @@ }, "react-dom": { "optional": true + }, + "remix": { + "optional": true } } }, @@ -2480,8 +2806,6 @@ }, "node_modules/get-nonce": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", - "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", "license": "MIT", "engines": { "node": ">=6" @@ -2489,14 +2813,34 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, + "node_modules/html-encoding-sniffer": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.15.1" + }, + "engines": { + "node": "^22.13.0 || >=24.0.0" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, "node_modules/isbot": { "version": "5.1.44", - "resolved": "https://registry.npmjs.org/isbot/-/isbot-5.1.44.tgz", - "integrity": "sha512-PGEHtwMnKbZpeSEXW2Utx+/JWed7dp6DiH0WWg33vGSDA7RUvpUeJSVlLrVkQ1RCpvDOUc/eH9ql7VsdbBZ8pA==", "license": "Unlicense", "engines": { "node": ">=18" @@ -2504,17 +2848,57 @@ }, "node_modules/jiti": { "version": "2.7.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", - "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/jsdom": { + "version": "30.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^7.1.3", + "@asamuzakjp/dom-selector": "^10.0.3", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.15", + "@exodus/bytes": "^1.16.0", + "css-tree": "^3.2.1", + "data-urls": "^8.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^7.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.5.3", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "tough-cookie": "^6.0.2", + "undici": "^8.12.1", + "w3c-xmlserializer": "^6.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^17.2.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + }, + "peerDependencies": { + "canvas": "^3.2.3" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/lightningcss": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -2754,8 +3138,6 @@ }, "node_modules/lightningcss-win32-x64-msvc": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", "cpu": [ "x64" ], @@ -2772,10 +3154,18 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lru-cache": { + "version": "11.5.3", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/lucide-react": { - "version": "1.61.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.61.0.tgz", - "integrity": "sha512-v2SQPPPNjnr+BlhL9Spuzmo5yR+hLkftfuE4E3jaxdNPgSH0hJKPcLJBP7yAta87jQNPsSERS59PJjCJ/CorBA==", + "version": "1.66.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.66.0.tgz", + "integrity": "sha512-8zjbi/Us+ETXqfBwmXF/FHrWGIqOdE6zT1yUdRJLQVoptomSkB0zdFWVIM667t9K8djWDaXdkBkqi2qVTuDNiw==", "license": "ISC", "peerDependencies": { "@types/react": "*", @@ -2787,27 +3177,48 @@ } } }, + "node_modules/lz-string": { + "version": "1.5.0", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "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==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/mdn-data": { + "version": "2.27.1", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/min-indent": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/motion": { - "version": "14.6.0", - "resolved": "https://registry.npmjs.org/motion/-/motion-14.6.0.tgz", - "integrity": "sha512-qqaZbyubgk7x3zxjPgP1l3Br/KQXol/IIV1uqb358SoN4ApcqNuV7+wyDPg/XLErmuyQL5lL8JVI/fOmDsmBKQ==", + "version": "14.9.0", + "resolved": "https://registry.npmjs.org/motion/-/motion-14.9.0.tgz", + "integrity": "sha512-Oc+jjq/AyhwsnEUaAq2xEu/SsfKTphgsXqoxddTSLzgTdXZc6FwHcp9RLGSECccTykN1/p3gWUN4kMJAamm+1g==", "license": "MIT", "dependencies": { - "framer-motion": "14.6.0", + "framer-motion": "14.9.0", "tslib": "^2.4.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" + "react-dom": "^18.0.0 || ^19.0.0", + "remix": "^3.1.0" }, "peerDependenciesMeta": { "react": { @@ -2815,28 +3226,29 @@ }, "react-dom": { "optional": true + }, + "remix": { + "optional": true } } }, "node_modules/motion-dom": { - "version": "14.6.0", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-14.6.0.tgz", - "integrity": "sha512-lIojTl9aYSPIkeOOSzBliy1nNvpdO3JtVzW2WgTXQlOP4ZMuJhtWO3fP7OD4TVFwlRucdvxlDSMkL+fEQnQ52A==", + "version": "14.9.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-14.9.0.tgz", + "integrity": "sha512-wGjKbNDMHd75aLStkxLCKZ+T8/wusZHklmNLK1Ak68UrdBt2X8I4yweYR/0WonxGnBsw6oloDTIV8pN+UTvzzA==", "license": "MIT", "dependencies": { - "motion-utils": "14.6.0" + "motion-utils": "14.7.0" } }, "node_modules/motion-utils": { - "version": "14.6.0", - "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-14.6.0.tgz", - "integrity": "sha512-bZJo2AmQ2aOFkaato6VXrTbkPG0XhMzivWUWSaI59pxyllj8QMQUFZ84MYRQnPkK+7dr4KldyJwF2+RM0sdq/w==", + "version": "14.7.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-14.7.0.tgz", + "integrity": "sha512-woO0/Nb7ys75f94fjvQi/6leiHGvZUYkeHUPEZ3vxsV+9OpEVhkxp01x8MlRwX0zt31EqgXsKDAoBoJLC148Tw==", "license": "MIT" }, "node_modules/nanoid": { "version": "3.3.20", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", - "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", "funding": [ { "type": "github", @@ -2851,10 +3263,22 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/obug": { + "version": "3.0.0", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/oxlint": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.89.0.tgz", - "integrity": "sha512-a25hkfo61okGPvJz432fRIPoxlKtNnOVy9w4rzrmGgNm4iVTPwpSQlmdqNWnp6p9QyyPVfAZRgnf5XvSBZFk/A==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.90.0.tgz", + "integrity": "sha512-RkTN9EmgIUpLuhNoLhO3OliMGJlvb3Dk7XVjBZnfoMCHrVpuz5ESd97klfiyYeV7XfA2lNLnDCPMkUI+Qw5How==", "dev": true, "license": "MIT", "bin": { @@ -2867,28 +3291,28 @@ "url": "https://github.com/sponsors/oxc-project" }, "optionalDependencies": { - "@oxlint/binding-android-arm-eabi": "1.89.0", - "@oxlint/binding-android-arm64": "1.89.0", - "@oxlint/binding-darwin-arm64": "1.89.0", - "@oxlint/binding-darwin-x64": "1.89.0", - "@oxlint/binding-freebsd-x64": "1.89.0", - "@oxlint/binding-linux-arm-gnueabihf": "1.89.0", - "@oxlint/binding-linux-arm-musleabihf": "1.89.0", - "@oxlint/binding-linux-arm64-gnu": "1.89.0", - "@oxlint/binding-linux-arm64-musl": "1.89.0", - "@oxlint/binding-linux-ppc64-gnu": "1.89.0", - "@oxlint/binding-linux-riscv64-gnu": "1.89.0", - "@oxlint/binding-linux-riscv64-musl": "1.89.0", - "@oxlint/binding-linux-s390x-gnu": "1.89.0", - "@oxlint/binding-linux-x64-gnu": "1.89.0", - "@oxlint/binding-linux-x64-musl": "1.89.0", - "@oxlint/binding-openharmony-arm64": "1.89.0", - "@oxlint/binding-win32-arm64-msvc": "1.89.0", - "@oxlint/binding-win32-ia32-msvc": "1.89.0", - "@oxlint/binding-win32-x64-msvc": "1.89.0" + "@oxlint/binding-android-arm-eabi": "1.90.0", + "@oxlint/binding-android-arm64": "1.90.0", + "@oxlint/binding-darwin-arm64": "1.90.0", + "@oxlint/binding-darwin-x64": "1.90.0", + "@oxlint/binding-freebsd-x64": "1.90.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.90.0", + "@oxlint/binding-linux-arm-musleabihf": "1.90.0", + "@oxlint/binding-linux-arm64-gnu": "1.90.0", + "@oxlint/binding-linux-arm64-musl": "1.90.0", + "@oxlint/binding-linux-ppc64-gnu": "1.90.0", + "@oxlint/binding-linux-riscv64-gnu": "1.90.0", + "@oxlint/binding-linux-riscv64-musl": "1.90.0", + "@oxlint/binding-linux-s390x-gnu": "1.90.0", + "@oxlint/binding-linux-x64-gnu": "1.90.0", + "@oxlint/binding-linux-x64-musl": "1.90.0", + "@oxlint/binding-openharmony-arm64": "1.90.0", + "@oxlint/binding-win32-arm64-msvc": "1.90.0", + "@oxlint/binding-win32-ia32-msvc": "1.90.0", + "@oxlint/binding-win32-x64-msvc": "1.90.0" }, "peerDependencies": { - "oxlint-tsgolint": ">=7.0.2003", + "oxlint-tsgolint": ">=7.0.2005", "vite-plus": "*" }, "peerDependenciesMeta": { @@ -2900,16 +3324,23 @@ } } }, + "node_modules/parse5": { + "version": "8.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/picocolors": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.7", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", - "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "license": "MIT", "engines": { "node": ">=12" @@ -2920,8 +3351,6 @@ }, "node_modules/postcss": { "version": "8.5.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.31.tgz", - "integrity": "sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==", "funding": [ { "type": "opencollective", @@ -2946,10 +3375,30 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/pretty-format": { + "version": "27.5.1", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/react": { "version": "19.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-19.3.1.tgz", - "integrity": "sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -2957,8 +3406,6 @@ }, "node_modules/react-dom": { "version": "19.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.1.tgz", - "integrity": "sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ==", "license": "MIT", "dependencies": { "scheduler": "^0.28.0" @@ -2967,10 +3414,14 @@ "react": "^19.3.1" } }, + "node_modules/react-is": { + "version": "17.0.2", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/react-remove-scroll": { "version": "2.7.2", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", - "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", "license": "MIT", "dependencies": { "react-remove-scroll-bar": "^2.3.7", @@ -2994,8 +3445,6 @@ }, "node_modules/react-remove-scroll-bar": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", - "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", "license": "MIT", "dependencies": { "react-style-singleton": "^2.2.2", @@ -3016,8 +3465,6 @@ }, "node_modules/react-style-singleton": { "version": "2.2.3", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", - "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", "license": "MIT", "dependencies": { "get-nonce": "^1.0.0", @@ -3036,10 +3483,28 @@ } } }, + "node_modules/redent": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/rolldown": { "version": "1.2.14", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.14.tgz", - "integrity": "sha512-eTBTTqMR6uHKLgcUqpJP8ntzDI6V4LYA8negAMBEI7gkgVvOdrx6VsvS2Ym+83KhOPdLCHWJu0w7UQFJZhHKNw==", "license": "MIT", "dependencies": { "@oxc-project/types": "=0.154.0", @@ -3069,25 +3534,34 @@ "@rolldown/binding-win32-x64-msvc": "1.2.14" } }, + "node_modules/saxes": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.28.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", - "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", "license": "MIT" }, "node_modules/seroval": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.7.2.tgz", - "integrity": "sha512-NPOYrDVbvtuTo5m9InqKE7O6wBASON46OabjyqLUGPvoXNDYp0pReuFYkqOQwGsoj1OpZVj6mIdnRRgt5O1/gQ==", + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.7.6.tgz", + "integrity": "sha512-oKNe6A8I3lSA5J0xg9xsrWCWDpET6CFhCMVuIsCmqrjNpBXzQXDAAQI5UfuC4Ner4DIR+VAtSw7TC0AmRMqFFQ==", "license": "MIT", "engines": { "node": ">=20" } }, "node_modules/seroval-plugins": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.7.2.tgz", - "integrity": "sha512-ud8kMCGLHmGkW9YtVDluJtqTukSx/4YBqtmPAvAz/HxbIwhwMeZU52imxeyZ1zoAXtqBDiaLwSasbnK9DptrDg==", + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.7.6.tgz", + "integrity": "sha512-/TPCAd6q5stAZnugD2fsvNKtM2UpgwrS8/fO1cXTgPC0EqCsGB+UfkEEsToFCfg35CDTMlWkppfr/zWnLJGpBA==", "license": "MIT", "engines": { "node": ">=20" @@ -3098,17 +3572,29 @@ }, "node_modules/source-map-js": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", - "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/std-env": { + "version": "4.3.0", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/tailwind-merge": { "version": "3.7.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.7.0.tgz", - "integrity": "sha512-XPPUyAc+cvspz3lHTcR/QgPfW2A0lv/xQNIjX3HGhLR+Nq2lHaLq5MtTesHn8GUr3W3DguT2KT5x3NVgRtYwmA==", "license": "MIT", "funding": { "type": "github", @@ -3117,14 +3603,10 @@ }, "node_modules/tailwindcss": { "version": "4.3.3", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", - "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", "license": "MIT" }, "node_modules/tapable": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", - "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", "license": "MIT", "engines": { "node": ">=6" @@ -3134,10 +3616,24 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/tinybench": { + "version": "6.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/tinyexec": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -3150,16 +3646,50 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tldts": { + "version": "7.4.19", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.19" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.4.19", + "dev": true, + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "6.0.2", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": "^22.14.0 || >=24.0.0" + } + }, "node_modules/tslib": { "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, "node_modules/typescript": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", - "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -3191,17 +3721,21 @@ "@typescript/typescript-win32-x64": "7.0.2" } }, + "node_modules/undici": { + "version": "8.12.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, "node_modules/undici-types": { "version": "8.9.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", - "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", "devOptional": true, "license": "MIT" }, "node_modules/use-callback-ref": { "version": "1.3.3", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", - "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", "license": "MIT", "dependencies": { "tslib": "^2.0.0" @@ -3221,8 +3755,6 @@ }, "node_modules/use-sidecar": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", - "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", "license": "MIT", "dependencies": { "detect-node-es": "^1.1.0", @@ -3243,8 +3775,6 @@ }, "node_modules/use-sync-external-store": { "version": "1.7.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.7.0.tgz", - "integrity": "sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==", "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -3329,8 +3859,6 @@ }, "node_modules/vite/node_modules/lightningcss": { "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", - "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -3570,8 +4098,6 @@ }, "node_modules/vite/node_modules/lightningcss-win32-x64-msvc": { "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", - "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", "cpu": [ "x64" ], @@ -3587,6 +4113,159 @@ "type": "opencollective", "url": "https://opencollective.com/parcel" } + }, + "node_modules/vitest": { + "version": "5.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/mocker": "5.0.5", + "chai": "^6.3.0", + "es-module-lexer": "^2.3.2", + "expect-type": "^1.4.0", + "magic-string": "^1.4.3", + "obug": "^3.0.0", + "picomatch": "^4.0.7", + "std-env": "^4.3.0", + "tinybench": "^6.2.2", + "tinyexec": "^1.3.1", + "tinyglobby": "^0.2.17", + "why-is-node-running": "3.2.1" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^22.12.0 || ^24.0.0 || >=26.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "5.0.5", + "@vitest/browser-preview": "5.0.5", + "@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0", + "@vitest/coverage-istanbul": "5.0.5", + "@vitest/coverage-v8": "5.0.5", + "@vitest/ui": "5.0.5", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.4.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/magic-string": { + "version": "1.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.6.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "17.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.16.0", + "tr46": "^7.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^22.14.0 || >=24.0.0" + } + }, + "node_modules/why-is-node-running": { + "version": "3.2.1", + "dev": true, + "license": "MIT", + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=20.11" + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "dev": true, + "license": "MIT" } } } diff --git a/web/package.json b/web/package.json index e3b38027..25a76fe7 100644 --- a/web/package.json +++ b/web/package.json @@ -9,7 +9,8 @@ "build:app": "tsc -b && vite build --base=/app/", "build:demo": "vite build --base=./ --mode demo", "lint": "oxlint --deny-warnings", - "preview": "vite preview" + "preview": "vite preview", + "test": "vitest run" }, "dependencies": { "@fontsource-variable/inter": "^5.3.0", @@ -33,12 +34,17 @@ "tailwindcss": "^4.3.2" }, "devDependencies": { + "@testing-library/jest-dom": "^7.0.1", + "@testing-library/react": "^16.3.3", + "@testing-library/user-event": "^14.6.8", "@types/node": "^26.6.5", "@types/react": "^19.3.0", "@types/react-dom": "^19.3.0", "@vitejs/plugin-react": "^6.1.2", + "jsdom": "^30.1.3", "oxlint": "^1.89.0", "typescript": "~7.0.2", - "vite": "^8.3.5" + "vite": "^8.3.5", + "vitest": "^5.0.5" } } diff --git a/web/src/components/DiffDrawer.test.tsx b/web/src/components/DiffDrawer.test.tsx new file mode 100644 index 00000000..8089fae2 --- /dev/null +++ b/web/src/components/DiffDrawer.test.tsx @@ -0,0 +1,61 @@ +import { render, screen } from "@testing-library/react"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +import { DiffDrawer, type DiffTarget } from "@/components/DiffDrawer"; +import { createWrapper } from "@/test/query"; + +const target: DiffTarget = { + runId: 21, + sampleId: 14, + regressionId: 7, + outputId: 3, + command: "-autoprogram", + sampleName: "pal.ts", +}; + +function jsonResponse(status: number, body: unknown): Response { + return new Response(JSON.stringify(body), { + status, + headers: { "Content-Type": "application/json" }, + }); +} + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +describe("DiffDrawer", () => { + it("renders a unified diff for a failing output", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async (input: RequestInfo | URL) => { + const url = String(input); + if (url.includes("/diff")) { + return jsonResponse(200, { + content: "--- expected\n+++ actual\n@@ -1 +1 @@\n-old caption\n+new caption\n", + }); + } + return jsonResponse(404, {}); + }), + ); + + const { wrapper } = createWrapper(); + render(, { wrapper }); + + expect(screen.getByText("Test #7")).toBeInTheDocument(); + expect(screen.getByText(/pal\.ts/)).toBeInTheDocument(); + expect(await screen.findByText("-old caption")).toBeInTheDocument(); + expect(screen.getByText("+new caption")).toBeInTheDocument(); + }); + + it("explains when the output file is missing from storage", async () => { + vi.stubGlobal("fetch", vi.fn(async () => jsonResponse(404, { message: "not stored" }))); + + const { wrapper } = createWrapper(); + render(, { wrapper }); + + expect( + await screen.findByText(/output file for this result isn't present in storage/i), + ).toBeInTheDocument(); + }); +}); diff --git a/web/src/components/Login.test.tsx b/web/src/components/Login.test.tsx new file mode 100644 index 00000000..0c52bc80 --- /dev/null +++ b/web/src/components/Login.test.tsx @@ -0,0 +1,69 @@ +import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +import { Login } from "@/components/Login"; + +function jsonResponse(status: number, body: unknown): Response { + return new Response(JSON.stringify(body), { + status, + headers: { "Content-Type": "application/json" }, + }); +} + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +describe("Login", () => { + it("shows the same vague error on a 401", async () => { + const user = userEvent.setup(); + vi.stubGlobal("fetch", vi.fn(async () => jsonResponse(401, { message: "nope" }))); + + render(); + await user.type(screen.getByLabelText("Email"), "dev@example.com"); + await user.type(screen.getByLabelText("Password"), "wrong"); + await user.click(screen.getByRole("button", { name: /sign in/i })); + + expect(await screen.findByText("Email or password is incorrect.")).toBeInTheDocument(); + }); + + it("uses identical copy for a reset whether or not the address exists", async () => { + const user = userEvent.setup(); + vi.stubGlobal("fetch", vi.fn(async () => jsonResponse(200, { ok: true }))); + + render(); + await user.click(screen.getByRole("button", { name: /forgot your password/i })); + await user.type(screen.getByLabelText("Email"), "dev@example.com"); + await user.click(screen.getByRole("button", { name: /email me a reset link/i })); + + expect( + await screen.findByText("If that address has an account, a reset link is on its way."), + ).toBeInTheDocument(); + }); + + it("calls onDone after a successful sign-in", async () => { + const user = userEvent.setup(); + const onDone = vi.fn(); + vi.stubGlobal( + "fetch", + vi.fn(async (input: RequestInfo | URL) => { + const url = String(input); + if (url.endsWith("/auth/tokens")) { + return jsonResponse(200, { token: "minted", expires_at: "2099-01-01T00:00:00Z" }); + } + if (url.endsWith("/auth/me")) { + return jsonResponse(200, { role: "user" }); + } + return jsonResponse(404, {}); + }), + ); + + render(); + await user.type(screen.getByLabelText("Email"), "dev@example.com"); + await user.type(screen.getByLabelText("Password"), "secret"); + await user.click(screen.getByRole("button", { name: /sign in/i })); + + await waitFor(() => expect(onDone).toHaveBeenCalled()); + }); +}); diff --git a/web/src/components/StatusBadge.test.tsx b/web/src/components/StatusBadge.test.tsx new file mode 100644 index 00000000..9c750fde --- /dev/null +++ b/web/src/components/StatusBadge.test.tsx @@ -0,0 +1,18 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; + +import { RunStatusBadge } from "@/components/StatusBadge"; + +describe("RunStatusBadge", () => { + it("labels failed and passed runs", () => { + const { rerender } = render(); + expect(screen.getByText("Failed")).toBeInTheDocument(); + rerender(); + expect(screen.getByText("Passed")).toBeInTheDocument(); + }); + + it("treats completed as a finished success, not still-running", () => { + render(); + expect(screen.getByText("Completed")).toBeInTheDocument(); + }); +}); diff --git a/web/src/lib/api.test.ts b/web/src/lib/api.test.ts new file mode 100644 index 00000000..0b7af3b3 --- /dev/null +++ b/web/src/lib/api.test.ts @@ -0,0 +1,332 @@ +import { renderHook, waitFor } from "@testing-library/react"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +import { + categoryHealth, + deriveCategories, + healthOf, + useRegressionTests, + useRuns, + useSamples, +} from "@/lib/api"; +import type { RegressionTest } from "@/lib/types"; +import { createWrapper } from "@/test/query"; + +function jsonResponse(status: number, body: unknown): Response { + return new Response(JSON.stringify(body), { + status, + headers: { "Content-Type": "application/json" }, + }); +} + +const SHA = "abcdef0123456789abcdef0123456789abcdef01"; + +function liveRun(overrides: Record = {}) { + return { + run_id: 10, + status: "pass", + platform: "linux", + test_type: "commit", + repository: "CCExtractor/ccextractor", + branch: "master", + commit_sha: SHA, + pr_number: null, + created_at: "2026-10-01T00:00:00Z", + queued_at: null, + started_at: null, + completed_at: null, + github_link: "https://github.com/CCExtractor/ccextractor/commit/" + SHA, + ...overrides, + }; +} + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +describe("useRuns grouping", () => { + it("folds linux and windows for the same commit into one logical run", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async (input: RequestInfo | URL) => { + const url = String(input); + if (url.includes("/api/v1/runs?")) { + return jsonResponse(200, { + data: [ + liveRun({ run_id: 21, platform: "windows", status: "fail" }), + liveRun({ run_id: 20, platform: "linux", status: "pass" }), + ], + }); + } + return jsonResponse(404, {}); + }), + ); + + const { wrapper } = createWrapper(); + const { result, unmount } = renderHook(() => useRuns(), { wrapper }); + await waitFor(() => expect(result.current.data).toHaveLength(1)); + + const run = result.current.data![0]; + expect(run.id).toBe("21"); + expect(run.commit).toBe(SHA.slice(0, 9)); + expect(run.fork).toBe("CCExtractor/ccextractor"); + expect(run.platforms.map((p) => p.platform)).toEqual(["linux", "windows"]); + expect(run.platforms.find((p) => p.platform === "windows")?.status).toBe("fail"); + unmount(); + }); + + it("keeps a PR run distinct from a commit run on the same SHA", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async (input: RequestInfo | URL) => { + const url = String(input); + if (url.includes("/api/v1/runs?")) { + return jsonResponse(200, { + data: [ + liveRun({ run_id: 5, test_type: "pr", pr_number: 2406 }), + liveRun({ run_id: 4, test_type: "commit", pr_number: null }), + ], + }); + } + return jsonResponse(404, {}); + }), + ); + + const { wrapper } = createWrapper(); + const { result, unmount } = renderHook(() => useRuns(), { wrapper }); + await waitFor(() => expect(result.current.data).toHaveLength(2)); + expect(result.current.data!.map((r) => r.pr_nr).sort()).toEqual([2406, null].sort()); + unmount(); + }); +}); + +describe("useSamples mapping", () => { + it("normalizes extensions and keeps sample ids/shas from the API", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async (input: RequestInfo | URL) => { + const url = String(input); + if (url.includes("/api/v1/samples?")) { + return jsonResponse(200, { + data: [ + { + sample_id: 14, + sha: "abc", + extension: "ts", + original_name: "pal.ts", + tags: ["pal"], + regression_test_count: 3, + }, + { + sample_id: 121, + sha: "def", + extension: ".mkv", + original_name: "film.mkv", + tags: [], + regression_test_count: 1, + }, + ], + pagination: { total: 2, next_offset: null }, + }); + } + return jsonResponse(404, {}); + }), + ); + + const { wrapper } = createWrapper(); + const { result, unmount } = renderHook(() => useSamples(), { wrapper }); + await waitFor(() => expect(result.current.data).toHaveLength(2)); + expect(result.current.data).toEqual([ + { + id: 14, + sha: "abc", + extension: ".ts", + original_name: "pal.ts", + tags: ["pal"], + test_count: 3, + }, + { + id: 121, + sha: "def", + extension: ".mkv", + original_name: "film.mkv", + tags: [], + test_count: 1, + }, + ]); + unmount(); + }); +}); + +describe("useRegressionTests", () => { + it("joins sample SHA and treats a Linux-only failure as failing the commit", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async (input: RequestInfo | URL) => { + const url = String(input); + if (url.includes("/regression-tests?active=true")) { + return jsonResponse(200, { + data: [ + { + regression_test_id: 7, + sample_id: 14, + sample_name: "pal.ts", + command: "-autoprogram", + input_type: "ts", + output_type: "srt", + expected_rc: 0, + active: true, + description: null, + categories: ["General"], + }, + ], + pagination: { total: 1, next_offset: null }, + }); + } + if (url.includes("/regression-tests?active=false")) { + return jsonResponse(200, { + data: [], + pagination: { total: 0, next_offset: null }, + }); + } + if (url.includes("/runs/30/samples")) { + return jsonResponse(200, { + data: [{ regression_test_id: 7, status: "fail", runtime_ms: 100 }], + pagination: { total: 1, next_offset: null }, + }); + } + if (url.includes("/runs/32/samples")) { + return jsonResponse(200, { + data: [{ regression_test_id: 7, status: "pass", runtime_ms: 10 }], + pagination: { total: 1, next_offset: null }, + }); + } + if (url.includes("/api/v1/samples?")) { + return jsonResponse(200, { + data: [ + { + sample_id: 14, + sha: "deadbeef", + extension: ".ts", + original_name: "pal.ts", + tags: [], + regression_test_count: 1, + }, + ], + pagination: { total: 1, next_offset: null }, + }); + } + // History asks for master runs on their own so PR traffic cannot + // empty the sparkline (sample-platform#1246). + if (url.includes("branch=master")) { + return jsonResponse(200, { + data: [ + liveRun({ + run_id: 30, + platform: "linux", + status: "fail", + test_type: "commit", + }), + liveRun({ + run_id: 32, + platform: "windows", + status: "pass", + test_type: "commit", + }), + liveRun({ + run_id: 40, + platform: "linux", + status: "queued", + test_type: "commit", + }), + ], + }); + } + if (url.includes("/api/v1/runs?")) { + return jsonResponse(200, { + data: [ + liveRun({ + run_id: 31, + test_type: "pr", + pr_number: 99, + platform: "linux", + }), + ], + }); + } + return jsonResponse(404, { message: url }); + }), + ); + + const { wrapper } = createWrapper(); + const { result, unmount } = renderHook(() => useRegressionTests(), { wrapper }); + await waitFor(() => expect(result.current.data).toHaveLength(1)); + expect(result.current.data[0].sample_sha).toBe("deadbeef"); + expect(result.current.data[0].id).toBe(7); + await waitFor(() => expect(result.current.data[0].recent_results).toEqual(["fail"])); + expect(result.current.data[0].avg_runtime_ms).toBe(55); + unmount(); + }); +}); + +describe("401 handling", () => { + it("clears the session and reloads when the API says the token is dead", async () => { + localStorage.setItem( + "sp-session", + JSON.stringify({ + token: "stale", + email: "dev@example.com", + role: "user", + expires_at: new Date(Date.now() + 60_000).toISOString(), + }), + ); + Object.defineProperty(window, "location", { + configurable: true, + value: { reload: vi.fn() }, + }); + vi.stubGlobal("fetch", vi.fn(async () => jsonResponse(401, { message: "expired" }))); + + const { wrapper } = createWrapper(); + const { result, unmount } = renderHook(() => useSamples(), { wrapper }); + await waitFor(() => expect(result.current.isError).toBe(true)); + await vi.waitFor(() => { + expect(window.location.reload).toHaveBeenCalled(); + }); + expect(localStorage.getItem("sp-session")).toBeNull(); + unmount(); + }); +}); + +describe("healthOf / deriveCategories", () => { + const testRow = (overrides: Partial = {}): RegressionTest => ({ + id: 1, + sample_id: 1, + sample_name: "a.ts", + sample_sha: "x", + command: "-autoprogram", + input_type: "ts", + output_type: "srt", + expected_rc: 0, + active: true, + description: "", + categories: ["General"], + avg_runtime_ms: null, + recent_results: [], + ...overrides, + }); + + it("uses the latest non-skip result so a customized-run gap is not a failure", () => { + expect(healthOf(testRow({ recent_results: ["fail", "skip"] }))).toBe("fail"); + expect(healthOf(testRow({ recent_results: ["fail", "pass"] }))).toBe("pass"); + expect(healthOf(testRow({ recent_results: ["skip"] }))).toBe("skip"); + }); + + it("counts failing tests per category", () => { + const tests = [ + testRow({ id: 1, categories: ["General"], recent_results: ["fail"] }), + testRow({ id: 2, categories: ["General", "Options"], recent_results: ["pass"] }), + ]; + expect(categoryHealth("General", tests)).toEqual({ total: 2, failing: 1 }); + expect(deriveCategories(tests).map((c) => c.name)).toEqual(["General", "Options"]); + }); +}); diff --git a/web/src/lib/auth.test.ts b/web/src/lib/auth.test.ts new file mode 100644 index 00000000..b2677e0b --- /dev/null +++ b/web/src/lib/auth.test.ts @@ -0,0 +1,135 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { canManage, canOperateCi, getSession, login, logout, type Session } from "@/lib/auth"; + +const KEY = "sp-session"; + +function session(overrides: Partial = {}): Session { + return { + token: "tok", + email: "dev@example.com", + role: "user", + expires_at: new Date(Date.now() + 60_000).toISOString(), + ...overrides, + }; +} + +function jsonResponse(status: number, body: unknown): Response { + return new Response(JSON.stringify(body), { + status, + headers: { "Content-Type": "application/json" }, + }); +} + +describe("getSession", () => { + it("returns null when nothing is stored", () => { + expect(getSession()).toBeNull(); + }); + + it("drops an expired session", () => { + localStorage.setItem( + KEY, + JSON.stringify(session({ expires_at: new Date(Date.now() - 1000).toISOString() })), + ); + expect(getSession()).toBeNull(); + expect(localStorage.getItem(KEY)).toBeNull(); + }); + + it("returns a still-valid session", () => { + const s = session(); + localStorage.setItem(KEY, JSON.stringify(s)); + expect(getSession()).toEqual(s); + }); + + it("returns null for corrupt JSON", () => { + localStorage.setItem(KEY, "{not-json"); + expect(getSession()).toBeNull(); + }); +}); + +describe("role gates", () => { + it("lets admins and contributors manage the suite", () => { + expect(canManage(session({ role: "admin" }))).toBe(true); + expect(canManage(session({ role: "contributor" }))).toBe(true); + expect(canManage(session({ role: "tester" }))).toBe(false); + expect(canManage(session({ role: "user" }))).toBe(false); + expect(canManage(null)).toBe(false); + }); + + it("lets testers queue CI but not manage tests", () => { + const tester = session({ role: "tester" }); + expect(canOperateCi(tester)).toBe(true); + expect(canManage(tester)).toBe(false); + expect(canOperateCi(session({ role: "user" }))).toBe(false); + }); +}); + +describe("login", () => { + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it("stores the session after minting a token and reading /auth/me", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => { + const url = String(input); + if (url.endsWith("/auth/tokens") && init?.method === "POST") { + return jsonResponse(200, { + token: "minted", + expires_at: "2099-01-01T00:00:00Z", + }); + } + if (url.endsWith("/auth/me")) { + return jsonResponse(200, { role: "contributor" }); + } + return jsonResponse(404, { message: "unexpected" }); + }), + ); + + const s = await login("dev@example.com", "secret"); + expect(s.token).toBe("minted"); + expect(s.role).toBe("contributor"); + expect(s.email).toBe("dev@example.com"); + expect(getSession()?.token).toBe("minted"); + }); + + it("does not reveal whether the email exists on 401", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async () => jsonResponse(401, { message: "no such user" })), + ); + + await expect(login("dev@example.com", "wrong")).rejects.toThrow( + "Email or password is incorrect.", + ); + expect(getSession()).toBeNull(); + }); +}); + +describe("logout", () => { + beforeEach(() => { + vi.stubGlobal( + "fetch", + vi.fn(async () => jsonResponse(204, {})), + ); + Object.defineProperty(window, "location", { + configurable: true, + value: { reload: vi.fn() }, + }); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it("clears storage and reloads once even if called twice", async () => { + localStorage.setItem(KEY, JSON.stringify(session())); + logout(); + logout(); + await vi.waitFor(() => { + expect(window.location.reload).toHaveBeenCalledTimes(1); + }); + expect(localStorage.getItem(KEY)).toBeNull(); + }); +}); diff --git a/web/src/lib/cues.test.ts b/web/src/lib/cues.test.ts new file mode 100644 index 00000000..76949d11 --- /dev/null +++ b/web/src/lib/cues.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, it } from "vitest"; + +import { clock, cuesToVtt, parseCues } from "@/lib/cues"; + +const SRT = `1 +00:00:01,000 --> 00:00:02,500 +Hello + +2 +00:00:03,000 --> 00:00:04,000 +World +`; + +const VTT = `WEBVTT + +00:00:01.000 --> 00:00:02.500 +Hello +`; + +describe("parseCues", () => { + it("reads SubRip commas and WebVTT dots as the same timings", () => { + const srt = parseCues(SRT); + const vtt = parseCues(VTT); + expect(srt).toEqual([ + { start: 1, end: 2.5, text: "Hello" }, + { start: 3, end: 4, text: "World" }, + ]); + expect(vtt).toEqual([{ start: 1, end: 2.5, text: "Hello" }]); + }); + + it("yields nothing for a plain transcript so the caller can show the file", () => { + expect(parseCues("just some text\nno timings")).toEqual([]); + }); +}); + +describe("clock / cuesToVtt", () => { + it("formats the cue gutter as m:ss", () => { + expect(clock(0)).toBe("0:00"); + expect(clock(65)).toBe("1:05"); + }); + + it("round-trips cues into a WebVTT track", () => { + const vtt = cuesToVtt([{ start: 1, end: 2.5, text: "Hello" }]); + expect(vtt.startsWith("WEBVTT\n\n")).toBe(true); + expect(vtt).toContain("00:00:01.000 --> 00:00:02.500"); + expect(vtt).toContain("Hello"); + }); +}); diff --git a/web/src/lib/validate.test.ts b/web/src/lib/validate.test.ts new file mode 100644 index 00000000..257a6cb4 --- /dev/null +++ b/web/src/lib/validate.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from "vitest"; + +import { + githubUrl, + httpsUrl, + isBranchName, + isCommitSha, + isRepoSlug, +} from "@/lib/validate"; + +describe("githubUrl", () => { + it("keeps a real github.com https URL", () => { + expect(githubUrl("https://github.com/CCExtractor/ccextractor")).toBe( + "https://github.com/CCExtractor/ccextractor", + ); + }); + + it("drops javascript: and non-github hrefs so they never reach ", () => { + expect(githubUrl("javascript:alert(1)")).toBeNull(); + expect(githubUrl("https://evil.example/CCExtractor/ccextractor")).toBeNull(); + expect(githubUrl("http://github.com/CCExtractor/ccextractor")).toBeNull(); + expect(githubUrl(null)).toBeNull(); + }); +}); + +describe("httpsUrl", () => { + it("allows signed https downloads and rejects data/javascript", () => { + expect(httpsUrl("https://storage.googleapis.com/bucket/file")).toBe( + "https://storage.googleapis.com/bucket/file", + ); + expect(httpsUrl("javascript:alert(1)")).toBeNull(); + expect(httpsUrl("data:text/html,hi")).toBeNull(); + }); +}); + +describe("form gates", () => { + it("accepts a full 40-hex SHA and a owner/repo slug", () => { + expect(isCommitSha("a".repeat(40))).toBe(true); + expect(isCommitSha("a".repeat(39))).toBe(false); + expect(isRepoSlug("CCExtractor/sample-platform")).toBe(true); + expect(isRepoSlug("not-a-slug")).toBe(false); + }); + + it("rejects branch names that would be unsafe to forward to CI", () => { + expect(isBranchName("fix/restart-clears-gcp")).toBe(true); + expect(isBranchName("-leading-dash")).toBe(false); + expect(isBranchName("has space")).toBe(false); + expect(isBranchName("bad;rm -rf")).toBe(false); + }); +}); diff --git a/web/src/test/query.tsx b/web/src/test/query.tsx new file mode 100644 index 00000000..e88f70e6 --- /dev/null +++ b/web/src/test/query.tsx @@ -0,0 +1,15 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import type { ReactNode } from "react"; + +/** Isolated query client so tests never share cache or retry. */ +export function createWrapper() { + const client = new QueryClient({ + defaultOptions: { queries: { retry: false, refetchOnWindowFocus: false } }, + }); + return { + client, + wrapper: ({ children }: { children: ReactNode }) => ( + {children} + ), + }; +} diff --git a/web/src/test/setup.ts b/web/src/test/setup.ts new file mode 100644 index 00000000..83abbca8 --- /dev/null +++ b/web/src/test/setup.ts @@ -0,0 +1,9 @@ +import "@testing-library/jest-dom/vitest"; +import { afterEach } from "vitest"; + +import { cleanup } from "@testing-library/react"; + +afterEach(() => { + cleanup(); + localStorage.clear(); +}); diff --git a/web/vite.config.ts b/web/vite.config.ts index 20c1198b..efaf366e 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -1,14 +1,14 @@ import path from "node:path"; import tailwindcss from "@tailwindcss/vite"; import react from "@vitejs/plugin-react"; -import { defineConfig } from "vite"; +import { defineConfig } from "vitest/config"; // https://vite.dev/config/ export default defineConfig({ plugins: [react(), tailwindcss()], resolve: { alias: { - "@": path.resolve(__dirname, "./src"), + "@": path.resolve(import.meta.dirname, "./src"), }, }, server: { @@ -21,4 +21,10 @@ export default defineConfig({ }, }, }, + test: { + environment: "jsdom", + setupFiles: "./src/test/setup.ts", + restoreMocks: true, + css: false, + }, });