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,
+ },
});